Note
This does not exist, I am sketching out a design idea.
The go
keyword accepts a call expression, evaluates all the terms of the expression up to the point of the call itself, then issues the call inside a newly-created goroutine.
By analogy, the call
keyword accepts a call expression and generates a function literal that evalutes that expression.
Given:
func f() {}
func g(a, b int) {}
func h(s string) int { return 0 }
func do(v int) (bool, error) { return false, nil }
type thing struct{}
func (thing) act(a int, b string) {}
func (thing) perform() {}
func (thing) get() int { return 0 }
Call | Meaning |
---|---|
call f() |
func() { f() } |
call g(5, 3) |
func() { g(5, 3) } |
call h("foo") |
func() int { return h("foo") } |
call p.perform() |
func() { p.perform() } |
call p.act(1, "x") |
func() { p.act(1, "x") } |
Note that whether the resulting function returns or not depends on the result type of the call expression.
With arguments, call
behaves like a function literal, call(a type1, b type2)
allows currying arguments for the call expression.
Call | Meaning |
---|---|
call(a int) g(a, 3) |
func(a int) { g(a, 3) } |
call(b int) g(5, b) |
func(b int) { g(5, b) } |
call(s string) h(s) |
func(s string) int { return h(s) } |
call do(10) |
func() (bool, error) { return do(10) } |
call(z int) do(z) |
func(z int) (bool, error) { return do(z) } |
call(p thing) p.perform() |
func(p thing) { p.perform() } |
call(p thing) p.get() |
func(p thing) int { return p.get() } |
No mechanism is provided for currying result values; at that point you should just write the function literal out. The call notation mixes just fine with ordinary literals, e.g.,
call(z int) func() bool { b, _ := do(z); return b }(z)
becomes
func(z int) bool { b, _ := do(z); return b }
Though, at that point you might as well just write out the literal directly (which is why call
doesn't support it). This does admit the possibility of helper functions, though, e.g., given:
func noError[T any](v T, _ error) T { return v }
You could then productively write call(v int) noError(do(z))
to get:
func(v int) bool { return noError(do(v)) }
The result result of the call
form is an expression, so it can appear in places where expressions go:
func do(flag bool, f func(s string) int) { /* ... */ }
do(true, call(s string) h(s))
func f() func() int {
var p thing
return call p.get()
}
call(sig1...) <call-expr>
where <call-expr>
has result type (sig2...)
, becomes
func(sig1...) (sig2...) { [return] <call-expr> }
In the case where sig2...
is empty, the return
is omitted. In the case where sig1...
is omitted, the resulting function has an empty argument list.
Maybe a new statement-like keyword would be tricky. It avoids conflict with existing code, but I haven't explored all the implications on the existing expression syntax. As an alternative, you could imagine using function notation, like for the other built-in generics (make
, new
, etc.):
call(sig1 ..., <call-expr>)
so, for example:
call(z int, do(z))
This still requires extension of the grammar—this new construct accepts type signature arguments like func
does, for example. But it means the resulting expression "looks like" a call expression to everything outside that nonterminal.