New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: removal of interface{} type in Go 2.0 #20188

Closed
henryas opened this Issue May 1, 2017 · 11 comments

Comments

Projects
None yet
9 participants
@henryas

henryas commented May 1, 2017

Hi,

Given Go's powerful interface, I think it is possible to remove the "blank interface" type entirely, making the language simpler and more streamlined. For instance, instead of fmt.Printf(format string, args ...interface{}), we can perhaps write it as fmt.Printf(format string, args ...Stringer). The idea is to shift as much work as possible to the compiler and leave very little to the runtime type checking.

At the moment, the "blank interface" type is increasingly being misused as a convenient way to bypass Go compiler's type checking. There are places where "blank interface" is used in place where explicit interface could have been used.

The problem with runtime type checking is that you will never know if there is a problem until it is run. With static type checking, you can catch the bug during compile time. By removing the "blank interface" type, Go will have a stronger type system.

In addition, with explicit interface, it will be easier to understand what a type does than with "blank interface". There will also be less code associated with runtime type checking.

I know this will be a breaking change to the language as well as to the existing standard library, which is why I am proposing this for Go 2.x.

@gopherbot gopherbot added this to the Proposal milestone May 1, 2017

@gopherbot gopherbot added the Proposal label May 1, 2017

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

ianlancetaylor May 1, 2017

Contributor

Right now fmt.Printf can print any type, including types that don't implement the fmt.Stringer interface. How do you propose for that to work without an empty interface type?

Contributor

ianlancetaylor commented May 1, 2017

Right now fmt.Printf can print any type, including types that don't implement the fmt.Stringer interface. How do you propose for that to work without an empty interface type?

@henryas

This comment has been minimized.

Show comment
Hide comment
@henryas

henryas May 1, 2017

You delegate the formatting work to the individual types. Perhaps the above example with Stringer is an oversimplification.

Let's define a Formatter interface that contains the following method: Format(fmt string) string. The argument fmt accepts precision modifier, etc. Then, implement the Formatter interface for each target type. The fmt is then defined as fmt.Printf(format string, args ...Formatter) and you don't need blank interface type anymore.

Another advantage of this approach is that fmt can now work with user-defined types and custom formatting.

henryas commented May 1, 2017

You delegate the formatting work to the individual types. Perhaps the above example with Stringer is an oversimplification.

Let's define a Formatter interface that contains the following method: Format(fmt string) string. The argument fmt accepts precision modifier, etc. Then, implement the Formatter interface for each target type. The fmt is then defined as fmt.Printf(format string, args ...Formatter) and you don't need blank interface type anymore.

Another advantage of this approach is that fmt can now work with user-defined types and custom formatting.

@huguesb

This comment has been minimized.

Show comment
Hide comment
@huguesb

huguesb May 1, 2017

Contributor

Note that there is already a Formatter interface: https://golang.org/pkg/fmt/#Formatter

How would your proposal handle builtin types (int, float, ...) ? What about the %p verb used to print pointers ?

Contributor

huguesb commented May 1, 2017

Note that there is already a Formatter interface: https://golang.org/pkg/fmt/#Formatter

How would your proposal handle builtin types (int, float, ...) ? What about the %p verb used to print pointers ?

@henryas

This comment has been minimized.

Show comment
Hide comment
@henryas

henryas May 1, 2017

It is merely a naming issue. The interface can be renamed to something else if you wish.

Currently, Go 1.x treats built-in types as special types. You can't attach any method on them. In my opinion, a type is a type. It would be better if we can treat built-in types just like any other types, and remove as much magic as possible from Go. Assuming we can attach methods to built-in types, we would implement the required interface for each built-in type, and they would be accessible to fmt. In fact, you can define them under the fmt package, so that they would be available only when fmt package is imported.

Alternatively, we can follow what other languages have done by wrapping the primitive types. For instance, Java has Integer class to wrap the int type. I don't think this approach is ideal. It seems like a convoluted way to merely attach behavior to primitive types. However, the option is there for consideration. In fact, you can already do this in Go 1.x.

As for %p, I don't currently have any answer to that. I am on a vacation and I am lazy to think about it. Removing "blank interface" type itself already breaks Go 1.x and its existing library, so quit trying to fit this into Go 1.x because it won't fit.

