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: Go 2: Have functions auto-implement interfaces with only a single method of that same signature #21670

Open
mediocregopher opened this Issue Aug 28, 2017 · 34 comments

Comments

Projects
None yet
@mediocregopher

mediocregopher commented Aug 28, 2017

For example, instead of having to have http.HandlerFunc, a function with the signature func(http.ResponsWriter, *http.Request) would automatically implement http.Handler.


Where I work we have at least 4 internal packages which use some form of interface layering/middleware, similar to http.Handler and it's middlewares like http.StripPrefix and http.TimeoutHandler. It's a very powerful pattern which I don't think needs justification (I could give one, but it's not really what this proposal is about).

Having now written a few versions of what's essentially the exact same type I have a few different reasons I think this change is warranted:

  • It reduces confusion for programmers new to go. Anecdotally, pretty much every new go developer I've worked with has tried to pass a function like this and been confused as to why they can't. While I understand that this is a fairly small blip on the go learning-curve, I do think this is an indication that this function-as-interface behavior fits better with devs' mental model of how interfaces work.

  • It would allow for removing some public-facing types/functions from packages like http (if such a thing is on the table, at the very least it allows for reducing api surface area in new and internal packages). By the same token it reduces code-clutter in cases where something like HandlerFunc isn't available and you have to wrap your function in the WhateverFunc type inline.

  • It would be almost completely backwards compatible. There's no new syntax, and all existing code using types like http.HandlerFunc would continue to work as they are. The only exception I can think of is if someone is doing something with a switch v.(type) {...}, where v might be a function, and the dev expects that the function won't implement the interface as part of that switch behaving correctly (or the equivalent with if-statements or whatever).

These reasons don't justify a language change individually, but in sum I think it becomes worth talking about. Sorry if this has been proposed already, I searched for it but wasn't able to find anything related.

@gopherbot gopherbot added this to the Proposal milestone Aug 28, 2017

@gopherbot gopherbot added the Proposal label Aug 28, 2017

@dsnet

This comment has been minimized.

Show comment
Hide comment
@dsnet

dsnet Aug 28, 2017

Member

I have wanted this many times. I often find myself creating custom io.Reader and io.Writer in unit tests via closured functions. I often have to have this separate type defined so I can convert the function to satisfy the interface:

type ReaderFunc func([]byte) (int, error)
func (f Readerfunc) Read(b []byte) (int, error) { return f(b) }
Member

dsnet commented Aug 28, 2017

I have wanted this many times. I often find myself creating custom io.Reader and io.Writer in unit tests via closured functions. I often have to have this separate type defined so I can convert the function to satisfy the interface:

type ReaderFunc func([]byte) (int, error)
func (f Readerfunc) Read(b []byte) (int, error) { return f(b) }
@tv42

This comment has been minimized.

Show comment
Hide comment
@tv42

tv42 Aug 28, 2017

So a func foo() implements all of the single-method no arguments interfaces? I don't like the sound of that. The method name matters.

tv42 commented Aug 28, 2017

So a func foo() implements all of the single-method no arguments interfaces? I don't like the sound of that. The method name matters.

@dsnet

This comment has been minimized.

Show comment
Hide comment
@dsnet

dsnet Aug 28, 2017

Member

How many single-method interfaces are there with no inputs and outputs?

Member

dsnet commented Aug 28, 2017

How many single-method interfaces are there with no inputs and outputs?

@mediocregopher

This comment has been minimized.

Show comment
Hide comment
@mediocregopher

mediocregopher Aug 28, 2017

I don't think the concern of interfaces being accidentally implemented due to unspecific signatures is constrained to just this, that concern could just as well be applied to "all structs with a method with signature func()", which is something which already exists and is fairly common.

mediocregopher commented Aug 28, 2017

I don't think the concern of interfaces being accidentally implemented due to unspecific signatures is constrained to just this, that concern could just as well be applied to "all structs with a method with signature func()", which is something which already exists and is fairly common.

@griesemer

This comment has been minimized.

Show comment
Hide comment
@griesemer

griesemer Aug 28, 2017

Contributor

This has come up before and various people have proposed more or less the same thing. See for instance: https://groups.google.com/forum/#!searchin/golang-nuts/single-method$20interface%7B%7D/golang-nuts/AAVCVpWqGCo/cWtHROB2K_8J

I wished for this a few years back for "completeness" sake, when we introduced method values: We can go from a method to a function; so why shouldn't we be able to go from a function to a method (very loosely speaking).

Contributor

griesemer commented Aug 28, 2017

This has come up before and various people have proposed more or less the same thing. See for instance: https://groups.google.com/forum/#!searchin/golang-nuts/single-method$20interface%7B%7D/golang-nuts/AAVCVpWqGCo/cWtHROB2K_8J

I wished for this a few years back for "completeness" sake, when we introduced method values: We can go from a method to a function; so why shouldn't we be able to go from a function to a method (very loosely speaking).

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Aug 28, 2017

Member

It would be easy to go nuts allocating wrappers if something got passed back and forth between func and interface, as demonstrated in this very useful program below:

package main

type Fooer interface { foo() }

func foo() {}

func R(fooer Fooer) {
  R(fooer.foo) // interface to method value then func to interface
}

func main() {
  R(foo)
}
Member

jimmyfrasche commented Aug 28, 2017

It would be easy to go nuts allocating wrappers if something got passed back and forth between func and interface, as demonstrated in this very useful program below:

package main

type Fooer interface { foo() }

func foo() {}

func R(fooer Fooer) {
  R(fooer.foo) // interface to method value then func to interface
}

func main() {
  R(foo)
}
@ccbrown

This comment has been minimized.

Show comment
Hide comment
@ccbrown

ccbrown Aug 29, 2017

I wished for this a few years back for "completeness" sake, when we introduced method values: We can go from a method to a function; so why shouldn't we be able to go from a function to a method (very loosely speaking).

To me it would make more sense to be able to create an interface implementation out of one or more functions like so:

type FooBarer interface {
    Foo() string
    Bar() string
}

