Let struct function field behave like a method

1,530 views
Skip to first unread message

meta keule

unread,
May 24, 2013, 7:42:02 PM5/24/13
to golan...@googlegroups.com
Hi,

with the new MakeFunc, it is possible to make generic functions
and "attach" them to a struct, e.g.


     type S struct { F func(int,int) (int, int) }
     func Assign(ptr interface{}, field string, generic func(in []reflect.Value) []reflect.Value){
          fv :=  reflect.ValueOf(ptr).Elem()
          p := fv.FieldByName(field)
          v := reflect.MakeFunc(p.Type(), generic)
          p.Set(v)
     }

     func Swap(in []reflect.Value) []reflect.Value { return []reflect.Value{in[1],in[0]} }

     func main() {
          s := &S{}
          Assign(s, "F", Swap)
          fmt.Println(s.F(2,1))
     }

From the outside it looks as if s.F() was a method call.
However, it does not fullfill an interface, e.g.


     type I interface {
          F(int, int) (int, int)
     }


     func main() {
          s := &S{}
          Assign(s, "F", Swap)
          fmt.Println(s.F(2,1))
          var i I
          i = s  
     }

results in:
S.F is a field, not a method
cannot use s (type *S) as type I in assignment:
	*S does not implement I (missing F method)

Now the question to the language implementors:
--------------------------------------------------------------------

Would it require a major effort to allow fields of structs that are functions to
fullfill an interface that requires a method with the same name and signature?

------------------------------------------------------------------------------------------------------

That would be a very powerful combination. Even the struct itself could be passed
in via closure, e.g.

     func AddX(i interface{}) func (in []reflect.Value) []reflect.Value { 
          return func (in []reflect.Value) []reflect.Value { 
               v := in[0].Int() + reflect.ValueOf(i).Elem().FieldByName("X").Int()
               return []reflect.Value{reflect.ValueOf(v)} 
          }
     }

     type S1 struct { 
          Add func(int) (int64) 
          X int
     }

     func main() {
          s := &S1{X: 2}
          Assign(s, "Add", AddX(s))
          fmt.Println(s.Add(3))
     }


Regards

Kyle Lemons

unread,
May 24, 2013, 7:50:28 PM5/24/13
to meta keule, golang-nuts
That is an interesting question, and one I've pondered on and off.

I can think of two implementation approaches:

(1)
Give every struct value its own method table.  When there is a func field, it is added to the method table but the field points to the empty space in the table.  Assigning to the field updates the method table.

This is a radical departure from the way things work now, where structs are only as big as the values inside them.  The changes required would be widespread and invasive.

(2)
Generate a wrapper method with a mangled name that calls the function.  When checking a type for interface compliance and a function is missing, look for its mangled name as well.

This is also a pretty major departure, and it would slow down every negative interface check.  The changes would be reasonably focused, but still pretty hacky.


It's possible that there are more ways, but neither of these get me excited.  You can accomplish the moral equivalent of #2 by making your own wrapper.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

meta keule

unread,
May 24, 2013, 8:19:24 PM5/24/13
to golan...@googlegroups.com, meta keule
Why not change the internal representation of the names of the struct fields and make a real method for every field that has
a function as value (with the same signature) and that calls the function in the struct field.

E.g.

a struct field would now internally be presented with an @ at the end.
So the parser finds

S.Name or S.Name = ....

and replaces it with 

S.Name@ and S.Name@ = ....

(since @ is not allowed in fieldnames there is no conflict).

Method calls on the other hand stay unaffected.

This way we should only have a little more work on the build of the libraries but no runtime delay since
the 1:1 method to field function call could be optimized away (I guess).

Kyle Lemons

unread,
May 24, 2013, 9:24:32 PM5/24/13
to meta keule, golang-nuts
That's just a variation on suggestion (2), except you're mangling the field names and making the symbol table and the debug experience more "weird."  You still need to generate a wrapper function for all func fields.  Again, I don't see what the benefit is over just writing the wrappers yourself; it's nice and explicit and everyone can tell what's going on.

Ian Lance Taylor

unread,
May 25, 2013, 1:03:37 AM5/25/13
to meta keule, golan...@googlegroups.com
On Fri, May 24, 2013 at 4:42 PM, meta keule <marcre...@googlemail.com> wrote:
>
It's interesting, but, it seems to me that you could write

type S struct { F1 func(int,int) (int, int) }
func (s S) F(a, b int) (int, int) { return s.F1(a, b) }

and then proceed just as you outline. That is, your proposal saves
writing one boilerplate method. Boilerplate is not good, but on the
other hand this doesn't seem like a technique that will be widely
used.

Ian

meta keule

unread,
May 25, 2013, 4:28:16 AM5/25/13
to golan...@googlegroups.com, meta keule
Clearly is is a matter of comfort.
If you want to write a library that predefines some generic functions and consumes them via interface,
it is a matter of requiring every instance to have the double effort for no matter, but a weird limitation of the language.

meta keule

unread,
May 25, 2013, 4:36:58 AM5/25/13
to golan...@googlegroups.com, meta keule
Well, it is really just for struct fields and it could be well documented. On the other hand, the current behaviour is much less
obvious, since it is inconsistent (I can call it like a method, but it is none) and it is weird, if someone sees method usage in
code, try to wrap it in an interface and... bam! Also it requires deeply understanding of the language in order to understand, why
it does not work. To expect that understanding from a person who is debugging is more appropriate, since
a programming languag is an interface to a programmer and the deeper you dig, the more you need to know.

The difference to suggestion (2) is that this mangling has to be done only once for each struct and at compile time and
not again if you import a package.

Carlos Castillo

unread,
May 25, 2013, 9:52:33 PM5/25/13
to golan...@googlegroups.com, meta keule
I find your definition of comfort somewhat suspicious.

In your orignal post you have a monstrosity of a function (AddX) which uses a heavy amount of reflection to create a function to process an arbitrary struct, pull out it's first field (assuming that what you want is there), assume it's an integer (runtime panic otherwise), pull out the second argument (an integer) add them up (result is now int64 because reflect.Value.Int() always returns int64), and return the result as a reflect.Value. In the playground code, you then convert the generated function into an appropriate type using reflect.MakeFunc to attach it to the object. Only now does your feature come into play, saving the need to type a one line method definition.

Your solution is slower, more brittle, and less type-safe then http://play.golang.org/p/9obIw9R1B2, where I only need additionally a single line method to add a new field/method, and a simple anonymous function to implement it.

I see little benefit to the reflective code in AddX, other then it can be applied to any struct with the first field being an int type instead of having one line of "boilerplate" that allows the field function to be much simpler, and can use any field of the struct (by name, so it can be moved in the struct), with the correct and exact type.

Also, your example seems to make the dangerous assumption that a field of type func(x int) int64 is actually supposed to be assigned a func(s S1, x int) int64 (or a func(s *S1, x int) int64.) I know that this is to pass the receiver as part of the method call, but many people might not realize this, and get new (possibly quite obscure) errors. Resolving this 3 to 1 mapping (two method signatures, and the straight function one), seems like a much bigger problem and far more programmer headaches than the initial problem that val.Field() is not a method call despite looking like one, and is spelled out in the package documentation.

meta keule

unread,
May 27, 2013, 6:08:52 AM5/27/13
to golan...@googlegroups.com, meta keule
I posted a somewhat lengthy reply which somehow got lost when posting (maybe its just Chromium).

Instead now the short version:

Imagine, the different parts of my code being implemented by different persons with different knowledge.
At the end of the chain are the library users, that get WTFs if they can't make use of the libraries
of others in their own interfaces. (50 libraries x 1000 users....)

Also if the library programmers didn't care about the constructed struct instances being us in interfaces,
if they latter get a feature request to enable them, it will break everybody's code.

For the performance:

It is reasonable to prefer rapid development to performance in some circumstances (e.g. prototyping).
Especially, if you can replace the slowest parts later on, because you use interfaces.

meta keule

unread,
May 27, 2013, 8:14:23 AM5/27/13
to golang-nuts
It could help in lots of cases where "generics" are requested.
Since they are requested very often on this list, I suppose
it would be used.

From the perspective of a library user it would make no difference,
but
it would allow library writers to write powerful generic functions and
reuse
them everywhere.

It would come at a performance loss, but with usage of interfaces the
worst
points could be optimized and replaced.

On 25 Mai, 07:03, Ian Lance Taylor <i...@golang.org> wrote:
Reply all
Reply to author
Forward
0 new messages