However, the benefit is clear. Removing the "blank interface" leads to a better design. For instance, in the fmt source code, you may notice it has many little functions to format each different type. Rather than hardcoding this in fmt, it would be better to move these functions into their respective types. As a result, fmt too is more flexible and extensible. Future support for more types can be done without changing the fmt codes. All this is intuitively accomplished just by removing the "blank interface" type from Go.

Go's interface is powerful, and "blank interface" type is currently undermining Go's interface from realizing its full potential.

henryas commented May 1, 2017

It is merely a naming issue. The interface can be renamed to something else if you wish.

Currently, Go 1.x treats built-in types as special types. You can't attach any method on them. In my opinion, a type is a type. It would be better if we can treat built-in types just like any other types, and remove as much magic as possible from Go. Assuming we can attach methods to built-in types, we would implement the required interface for each built-in type, and they would be accessible to fmt. In fact, you can define them under the fmt package, so that they would be available only when fmt package is imported.

Alternatively, we can follow what other languages have done by wrapping the primitive types. For instance, Java has Integer class to wrap the int type. I don't think this approach is ideal. It seems like a convoluted way to merely attach behavior to primitive types. However, the option is there for consideration. In fact, you can already do this in Go 1.x.

As for %p, I don't currently have any answer to that. I am on a vacation and I am lazy to think about it. Removing "blank interface" type itself already breaks Go 1.x and its existing library, so quit trying to fit this into Go 1.x because it won't fit.

However, the benefit is clear. Removing the "blank interface" leads to a better design. For instance, in the fmt source code, you may notice it has many little functions to format each different type. Rather than hardcoding this in fmt, it would be better to move these functions into their respective types. As a result, fmt too is more flexible and extensible. Future support for more types can be done without changing the fmt codes. All this is intuitively accomplished just by removing the "blank interface" type from Go.

Go's interface is powerful, and "blank interface" type is currently undermining Go's interface from realizing its full potential.

@griesemer

This comment has been minimized.

Show comment
Hide comment
@griesemer

griesemer May 1, 2017

Contributor

The "blank interface" as you call it falls out naturally from the definition of interfaces. Removing it from the language would require making the language more complex for no good reason. Forcing "good design" upon people by restricting the language this way is not going to be successful. If somebody wants to have a "minimal" interface, they will still use that minimal interface, even if that means it needs to have at least one method. Having the blank interface in Go enables Go to reach its full potential - not the other way around.

Needless to say that I am against this proposal.

Contributor

griesemer commented May 1, 2017

The "blank interface" as you call it falls out naturally from the definition of interfaces. Removing it from the language would require making the language more complex for no good reason. Forcing "good design" upon people by restricting the language this way is not going to be successful. If somebody wants to have a "minimal" interface, they will still use that minimal interface, even if that means it needs to have at least one method. Having the blank interface in Go enables Go to reach its full potential - not the other way around.

Needless to say that I am against this proposal.

@henryas

This comment has been minimized.

Show comment
Hide comment
@henryas

henryas May 1, 2017

The good design aspect is just the side effect of having an explicit interface. The primary objective -as stated in the original post- is to shift as much as possible to static type checking rather than relying on dynamic type checking. The second objective is to streamline the language and remove extra features that are non-essentials.

Anyhow, I just think it is wrong for a function to say "I'll take anything." or "I'll return anything."

henryas commented May 1, 2017

The good design aspect is just the side effect of having an explicit interface. The primary objective -as stated in the original post- is to shift as much as possible to static type checking rather than relying on dynamic type checking. The second objective is to streamline the language and remove extra features that are non-essentials.

Anyhow, I just think it is wrong for a function to say "I'll take anything." or "I'll return anything."

@huguesb

This comment has been minimized.

Show comment
Hide comment
@huguesb

huguesb May 1, 2017

Contributor

Anyhow, I just think it is wrong for a function to say "I'll take anything." or "I'll return anything."

How do you propose to implement something like atomic.Value then? One option would be generics but a lot of ink has already been spilled on that topic with no satisfactory conclusion so if you assume that as a pre-requisite it's much less likely that this specific proposal will be accepted.

Contributor

huguesb commented May 1, 2017

Anyhow, I just think it is wrong for a function to say "I'll take anything." or "I'll return anything."

How do you propose to implement something like atomic.Value then? One option would be generics but a lot of ink has already been spilled on that topic with no satisfactory conclusion so if you assume that as a pre-requisite it's much less likely that this specific proposal will be accepted.

@griesemer

This comment has been minimized.

Show comment
Hide comment
@griesemer

griesemer May 2, 2017