func main() {
    foobar := FooBarer{
        Foo: func() { return "foo" },
        Bar: func() { return "bar" },
    }

    handler := http.Handler{myHandlerFunction}
}

Then you can truly go full circle:

func fullCircle(fb FooBarer) FooBarer {
	foo := fb.Foo
	bar := fb.Bar
        return FooBarer{
            Foo: foo,
            Bar: bar,
        }
}

I'm not sure I would actually suggest such a change... but it's the one that came to mind when I saw the mention of "completeness", and I think it would happen to satisfy the original proposal's need as well.

ccbrown commented Aug 29, 2017

I wished for this a few years back for "completeness" sake, when we introduced method values: We can go from a method to a function; so why shouldn't we be able to go from a function to a method (very loosely speaking).

To me it would make more sense to be able to create an interface implementation out of one or more functions like so:

type FooBarer interface {
    Foo() string
    Bar() string
}

func main() {
    foobar := FooBarer{
        Foo: func() { return "foo" },
        Bar: func() { return "bar" },
    }

    handler := http.Handler{myHandlerFunction}
}

Then you can truly go full circle:

func fullCircle(fb FooBarer) FooBarer {
	foo := fb.Foo
	bar := fb.Bar
        return FooBarer{
            Foo: foo,
            Bar: bar,
        }
}

I'm not sure I would actually suggest such a change... but it's the one that came to mind when I saw the mention of "completeness", and I think it would happen to satisfy the original proposal's need as well.

@dsnet

This comment has been minimized.

Show comment
Hide comment
@dsnet

dsnet Aug 29, 2017

Member

@ccbrown, your idea also makes it explicit which method the function applies to, alleviating the concern that @tv42 had.

Member

dsnet commented Aug 29, 2017

@ccbrown, your idea also makes it explicit which method the function applies to, alleviating the concern that @tv42 had.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Aug 29, 2017

Member

@ccbrown interestingly that idea appears to have been considered #4146 except that issue was for adding it to reflect only. But if it's good enough for reflect . . .

Member

jimmyfrasche commented Aug 29, 2017

@ccbrown interestingly that idea appears to have been considered #4146 except that issue was for adding it to reflect only. But if it's good enough for reflect . . .

@tv42

This comment has been minimized.

Show comment
Hide comment
@tv42

tv42 Aug 29, 2017

@dsnet: A single non-exported func() method is a common way to mark interfaces as impossible for outsiders to implement.

Next up, func([]byte), or func([]byte) error. That means nothing without the method name!

tv42 commented Aug 29, 2017

@dsnet: A single non-exported func() method is a common way to mark interfaces as impossible for outsiders to implement.

Next up, func([]byte), or func([]byte) error. That means nothing without the method name!

@dsnet

This comment has been minimized.

Show comment
Hide comment
@dsnet

dsnet Aug 29, 2017

Member

Isn't that why @ccbrown's proposal avoids that problem?

w := io.Writer{Write: f} // Gives an explicit name to f

Note, that you can also lose the name when you extract a function from an interface (we can do this today):

f := w.Write // f is just a function, no particular "name" to it
Member

dsnet commented Aug 29, 2017

Isn't that why @ccbrown's proposal avoids that problem?

w := io.Writer{Write: f} // Gives an explicit name to f

Note, that you can also lose the name when you extract a function from an interface (we can do this today):

f := w.Write // f is just a function, no particular "name" to it
@tv42

This comment has been minimized.

Show comment
Hide comment
@tv42

tv42 Aug 29, 2017

@dsnet I wasn't responding to ccbrown, I was responding to the original proposal and to your question. Method values are just functions, there's no confusion there; the confusing only arises from accidentally implementing an interface.

tv42 commented Aug 29, 2017

@dsnet I wasn't responding to ccbrown, I was responding to the original proposal and to your question. Method values are just functions, there's no confusion there; the confusing only arises from accidentally implementing an interface.

@griesemer

This comment has been minimized.

Show comment
Hide comment
@griesemer

griesemer Aug 29, 2017

Contributor

@ccbrown This idea (#21670 (comment)) has also been proposed in the past by @Sajmani, albeit only Go Team internally, I believe.

Contributor

griesemer commented Aug 29, 2017

@ccbrown This idea (#21670 (comment)) has also been proposed in the past by @Sajmani, albeit only Go Team internally, I believe.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Aug 29, 2017

Member

There's something definitely appealing about the symmetry of @ccbrown's construction.

You can kind of do something similar now using

type FooBarer interface {
  Foo() string
  Bar() string
}
type FooBar struct {
  DoFoo func() string
  DoBar func() string
}
func (f FooBar) Foo() string { return f.DoFoo() }
func (f FooBar) Bar() string { return f.DoBar() }

It's a very useful construction for satisfying interfaces in terms of closures and the equivalent of the http.HandleFunc pattern extended to multi-method interfaces.

The major differences from @ccbrown's syntax are that the type name is different and the field names cannot correspond to the method names.

However, it does provide a greater flexibility. Namely, you can define methods in addition to the func fields. In particular, you can define methods that operate on data:

//example borrowed from the thread that introduced sort.Slice
type Sorter struct {
  Length int
  Swapper func(i, j int)
  Lesser func(i, j int) bool
}
func (s Sorter) Len() int { return s.Length }
func (s Sorter) Swap(i, j int) { s.Swapper(i, j) }
func (s Sorter) Less(i, j int) bool { return s.Lesser(i, j) }

Not pictured above, as it doesn't really apply, but you could also add sensible defaults to a method if the corresponding func field is nil.

Having an interface consider a field value that's a func with the correct name and signature to be the same as a method would reduce Sorter to

type Sorter struct {
  Length int
  Swap func(i, j int)
  Less func(i, j int) bool
}
func (s Sorter) Len() int { return s.Length }

That saves some boilerplate. Having the field names correspond to the method names makes it read a little better. Though you do lose the ability to provide a sensible default implementation if one of the pseudo-methods is nil, but you could still do that by using a field name that doesn't match the interface.

I'm not sure that buys enough to be worth it, though.

It's slightly more verbose at the definition and call sites but for the sake of completion you can also do this:

type Sorter struct { //data struct with the names we want
  Len int
  Swap func(i, j int)
  Less func(i, j int) bool
}

type sorter struct { //actual implementation of the interface
  len int
  swap func(i, j int)
  less func(i, j int)
}
func (s sorter) Len() int { return s.len }
func (s sorter) Swap(i, j int) { s.swap(i, j) }
func (s sorter) Less(i, j int) bool { return s.less(i, j) }

func NewSorter(s Sorter) sort.Interface { //translator
  //could also assign default implementations for nil Swap/Less,
  //if it were applicable
  return sorter{
    len: s.Len,
    swap: s.Swap,
    less: s.Less,
  }
}

That let's you have the field names be the method names which is clearer, but there's the extra step to build the implementation.

It would actually look pretty good if you could elide the struct name when calling NewSorter, though:

return packagename.NewSorter({
  Len: n,
  Swap: func(i, j int) {
    // ...
  },
  Less: func(i, j bool) {
    // ...
  },
})
Member

jimmyfrasche commented Aug 29, 2017

There's something definitely appealing about the symmetry of @ccbrown's construction.

You can kind of do something similar now using

type FooBarer interface {
  Foo() string
  Bar() string
}
type FooBar struct {
  DoFoo func() string
  DoBar func() string
}
func (f FooBar) Foo() string { return f.DoFoo() }
func (f FooBar) Bar() string { return f.DoBar() }

It's a very useful construction for satisfying interfaces in terms of closures and the equivalent of the http.HandleFunc pattern extended to multi-method interfaces.

The major differences from @ccbrown's syntax are that the type name is different and the field names cannot correspond to the method names.

However, it does provide a greater flexibility. Namely, you can define methods in addition to the func fields. In particular, you can define methods that operate on data:

//example borrowed from the thread that introduced sort.Slice
type Sorter struct {
  Length int
  Swapper func(i, j int)
  Lesser func(i, j int) bool
}
func (s Sorter) Len() int { return s.Length }
func (s Sorter) Swap(i, j int) { s.Swapper(i, j) }
func (s Sorter) Less(i, j int) bool { return s.Lesser(i, j) }

Not pictured above, as it doesn't really apply, but you could also add sensible defaults to a method if the corresponding func field is nil.

Having an interface consider a field value that's a func with the correct name and signature to be the same as a method would reduce Sorter to

type Sorter struct {
  Length int
  Swap func(i, j int)
  Less func(i, j int) bool
}
func (s Sorter) Len() int { return s.Length }

That saves some boilerplate. Having the field names correspond to the method names makes it read a little better. Though you do lose the ability to provide a sensible default implementation if one of the pseudo-methods is nil, but you could still do that by using a field name that doesn't match the interface.

I'm not sure that buys enough to be worth it, though.

It's slightly more verbose at the definition and call sites but for the sake of completion you can also do this:

type Sorter struct { //data struct with the names we want
  Len int
  Swap func(i, j int)
  Less func(i, j int) bool
}

type sorter struct { //actual implementation of the interface
  len int
  swap func(i, j int)
  less func(i, j int)
}
func (s sorter) Len() int { return s.len }
func (s sorter) Swap(i, j int) { s.swap(i, j) }
func (s sorter) Less(i, j int) bool { return s.less(i, j) }

func NewSorter(s Sorter) sort.Interface { //translator
  //could also assign default implementations for nil Swap/Less,
  //if it were applicable
  return sorter{
    len: s.Len,
    swap: s.Swap,
    less: s.Less,
  }
}

That let's you have the field names be the method names which is clearer, but there's the extra step to build the implementation.

It would actually look pretty good if you could elide the struct name when calling NewSorter, though:

return packagename.NewSorter({
  Len: n,
  Swap: func(i, j int) {
    // ...
  },
  Less: func(i, j bool) {
    // ...
  },
})
@Sajmani

This comment has been minimized.

Show comment
Hide comment
@Sajmani

Sajmani Aug 29, 2017

Contributor
Contributor

Sajmani commented Aug 29, 2017

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Aug 29, 2017

Member

@Sajmani wouldn't the same implementation complexity be required for #16522 / #4146 ? #16522 is blocking useful features (always discussed in #4146 but in the end often turning out to be better implemented via #16522 )

Member

jimmyfrasche commented Aug 29, 2017

@Sajmani wouldn't the same implementation complexity be required for #16522 / #4146 ? #16522 is blocking useful features (always discussed in #4146 but in the end often turning out to be better implemented via #16522 )

@Sajmani

This comment has been minimized.

Show comment
Hide comment
@Sajmani

Sajmani Aug 29, 2017

Contributor
Contributor

Sajmani commented Aug 29, 2017

@metakeule

This comment has been minimized.

Show comment
Hide comment
@metakeule

metakeule Aug 30, 2017

Although it goes against Go conventions, I would question, if an interface is needed in the first place, if it contains just a single method.

Simply accepting a callback works for both cases, is less code to write and more flexible:

func handler1(http.ResponsWriter, *http.Request) {}

type handler2 struct {}

func (h *handler2) HandleA(http.ResponsWriter, *http.Request) {}

func (h *handler2) HandleB(http.ResponsWriter, *http.Request) {}

func WantsHandler( handler func(http.ResponsWriter, *http.Request) {}

func main() {
  WantsHandler(handler1) // works
  h2 := handler2{}
  WantsHandler(h2.HandleA) // also works
  WantsHandler(h2.HandleB) // this too
}

Another benefit: The user is not forced to include a package, if all she needs is the reference to the interface name.

Agreed, it looks a bit ugly... ;-)

metakeule commented Aug 30, 2017

Although it goes against Go conventions, I would question, if an interface is needed in the first place, if it contains just a single method.

Simply accepting a callback works for both cases, is less code to write and more flexible:

func handler1(http.ResponsWriter, *http.Request) {}

type handler2 struct {}

func (h *handler2) HandleA(http.ResponsWriter, *http.Request) {}

func (h *handler2) HandleB(http.ResponsWriter, *http.Request) {}

func WantsHandler( handler func(http.ResponsWriter, *http.Request) {}

func main() {
  WantsHandler(handler1) // works
  h2 := handler2{}
  WantsHandler(h2.HandleA) // also works
  WantsHandler(h2.HandleB) // this too
}

Another benefit: The user is not forced to include a package, if all she needs is the reference to the interface name.

Agreed, it looks a bit ugly... ;-)

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Aug 30, 2017

Member

@metakeule that's a good point. Method values and type aliases† overlap the use-case for single method interfaces somewhat, but what you lose with that approach is the ability to feature test other methods with type asserts (like being able to ask, is this io.Reader also an io.ReaderFrom?).

Being able to more easily go from the one to the other would be useful, but I'd rather have the more explicit and general "interface literal" syntax. But it's not really that great a pain to make the explicit wrappers with a func/struct now.

† so you can write

//Handler is [documented here]
type Handler = func(ResponseWriter, *Request)

instead of repeating the signature/docs everywhere or having to worry about casts from defined types

Member

jimmyfrasche commented Aug 30, 2017

@metakeule that's a good point. Method values and type aliases† overlap the use-case for single method interfaces somewhat, but what you lose with that approach is the ability to feature test other methods with type asserts (like being able to ask, is this io.Reader also an io.ReaderFrom?).

Being able to more easily go from the one to the other would be useful, but I'd rather have the more explicit and general "interface literal" syntax. But it's not really that great a pain to make the explicit wrappers with a func/struct now.

† so you can write

//Handler is [documented here]
type Handler = func(ResponseWriter, *Request)

instead of repeating the signature/docs everywhere or having to worry about casts from defined types

@metakeule

This comment has been minimized.

Show comment
Hide comment
@metakeule

metakeule Aug 30, 2017

@jimmyfrasche

Regarding the "feature" to ask for other interface implementations (aka "is this io.Reader also an io.ReaderFrom"):

I think the problem this is supposed to solve is "optional features" of the receiving function. It is used in the standard library (e.g. http.ResponseWriter -> http.Flusher etc) and I also used it.

But for some time now I consider this a misfeature for the following reasons:

  1. It is a hidden feature. While the function signature says it expects an io.Writer it is really expecting an io.Writer and/or an io.WriteCloser. That behavior is only visible within the source code and maybe the documentation. However it the author decides to add some optional behavior in an update, it may break the user without a compiler error (since the signature does not change) and creates errors that are hard to debug (e.g. io.WriteCloser being closed too early). Also the user struct implementing a new interface might break suddenly (the authors update being done long before without issues) and would make new type variations neccessary in order to prevent triggering the unwanted option.

  2. It makes it hard to wrap the interface. E.g. it looks as it would be easy to wrap an http.ResponseWriter with another type that implements the same interface. Until you notice that the http server is checking for other interfaces as well (like http.Flusher etc). Now you have to wrap each of the other interfaces (that are not easily visible unless you read the full source code).

  3. There are better ways to offer optional features that don't have the disadvantages: Allow option functions to be passed to the constructor that set the appropriate not exported options in the struct. Having them as variadic argument allows for easy expansion (simply add a new top level option function overwriting a compatible defaults) without breaking anything and with clear visibility. It also decouples the options on the user side (makes the user code more robust for refactoring / smaller surface of coupling between user and provider).

metakeule commented Aug 30, 2017

@jimmyfrasche

Regarding the "feature" to ask for other interface implementations (aka "is this io.Reader also an io.ReaderFrom"):

I think the problem this is supposed to solve is "optional features" of the receiving function. It is used in the standard library (e.g. http.ResponseWriter -> http.Flusher etc) and I also used it.

But for some time now I consider this a misfeature for the following reasons:

  1. It is a hidden feature. While the function signature says it expects an io.Writer it is really expecting an io.Writer and/or an io.WriteCloser. That behavior is only visible within the source code and maybe the documentation. However it the author decides to add some optional behavior in an update, it may break the user without a compiler error (since the signature does not change) and creates errors that are hard to debug (e.g. io.WriteCloser being closed too early). Also the user struct implementing a new interface might break suddenly (the authors update being done long before without issues) and would make new type variations neccessary in order to prevent triggering the unwanted option.

  2. It makes it hard to wrap the interface. E.g. it looks as it would be easy to wrap an http.ResponseWriter with another type that implements the same interface. Until you notice that the http server is checking for other interfaces as well (like http.Flusher etc). Now you have to wrap each of the other interfaces (that are not easily visible unless you read the full source code).

  3. There are better ways to offer optional features that don't have the disadvantages: Allow option functions to be passed to the constructor that set the appropriate not exported options in the struct. Having them as variadic argument allows for easy expansion (simply add a new top level option function overwriting a compatible defaults) without breaking anything and with clear visibility. It also decouples the options on the user side (makes the user code more robust for refactoring / smaller surface of coupling between user and provider).

@rogpeppe

This comment has been minimized.

Show comment
Hide comment
@rogpeppe

rogpeppe Aug 30, 2017

Contributor

I'm not keen on this proposal. I think it will be confusing.

 var f = func([]byte) (int, error) {return 0, io.EOF}
 var r io.Reader = f

What happens when we reflect on the value of r? What should reflect.ValueOf(r).NumMethod() return?
I see two possibilities:

  • it could return 0, because the underlying value has no methods.
  • it could return 1, because any value satisfying io.Reader must have at least one method.

Both cases would be unexpected IMHO, because we expect the value inside an interface to be the value we started with. It would be unexpected to inspect the methods on an io.Reader value and find that Read isn't among them; conversely it would be unexpected for a anonymous function value to have a method (what would the method name be?).

Another possibility might be to actually change the underlying type when converting to a one-method interface. Then reflect.TypeOf(f) != reflect.TypeOf(io.Reader(f)) (the latter type would have a Read method), but that seems pretty unexpected too, because interface{}(io.Reader(r)) is currently the same as interface{}(r), and this would make it different, confusingly so in my view.

@Sajmani's suggestion of io.Reader{Read: f} (io.Reader{f} for short?) seems like it would work better to me - at least there's a clear constructor there. But on balance, I think that it's probably not worth it.

Contributor

rogpeppe commented Aug 30, 2017

I'm not keen on this proposal. I think it will be confusing.

 var f = func([]byte) (int, error) {return 0, io.EOF}
 var r io.Reader = f

What happens when we reflect on the value of r? What should reflect.ValueOf(r).NumMethod() return?
I see two possibilities:

  • it could return 0, because the underlying value has no methods.
  • it could return 1, because any value satisfying io.Reader must have at least one method.

Both cases would be unexpected IMHO, because we expect the value inside an interface to be the value we started with. It would be unexpected to inspect the methods on an io.Reader value and find that Read isn't among them; conversely it would be unexpected for a anonymous function value to have a method (what would the method name be?).

Another possibility might be to actually change the underlying type when converting to a one-method interface. Then reflect.TypeOf(f) != reflect.TypeOf(io.Reader(f)) (the latter type would have a Read method), but that seems pretty unexpected too, because interface{}(io.Reader(r)) is currently the same as interface{}(r), and this would make it different, confusingly so in my view.

@Sajmani's suggestion of io.Reader{Read: f} (io.Reader{f} for short?) seems like it would work better to me - at least there's a clear constructor there. But on balance, I think that it's probably not worth it.

@Sajmani

This comment has been minimized.

Show comment
Hide comment
@Sajmani

Sajmani Aug 31, 2017

Contributor
Contributor

Sajmani commented Aug 31, 2017

@rogpeppe

This comment has been minimized.

Show comment
Hide comment
@rogpeppe

rogpeppe Aug 31, 2017

Contributor

I think if we were to allow function values to satisfy interfaces, I'd
prefer an explicit conversion: io.Reader(f).

I'm not sure that this is much better. Explicit conversion to other interface
types is still possible, and has quite different semantics.

f := func() {}
x := interface{}(f)
y := interface{F()}(f)
if reflect.TypeOf(x) != reflect.TypeOf(y) {
    panic("unexpected")
}

I believe that as proposed, the above code would need to panic, but that
seems wrong to me.

I wouldn't mind interface{F()}{f} as a syntax (by analogy with SliceType{x} vs SliceType(x)),
especially as it could later be expanded to interface{F(); G()}{F: f, G: g} at some later
point to make it easier to implement multiple method interfaces too.

We'd still need to decide what the underlying type's Kind and Name etc would look like.

Contributor

rogpeppe commented Aug 31, 2017

I think if we were to allow function values to satisfy interfaces, I'd
prefer an explicit conversion: io.Reader(f).

I'm not sure that this is much better. Explicit conversion to other interface
types is still possible, and has quite different semantics.

f := func() {}
x := interface{}(f)
y := interface{F()}(f)
if reflect.TypeOf(x) != reflect.TypeOf(y) {
    panic("unexpected")
}

I believe that as proposed, the above code would need to panic, but that
seems wrong to me.

I wouldn't mind interface{F()}{f} as a syntax (by analogy with SliceType{x} vs SliceType(x)),
especially as it could later be expanded to interface{F(); G()}{F: f, G: g} at some later
point to make it easier to implement multiple method interfaces too.

We'd still need to decide what the underlying type's Kind and Name etc would look like.

@neild

This comment has been minimized.

Show comment
Hide comment
@neild

neild Sep 13, 2017

Contributor

I believe I have an interesting use case for interface literals as proposed by @ccbrown and @Sajmani .

I've been considering an ioctx package which would provide equivalent interfaces to io.Reader et al., only with an added Context parameter. This package would also provide some simple wrappers for adapting between methods which take or do not take a Context.

package ctxio

type Reader interface {
  Read(ctx context.Context, p []byte) (n int, err error)
}

// ReaderWithContext curries a ctxio.Reader with a Context, producing an io.Reader.
func ReaderWithContext(ctx context.Context, r Reader) io.Reader { return ctxReader{ctx, r} }

type ctxReader struct { ctx context.Context, r Reader }
func (r ctxReader) Read(p []byte) (n int, err error) { return r.Read(r.ctx, p) }

But there's a problem with the ReaderWithContext function: Functions like io.Copy use type assertion to probe for methods like WriteTo. ReaderWithContext strips these methods from the returned Reader.

ReaderWithContext could use type assertions to detect the methods on the passed in r and return an appropriate wrapper type, but there's an obvious combinatorial explosion here--we'll need to define four different return types to support Reader, ReaderAt, and WriterTo...and what if we want to preserve the writer functions as well? This is a problem.

What if we could instead write this?

type io interface {
  Read(p []byte) (n int, err error)
  ReadAt(p []byte, off int64) (n int, err error)
  WriteTo(w io.Writer) (n int64, err error)
}

func ReaderWithContext(ctx context.Context, r Reader) io.Reader {
  // ctxReader has Read and WriteTo methods.
  ctxr := ctxReader{ctx, r}

  var writeToFn func(w io.Writer) (n int64, err error)
  if rr, ok := r.(io.WriterTo); ok {
    writeToFn = ctxr.WriteTo
  }

  wrap := io{
    Read: ctxr.Read,
    WriteTo: writeToFn,
  }
  return wrap
}

If r has a WriteTo method, we return a value of type io.Reader that may be converted with a type assertion into an io.WriterTo.

If r does not have a WriteTo method, then things get tricky. We construct a value of type io, which does have a WriteTo; attempting to call WriteTo on this value will panic, however, since we initialized it with a nil function pointer. Converting this value to an io.Reader on return produces a value which cannot be converted into an io.WriterTo again.

This handling of nil methods is quite subtle, enough so that I'm extremely hesitant to suggest that it's something that we actually should do even if we do add interface literals. On the other hand, I haven't been able to come up with any good solutions to the problem of wrapping types which may or may not contain certain methods that aren't equally subtle.

Contributor

neild commented Sep 13, 2017

I believe I have an interesting use case for interface literals as proposed by @ccbrown and @Sajmani .

I've been considering an ioctx package which would provide equivalent interfaces to io.Reader et al., only with an added Context parameter. This package would also provide some simple wrappers for adapting between methods which take or do not take a Context.

package ctxio

type Reader interface {
  Read(ctx context.Context, p []byte) (n int, err error)
}

// ReaderWithContext curries a ctxio.Reader with a Context, producing an io.Reader.
func ReaderWithContext(ctx context.Context, r Reader) io.Reader { return ctxReader{ctx, r} }

type ctxReader struct { ctx context.Context, r Reader }
func (r ctxReader) Read(p []byte) (n int, err error) { return r.Read(r.ctx, p) }

But there's a problem with the ReaderWithContext function: Functions like io.Copy use type assertion to probe for methods like WriteTo. ReaderWithContext strips these methods from the returned Reader.

ReaderWithContext could use type assertions to detect the methods on the passed in r and return an appropriate wrapper type, but there's an obvious combinatorial explosion here--we'll need to define four different return types to support Reader, ReaderAt, and WriterTo...and what if we want to preserve the writer functions as well? This is a problem.

What if we could instead write this?

type io interface {
  Read(p []byte) (n int, err error)
  ReadAt(p []byte, off int64) (n int, err error)
  WriteTo(w io.Writer) (n int64, err error)
}

func ReaderWithContext(ctx context.Context, r Reader) io.Reader {
  // ctxReader has Read and WriteTo methods.
  ctxr := ctxReader{ctx, r}

  var writeToFn func(w io.Writer) (n int64, err error)
  if rr, ok := r.(io.WriterTo); ok {
    writeToFn = ctxr.WriteTo
  }

  wrap := io{
    Read: ctxr.Read,
    WriteTo: writeToFn,
  }
  return wrap
}

If r has a WriteTo method, we return a value of type io.Reader that may be converted with a type assertion into an io.WriterTo.

If r does not have a WriteTo method, then things get tricky. We construct a value of type io, which does have a WriteTo; attempting to call WriteTo on this value will panic, however, since we initialized it with a nil function pointer. Converting this value to an io.Reader on return produces a value which cannot be converted into an io.WriterTo again.

This handling of nil methods is quite subtle, enough so that I'm extremely hesitant to suggest that it's something that we actually should do even if we do add interface literals. On the other hand, I haven't been able to come up with any good solutions to the problem of wrapping types which may or may not contain certain methods that aren't equally subtle.

@dsnet

This comment has been minimized.

Show comment
Hide comment
@dsnet

dsnet Sep 13, 2017

Member

What if you were required to declare all methods of an interface literal? Sure someone could do:

wrap := io{
    Read: ctxr.Read,
    ReadAt: nil,
    WriteTo: writeToFn,
}

But the jokes on them... they explicitly trying to shoot themselves in the foot.

Member

dsnet commented Sep 13, 2017

What if you were required to declare all methods of an interface literal? Sure someone could do:

wrap := io{
    Read: ctxr.Read,
    ReadAt: nil,
    WriteTo: writeToFn,
}

But the jokes on them... they explicitly trying to shoot themselves in the foot.

@rogpeppe

This comment has been minimized.

Show comment
Hide comment
@rogpeppe

rogpeppe Sep 14, 2017

Contributor

The way I was looking at it was that:

x = interfaceType {m1:f1, m2:f2, ...}

would be syntax sugar for:

type _scratch struct {
    f1_ func typeof(interfaceType.m1)
    ...
}
func (s _scratch) f1(args) ret {
    return s.f1_(arg)
}
etc
x = interfaceType (_scratch{f1_: f1, ...})

I think it would be very odd if a non-nil something of a given interface type turned out not to actually implement that type.

That is, for all non-nil variables v of some interface type I, we should be able to do (interface{}(v)).(I) without panicking. Invariants like this are important for automatic code transformation and vetting tools.

That said, I do appreciate the issue encountered by @neild. I suspect the answer to that is more of a cultural one - define your interfaces in such a way that even if the interface is implemented, the it might not actually work. For example WriteTo could be defined such that it could return a "not implemented" error to cause io.Copy to fall back to the naive Read-based behaviour.

Then we can define an interface type with all the known methods on it and implement suitable behaviour when there's no function available.

Embedding unknown methods is another story.

Contributor

rogpeppe commented Sep 14, 2017

The way I was looking at it was that:

x = interfaceType {m1:f1, m2:f2, ...}

would be syntax sugar for:

type _scratch struct {
    f1_ func typeof(interfaceType.m1)
    ...
}
func (s _scratch) f1(args) ret {
    return s.f1_(arg)
}
etc
x = interfaceType (_scratch{f1_: f1, ...})

I think it would be very odd if a non-nil something of a given interface type turned out not to actually implement that type.

That is, for all non-nil variables v of some interface type I, we should be able to do (interface{}(v)).(I) without panicking. Invariants like this are important for automatic code transformation and vetting tools.

That said, I do appreciate the issue encountered by @neild. I suspect the answer to that is more of a cultural one - define your interfaces in such a way that even if the interface is implemented, the it might not actually work. For example WriteTo could be defined such that it could return a "not implemented" error to cause io.Copy to fall back to the naive Read-based behaviour.

Then we can define an interface type with all the known methods on it and implement suitable behaviour when there's no function available.

Embedding unknown methods is another story.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Sep 14, 2017

Member

@rogpeppe I think the known vs unknown methods wrapping is the same story—or at least similar enough that the same solution would work.

In both cases, you want to create a value whose method set is determined by the dynamic type of another value.

In #4146 (comment) this was discussed (starting at linked comment) in terms of the classic problem of hiding methods such as Temporary on a wrapped error.

The eventual solution was fairly general. A function Wrap(value, with interface{}) interface{} that conceptually returns

struct {
  underlyingTypeOfValue
  underlyingTypeOfWith
}

except that, unlike ordinary embedding, ambiguous methods always dispatch to underlyingTypeOfWith.

With that you could run Wrap(arbitraryErr, fmt.Errorf("mypkg: %s", arbitraryErr)).(error) and not lose any of the methods on arbitraryErr while replacing the Error method to provide the appropriate context.

In @neild's case this could be used like

func ReaderWithContext(ctx context.Context, r Reader) io.Reader {
  // ctxReader just has Read method.
  var ctxr interface{} = ctxReader{ctx, r}

  if rr, ok := r.(io.WriterTo); ok {
    //ctxWriteTo just has WriteTo method.
    withWriteTo := ctxWriteTo{ctx, rr}
    ctxr = Wrap(ctxr, withWriteTo)
  }
  //and so on for all the other various possibilities,
  //only adding methods to ctxr when necessary

  return ctxr.(io.Reader)
}

If r has a WriteTo method, so does the returned value. If it does not, neither does the returned value.

The implicit use of reflect is unsatisfying but it manages to sidestep the combinatorial explosion of types by generating only the appropriate combination at runtime.

Unfortunately Wrap is blocked on #16522

Member

jimmyfrasche commented Sep 14, 2017

@rogpeppe I think the known vs unknown methods wrapping is the same story—or at least similar enough that the same solution would work.

In both cases, you want to create a value whose method set is determined by the dynamic type of another value.

In #4146 (comment) this was discussed (starting at linked comment) in terms of the classic problem of hiding methods such as Temporary on a wrapped error.

The eventual solution was fairly general. A function Wrap(value, with interface{}) interface{} that conceptually returns

struct {
  underlyingTypeOfValue
  underlyingTypeOfWith
}

except that, unlike ordinary embedding, ambiguous methods always dispatch to underlyingTypeOfWith.

With that you could run Wrap(arbitraryErr, fmt.Errorf("mypkg: %s", arbitraryErr)).(error) and not lose any of the methods on arbitraryErr while replacing the Error method to provide the appropriate context.

In @neild's case this could be used like

func ReaderWithContext(ctx context.Context, r Reader) io.Reader {
  // ctxReader just has Read method.
  var ctxr interface{} = ctxReader{ctx, r}

  if rr, ok := r.(io.WriterTo); ok {
    //ctxWriteTo just has WriteTo method.
    withWriteTo := ctxWriteTo{ctx, rr}
    ctxr = Wrap(ctxr, withWriteTo)
  }
  //and so on for all the other various possibilities,
  //only adding methods to ctxr when necessary

  return ctxr.(io.Reader)
}

If r has a WriteTo method, so does the returned value. If it does not, neither does the returned value.

The implicit use of reflect is unsatisfying but it manages to sidestep the combinatorial explosion of types by generating only the appropriate combination at runtime.

Unfortunately Wrap is blocked on #16522

@rogpeppe

This comment has been minimized.

Show comment
Hide comment
@rogpeppe

rogpeppe Sep 14, 2017

Contributor

@rogpeppe I think the known vs unknown methods wrapping is the same story—or at least similar enough that the same solution would work.

It's true that the suggested solution for promoting unknown methods would work for known methods too, but I don't think that this proposal could be used to promote unknown methods (hence "a different story" IMHO). FWIW I suspect that the proposed solutions for promoting unknown methods will turn out to be more heavyweight than desired for the routine cases where they might be used.

Contributor

rogpeppe commented Sep 14, 2017

@rogpeppe I think the known vs unknown methods wrapping is the same story—or at least similar enough that the same solution would work.

It's true that the suggested solution for promoting unknown methods would work for known methods too, but I don't think that this proposal could be used to promote unknown methods (hence "a different story" IMHO). FWIW I suspect that the proposed solutions for promoting unknown methods will turn out to be more heavyweight than desired for the routine cases where they might be used.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Sep 17, 2017

Member

@rogpeppe

FWIW I suspect that the proposed solutions for promoting unknown methods will turn out to be more heavyweight than desired for the routine cases where they might be used.

I don't think this is necessarily true, though it certainly would not be free.

One way to implement it would be to use the same essential construct as you pointed out interface literals would likely use in #21670 (comment)

Say you had an interface ABer with methods A and B and two values a with only the method A and b with only the method B.

With an interface literal you could construct a "combined" value, v:

v := ABer{
  A: a.A,
  B: b.B,
}

Calling v.A would indirect though the interface and the function pointer in the _scratch struct.

A means of promoting unknown methods could work mostly the same. The only differences are it would need to compute and then create the method set and the _scratch struct at runtime. That only needs to happen when the value is created, though.

Going back to the ABer example, v := Wrap(a, b).(ABer) would create operationally the same value as constructed above. How it creates v would be slower, but using the value would have the same double-indirect performance characteristics as when it was created by the interface literal.

For Wrap to be implemented this way, it would need some inside information, so it would probably have to be implemented in reflect (and would require reflect to be able to construct method sets at runtime per #16522).

Member

jimmyfrasche commented Sep 17, 2017

@rogpeppe

FWIW I suspect that the proposed solutions for promoting unknown methods will turn out to be more heavyweight than desired for the routine cases where they might be used.

I don't think this is necessarily true, though it certainly would not be free.

One way to implement it would be to use the same essential construct as you pointed out interface literals would likely use in #21670 (comment)

Say you had an interface ABer with methods A and B and two values a with only the method A and b with only the method B.

With an interface literal you could construct a "combined" value, v:

v := ABer{
  A: a.A,
  B: b.B,
}

Calling v.A would indirect though the interface and the function pointer in the _scratch struct.

A means of promoting unknown methods could work mostly the same. The only differences are it would need to compute and then create the method set and the _scratch struct at runtime. That only needs to happen when the value is created, though.

Going back to the ABer example, v := Wrap(a, b).(ABer) would create operationally the same value as constructed above. How it creates v would be slower, but using the value would have the same double-indirect performance characteristics as when it was created by the interface literal.

For Wrap to be implemented this way, it would need some inside information, so it would probably have to be implemented in reflect (and would require reflect to be able to construct method sets at runtime per #16522).

@pciet

This comment has been minimized.

Show comment
Hide comment
@pciet

pciet Feb 5, 2018

Contributor

Perhaps http.Handler would be better described as a function instead of an interface?

func FileServer(root FileSystem) Handler returns a *fileHandler that has an interface field. A closure could be returned instead: https://play.golang.org/p/1TErgasMfc6

Contributor

pciet commented Feb 5, 2018

Perhaps http.Handler would be better described as a function instead of an interface?

func FileServer(root FileSystem) Handler returns a *fileHandler that has an interface field. A closure could be returned instead: https://play.golang.org/p/1TErgasMfc6

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

ianlancetaylor Mar 20, 2018

Contributor

The original proposal is for essentially the reverse of a method value. Perhaps it should be called a value method. The idea is to permit a conversion from a function value to an interface type with a single method, where the signature of the method is the same as the signature of the function. The conversion would involve creating an unnamed type with a single method, where the method would be implemented by calling the original function value with the arguments. This conversion could be implicit, or could require an explicit conversion.

Since this is the reverse of a method value, it is worth considering the reverse of a method expression (an expression method). That would start with a function with at least one argument. It would convert to a value whose type is the type of the first argument, with an attached method whose type is the remaining arguments of the function. This could only be used in a conversion to a single method interface, as above, where the method has the appropriate type.

If anybody wants to seriously push any of the other ideas discussed above, please open a separate proposal. They might be good ideas but they are more complex than this proposal and should be considered separately.

Contributor

ianlancetaylor commented Mar 20, 2018

The original proposal is for essentially the reverse of a method value. Perhaps it should be called a value method. The idea is to permit a conversion from a function value to an interface type with a single method, where the signature of the method is the same as the signature of the function. The conversion would involve creating an unnamed type with a single method, where the method would be implemented by calling the original function value with the arguments. This conversion could be implicit, or could require an explicit conversion.

Since this is the reverse of a method value, it is worth considering the reverse of a method expression (an expression method). That would start with a function with at least one argument. It would convert to a value whose type is the type of the first argument, with an attached method whose type is the remaining arguments of the function. This could only be used in a conversion to a single method interface, as above, where the method has the appropriate type.

If anybody wants to seriously push any of the other ideas discussed above, please open a separate proposal. They might be good ideas but they are more complex than this proposal and should be considered separately.

@bcmills

This comment has been minimized.

Show comment
Hide comment
@bcmills

bcmills Mar 20, 2018

Member

Since this is the reverse of a method value, it is worth considering the reverse of a method expression (an expression method). That would start with a function with at least one argument. It would convert to a value whose type is the type of the first argument, with an attached method whose type is the remaining arguments of the function.

A method expression moves the receiver parameter to the first argument position.
An “expression method” would move the first argument to the receiver position.

It's related to #21401, but not attached to the receiver type.
It's related to #23185, but presumably requires an explicit type conversion rather than changing assignability of existing types

The syntax for a method value is mv := v.Method, and the syntax for a method expression is me := T.Method.

Just sketching some composite literals, here's one with good symmetry:

	v := T{Method: mv}
	type T = {Method: me}

In the first declaration v is the function mv viewed as an interface of type T.

In the second declaration, T is an alias for “the type of the first argument to me augmented with Method”. Values assignable to the first argument to me can be converted to T.

In the more general form,

	type T = {
		Method1: me1,
		Method2: me2,
	}

T is an alias for some type that is assignable to the first arguments of its methods, augmented by those methods. If the first-argument types are interfaces, the underlying type of T is an interface containing a union of their methods; if they are concrete types, the underlying type of T is a type assignable to all of those concrete types. (If any of them are defined types rather than type literals or interfaces... I'd have to think about that some more.)

Member

bcmills commented Mar 20, 2018

Since this is the reverse of a method value, it is worth considering the reverse of a method expression (an expression method). That would start with a function with at least one argument. It would convert to a value whose type is the type of the first argument, with an attached method whose type is the remaining arguments of the function.

A method expression moves the receiver parameter to the first argument position.
An “expression method” would move the first argument to the receiver position.

It's related to #21401, but not attached to the receiver type.
It's related to #23185, but presumably requires an explicit type conversion rather than changing assignability of existing types

The syntax for a method value is mv := v.Method, and the syntax for a method expression is me := T.Method.

Just sketching some composite literals, here's one with good symmetry:

	v := T{Method: mv}
	type T = {Method: me}

In the first declaration v is the function mv viewed as an interface of type T.

In the second declaration, T is an alias for “the type of the first argument to me augmented with Method”. Values assignable to the first argument to me can be converted to T.

In the more general form,

	type T = {
		Method1: me1,
		Method2: me2,
	}

T is an alias for some type that is assignable to the first arguments of its methods, augmented by those methods. If the first-argument types are interfaces, the underlying type of T is an interface containing a union of their methods; if they are concrete types, the underlying type of T is a type assignable to all of those concrete types. (If any of them are defined types rather than type literals or interfaces... I'd have to think about that some more.)

@bcmills

This comment has been minimized.

Show comment
Hide comment
@bcmills

bcmills Mar 20, 2018

Member

There is an interesting asymmetry between method expressions and “expression methods”.

If we add a method to an existing value based on an arbitrary expression, the effective type definition for that value is computed at run-time and depends on a value. That's something we support today via reflect, but not in the language proper.

On the one hand, adding “expression methods” would significantly complicate the type system. (For example, can these new method-augmented types themselves be used as function parameter or return types?) On the other hand, it would close a gap between reflect and the compile-time language.

Member

bcmills commented Mar 20, 2018

There is an interesting asymmetry between method expressions and “expression methods”.

If we add a method to an existing value based on an arbitrary expression, the effective type definition for that value is computed at run-time and depends on a value. That's something we support today via reflect, but not in the language proper.

On the one hand, adding “expression methods” would significantly complicate the type system. (For example, can these new method-augmented types themselves be used as function parameter or return types?) On the other hand, it would close a gap between reflect and the compile-time language.

@bcmills

This comment has been minimized.

Show comment
Hide comment
@bcmills

bcmills Mar 20, 2018

Member

(In other words: I think “method expressions” would be plausible but they would merit a whole separate proposal.)

Member

bcmills commented Mar 20, 2018

(In other words: I think “method expressions” would be plausible but they would merit a whole separate proposal.)

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