go2go (generics) and function binding

351 views
Skip to first unread message

Sebastien Binet

unread,
May 30, 2020, 5:33:37 AM5/30/20
to golang-nuts
hi there,

I was trying out the generics proposal https://github.com/golang/go/issues/15292, via the "Go2 playground":
https://ccbrown.github.io/wasm-go-playground/experimental/generics

the thing I am trying to solve is, basically, a generic way to:
- pass a function with any number of parameters and a single return value
- "bind" that function to a slice of pointers to values (that will point to, e.g., values extracted from a db)
- create a closure that returns the value from evaluating the function with the bound paramaters.

basically, I am defining this interface:
type Formula interface {
Vars() []string
Bind(ptrs []interface{}) error
Func() interface{}
}

that people would be able (in Go-1) to implement like so:

type FormF64F32ToI64 struct {
vars []string
arg0 *float64
arg1 *float32
fct func(x1 float64, x2 float32) int64
}

func (f *FormF64F32ToI64) Vars() []string { return f.vars }
func (f *FormF64F32ToI64) Bind(ptrs []interface{}) error {
// check number of args elided.
f.arg0 = ptrs[0].(*float64)
f.arg1 = ptrs[1].(*float32)
return nil
}

func (f *FormF64F32ToI64) Func() interface{} {
return func() int64 {
return f.fct(*f.arg0, *f.arg1)
}
}

so some "framework/library" code can work in terms of this Formula interface and let clients register formulae (with a set of column names to read from the database) in a relatively type-safe way, and quite performant one too (you pay the price of the type-assert once when you retrieve the function closure):

usr, err := db.Formula(&FormF64F32ToI64{
names: []string{"col1", "col42"},
fct: func(x1 float64, x2 float32) int64 { return int64(x1) + int64(x2) },
})
check(err)

fct := usr.Func().(func () int64)

err = db.IterRows(func(tx Tx)error {
fmt.Printf("row[%d]: %d\n", tx.ID, fct())
})
check(err)

implementing the Formula interface from a given func is a pretty mechanical job.
right now, I am doing it with a little go/types-based command.

today, I tried to do the same with the generics proposal:


=====
package main

type Formula interface {
Names() []string
Bind(args []interface{}) error
Func() interface{}
}

type Form2 (type T1,T2,U) struct {
names []string
arg1 *T1
arg2 *T2
fct func(t1 T1, t2 T2) U
}

func (f *Form2(T1,T2,U)) Func() interface{} {
return func() U {
return f.fct(*f.arg1, *f.arg2)
}
}

func (f *Form2(T1,T2,U)) Names() []string { return f.names }
func (f *Form2(T1,T2,U)) Bind(args []interface{}) error {
if len(args) != 2 {
panic("invalid number of arguments")
}
f.arg1 = args[0].(*T1)
f.arg2 = args[1].(*T2)
return nil
}

var (
_ Formula = (*Form2(float64,float32,int64))(nil)
)

func main() {
arg1 := 42.0
arg2 := float32(42)
args := []interface{}{&arg1, &arg2}

form := &Form2(float64,float32,int64){
names: []string{"f64", "data"},
fct: func(x1 float64, x2 float32) int64 { return int64(x1) + int64(x2) },
}
err := form.Bind(args)
if err != nil { panic(err) }

fct := form.Func().(func() int64)
println("fct=",fct())
}
====

it's definitely an improvement: I "just" have to write the generics code for functions with a given arity (say, 0, 1, ... 5) w/o having to pre-generate all the functions I (or my users) may need for all combinations of (types x arity).

it's not completely satisfactory, though, as:
- it's not "as general" a solution as what one could come up with, say, std::function (from C++), where you "just" have to give a function pointer and it handles everything else for you (arity, in this case)
- there's probably the issue of "generic type argument" collision:
how to differentiate (in the current scheme) between 'func(x1 float64) (float64, int64)' and 'func(x1, x2 float64) int64'?
(this could perhaps be addressed w/ a different way of describing my Form2 generic type.)

perhaps there's a better way of doing this? (perhaps even w/ "just" Go1)

it'd be great to just have to write:
var f Formula = &Form(func(float64,float64)int64){
names: names,
fct: func(x1,x2 float64) float32 { return float32(x1+x2) },
}
and have everything instantiated and implemented correctly.

thoughts?

cheers,
-s


PS: here's the direct link to the Go+Generics playground of the code above:
https://ccbrown.github.io/wasm-go-playground/experimental/generics/#A4Qwxg1iDmCmAEBbEBLAdgKAwem/AGoQFzwAqAFigM7zXyXTkA2AnvLAB7CwBOKisNABcQTAHTwAIgHs0AciHsusMIrDSePFULSwqNDfCoiARiiYohLMVivd4AMQ2IArkxC1hvAGbgEAbwx4YPgAORABKgAKAEp4AG0AXWM+NGggkIAhdAATKJAeaBok9CEfP38AXzjeHg0M4IcXNDBYzzKeXzBYKoxK2xZ7Jx5EACZ4KLsEUgBGABpSUbmAVTiUl1V4QJD4NAi9BOShVPSdgugZ+AAqWYb4c/Gb0bvvTe9m1qFL2bn4IXHFnFln0sO8WhNvNdhmMoj9FisYnEmi02qVyt0qlsMABILRCFw8NDwMGtIFY7G42D4wnEsSvIRRK7eMTnebXZkPGI4/r9DAkiFQ5yjWHzeGrOLhSJtJIpdDQLbwPEEonMvaReD9flRSFXaHCuFLcXwbJoPLnYqJNGdCrVdiaQzbEIoSFMQT5QpUOIAQgAvPBxo6dsFQGgUK0AEToABuohQOV2LkQJl48GkkPOicEQio4a5O36Ow5hUufvN8QADIkxIzZnmQkXoONSx74jMqzXRnXgkqaaGmCCMDGeBM7gB9RzONweP2MvXapjSEBCABsABY5t4F0uAMxLUprxFRPtcrl8j5IVBoNqBbGs+BEP2r0Zics4h73v2bxdCXdRJ9c28PQ/Q4rS6HpKn8AAyVlfmgwpRn6bEcW8ZxgMgucvyXNcNy3H892EA8bx2NU9BIGVjjlfxw28Ndw1+cMciXEBw0qOYcULVQSBJKIOEuTCV3XeAOHGfjdzifdVwVHsiQkniZjiABqdo1x4zsNTY7FENqYCUJGMQTTND0AOdO1h19XZzAVEMwyiWo4kQ5DNgfYlnDEZFSWrbjxII1cAOAVIhCYK9qNUH06PpWIuX6IA==

Ian Lance Taylor

unread,
May 31, 2020, 11:55:40 PM5/31/20
to Sebastien Binet, golang-nuts
On Sat, May 30, 2020 at 2:32 AM Sebastien Binet <d...@sbinet.org> wrote:
>
> the thing I am trying to solve is, basically, a generic way to:
> - pass a function with any number of parameters and a single return value
> - "bind" that function to a slice of pointers to values (that will point to, e.g., values extracted from a db)
> - create a closure that returns the value from evaluating the function with the bound paramaters.

As you've discovered, the current generics design draft does not
support a variadic list of type parameters.

That's something that might perhaps be added later, if we can figure
out how to do it. There is some discussion of it starting at
https://github.com/golang/go/issues/15292#issuecomment-598775120 .

Ian

ffm...@web.de

unread,
Jun 2, 2020, 4:25:31 AM6/2/20
to golang-nuts
I didn't know you can already play around with early access releases of Go2 including generics. This is awsome! I promised myself to start working with Go once it has generics. Therefore, could someone tell me where to download the latest version of Go2 or tell me where the branch of it resides? This is a little off-topic, sorry. But I thought it is not worth creating a new topic just for this lilttle question.

Thank you.

Mandolyte

unread,
Jun 2, 2020, 6:47:32 AM6/2/20
to golang-nuts

Sebastien Binet

unread,
Jun 2, 2020, 6:47:54 AM6/2/20
to ffm...@web.de, golang-nuts
AFAIK, there's no release to speak of.
"just" a CL one can try out:

hth,
-s



‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
--
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.

Sebastien Binet

unread,
Jun 3, 2020, 7:44:25 AM6/3/20
to Ian Lance Taylor, golang-nuts
Ian,




‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
thanks.

Egon came up with some nice improvements upon my first stab at this.
His versions could probably handle the "multiple outputs" possible issue I was mentioning in passing.

- version 1
===
package main

type Eval(type T) interface {
Eval() T
}

type Binder interface {
Value(name string) interface{}
}

type Formula(type Result) interface {
Bind(source Binder) error
Eval(Result)
}

type Func2 (type A, B, R) struct {
Call func(A, B) R
A Eval(A)
B Eval(B)
}

func (fn Func2(A, B, R)) Eval() R {
return fn.Call(fn.A.Eval(), fn.B.Eval())
}

func Fn2(type A, B, R)(a, b string, db Binder, eval func(A, B) R) func() R {
fn := Func2(A, B, R){
Call: eval,
A: db.Value(a).(Eval(A)),
B: db.Value(b).(Eval(B)),
}
return fn.Eval
}

type Values map[string]interface{}

func (v Values) Value(name string) interface{} { return v[name]}

type ConstFloat64 float64
func (v ConstFloat64) Eval() float64 { return float64(v) }
type ConstFloat32 float32
func (v ConstFloat32) Eval() float32 { return float32(v) }

func main() {
rows := Values{
"alpha": ConstFloat64(123.123),
"beta": ConstFloat32(53.123),
}

fn := Fn2(float64, float32, int64)(
"alpha", "beta", rows,
func(a float64, b float32) int64 {
return int64(a) + int64(b)
},
)

println("fn=", fn())
}
===

- version 2:
===
package main

type Eval(type T) interface {
Eval() T
}

type Binder interface {
Value(name string) interface{}
}

type Formula(type Result) interface {
Bind(source Binder) error
Eval(Result)
}

type Func2 (type A, B, R) struct {
A, B string
Call func(A, B) R
}

func (fn Func2(A, B, R)) Bind(db Binder) Eval(R) {
a := db.Value(fn.A).(Eval(A))
b := db.Value(fn.B).(Eval(B))
return evalFunc(R)(func() R {
return fn.Call(a.Eval(), b.Eval())
})
}

type evalFunc (type R) func() R
func (fn evalFunc(R)) Eval() R { return fn() }

type Values map[string]interface{}

func (v Values) Value(name string) interface{} { return v[name]}

type ConstFloat64 float64
func (v ConstFloat64) Eval() float64 { return float64(v) }

type ConstFloat32 float32
func (v ConstFloat32) Eval() float32 { return float32(v) }

func main() {
rows := Values{
"alpha": ConstFloat64(123.123),
"beta": ConstFloat32(53.123),
}

form := Func2(float64, float32, int64){
A: "alpha",
B: "beta",
Call: func(a float64, b float32) int64 {
return int64(a) + int64(b)
},
}

fn := form.Bind(rows).Eval

println("fn=", fn())
}
===

-s

PS: here are the direct links:
- https://ccbrown.github.io/wasm-go-playground/experimental/generics/#A4Qwxg1iDmCmAEBbEBLAdgKAwFwJ7AQFEA3EAGwAo8D4AVASnnW1gCcAzcBAbwwEgS5Co1oYAvlmoIAQugAmbJmhYcu8XnwBq5AK6wKaEIgQBnbK3TRGzNpzCxuEiTnwIAYgHtWiHWRBVXeAAlWBNfbGtlWzVeeDj4WTQ5ChMPHVZ7BPk2RjZWLwx4+EFKELCyCPFJQLcdNDAAJngAmgBBABoEzqDGM1YdMGx1fgBhcjJ4djqwCg6ExiD+VuLSSlb6fmkVoWkN5yn65vY0eFr6htnO6W76RhLhYOG+VlhsdJPjgDoxskov1s+93onS+0kBq2EeywBzApzQFyk8Dm12C9AoIE6ACN4H1LJ05NjEgpWJ1YKtJtNLvNURT6g8gk9jvAAFwAXlO0wuyJuGj4PzIzPgZPI7X4fFagoJn20ZD06Ponwo93WwLF0klmOlun0mIVSohu1VfAkz1e70maHB5CqLhoMr0JiQIGAAG1cWhoABdGyqeyOaHTZrEeD20KMUMGIymcyWSIqOwOMTqeAvN6sE7EF2GYye5yIkYeNBmNxkDwgbAANgALJNS+XqxgYUH4AWi9gS2XK1W7hDGOw613k6nzf3O9WKMRGBJ84XiwOAMxNUflxeNwMTluz9sLho9oR9ndDs3p2udxcTqcBw7IdAPDT5ADujrZIe1Jl5ACJyMAABYgD+Cq2c5jlWFAAIwNPOnwQfORp8B+mKvP+gFbh2K4XAArFBMFGs4fBMi+bjwhQy5diCO6dMw1ZomKX5kL+/6dAhSEfp0j4mKKfD4ZSICnvWVZYnx2CLnG1ZPFxw4nlRoEgIwADUShdhQupimInEbPwwAWMoZBoBQH7HKyrEWpCVRAA===

- https://ccbrown.github.io/wasm-go-playground/experimental/generics/#A4Qwxg1iDmCmAEBbEBLAdgKAwFwJ7AQFEA3EAGwAo8D4AVASnnW1gCcAzcBAbwwEgS5Co1oYAvlmoIAQugAmbJmhYcu8XnwBq5AK6wKaEIgQBnbK3TRGzNpzCxuEiTnwIAYgHtWiHWRBVXeAAlWBNfbGtlWzVeeDj4WTQ5ChMPHVZ7BPk2RjZWLwx4+EFKELCyCPFJQLcdNDAAJngAmgBBABoEzqDGM1YdMGx1fg6E+D7LfgBhcjJ4djqwClHpRiCqjAX65vY0eFr6huXO6W76RkTkuQAjLKSc4tJSxg0QeAAuAF54G4A6bTIegou1+rXovwoJWW534ty+P2u/10+hBqwhUNW9H4rFg2HSe1gTwOSx6wMWwmCwz4fBxeNYexBMzIlBAvyh9E6iPZWL4Yixzik8EJ5GJzUFPXm5LWm0WOwJRPJPUY7Mp3HgtPx8zQFIFgQBehMSBAwAA2hM0NAALo2VT2RxYLZgZrEeD60KMN0GIymcyWSIqOwOMTqdW4zXEE2GYyW3U0KYeNBmNxkDwgbAANgALPMU2mszLthQXfHE9hk6mM5nlU8Kexc5WQxr6TmK1mi4xYwgS0n6wBmJp1iv9gtOovwbtlvsNatCRiDtP9xth5vz7D99vwZyOo3oCkafIAd0N8LdJg0fAARORgAALEAX97jhM91uZigARgavd+n97HP4l7XLi96PhO5YLkcACs36/v+vJYHw7BeIgHzfMSRyrlmnSrv2nTMFm9Dnq0j5XmQt73u0AHSCRQHYBRAFMmQj6OhQbyYZmnIthB/pZlS1JNns+FviAjAANRKJWFDXDyvKUfB/C7Kh8zIb8lwUIeJjgiUCHABYyhkNqF67J8F7YdqMISEAA=

Reply all
Reply to author
Forward
0 new messages