Contributor

@henryas It's never been Go's primary objective to statically type checking everything. In general, there's programs that cannot be statically type-checked, no matter the type system. There's many meaningful programs we want to express where we want to use dynamic type checking. It makes not much sense to introduce an artificial restriction that doesn't solve any specific problem and will be circumvented as necessary anyway. Finally, you're not streamlining the language, you're adding complexity.

Anyhow, I just think it is wrong for a function to say "I'll take anything." or "I'll return anything."

This sounds very much like your personal belief/ideology - which you're of course entitled to - but it is not a convincing argument for others.

Contributor

griesemer commented May 2, 2017

@henryas It's never been Go's primary objective to statically type checking everything. In general, there's programs that cannot be statically type-checked, no matter the type system. There's many meaningful programs we want to express where we want to use dynamic type checking. It makes not much sense to introduce an artificial restriction that doesn't solve any specific problem and will be circumvented as necessary anyway. Finally, you're not streamlining the language, you're adding complexity.

Anyhow, I just think it is wrong for a function to say "I'll take anything." or "I'll return anything."

This sounds very much like your personal belief/ideology - which you're of course entitled to - but it is not a convincing argument for others.

@henryas

This comment has been minimized.

Show comment
Hide comment
@henryas

henryas May 3, 2017

Sorry for the late reply. I have given it a good thought and let it sink for a bit during these couple of days, and I think you guys are right. There are use cases where blank interface type is irreplaceable in Go.

I have been learning Haskell and I am impressed with its type system. Haskell is able to figure out all the types during compile time, and I thought I could bring some of that to Go. I guess it's not possible. The key difference, as Huguesb pointed out, is that Go doesn't have generics. In the absence of generics, blank interface type becomes necessary.

This discussion has given me a new insight how the different components in Go work. Thanks!

I will let this thread linger for a bit in case if anyone has anything to add, but feel free to close it.

henryas commented May 3, 2017

Sorry for the late reply. I have given it a good thought and let it sink for a bit during these couple of days, and I think you guys are right. There are use cases where blank interface type is irreplaceable in Go.

I have been learning Haskell and I am impressed with its type system. Haskell is able to figure out all the types during compile time, and I thought I could bring some of that to Go. I guess it's not possible. The key difference, as Huguesb pointed out, is that Go doesn't have generics. In the absence of generics, blank interface type becomes necessary.

This discussion has given me a new insight how the different components in Go work. Thanks!

I will let this thread linger for a bit in case if anyone has anything to add, but feel free to close it.

@randall77 randall77 closed this May 3, 2017

@happilymarrieddad

This comment has been minimized.

Show comment
Hide comment
@happilymarrieddad

happilymarrieddad Feb 22, 2018

I'm not knowledgeable enough to add anything to this discussion but I wanted to say very interesting. Thanks guys!

I'm not knowledgeable enough to add anything to this discussion but I wanted to say very interesting. Thanks guys!

@WowVeryLogin

This comment has been minimized.

Show comment
Hide comment
@WowVeryLogin

WowVeryLogin Mar 18, 2018

I don't think it is a good idea because empty interfaces can be very handy in situation like when you don't want to have any information of an object you are working with.

For example, I am writing proxy-server, which sanitizes and redirects requests to real servers, which work with databases and return array of some objects (auth-tokens in my case) in reply. I really don't want my proxy-server to know anything about auth-tokens, their structure and have any dependency with realization of them, all I want is my proxy-server to expect an array of objects and pass it to caller. So using []interface{} seems to be pretty idiomatic. By this I put emphasis on structure of response (that it is an array), but really don't make any operations with its content, so I don't want to add type, concrete interface or any other restrictions to it. So as a result, I don't have any dependencies and additional information about types where I don't need them.

I don't think it is a good idea because empty interfaces can be very handy in situation like when you don't want to have any information of an object you are working with.

For example, I am writing proxy-server, which sanitizes and redirects requests to real servers, which work with databases and return array of some objects (auth-tokens in my case) in reply. I really don't want my proxy-server to know anything about auth-tokens, their structure and have any dependency with realization of them, all I want is my proxy-server to expect an array of objects and pass it to caller. So using []interface{} seems to be pretty idiomatic. By this I put emphasis on structure of response (that it is an array), but really don't make any operations with its content, so I don't want to add type, concrete interface or any other restrictions to it. So as a result, I don't have any dependencies and additional information about types where I don't need them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment