Skip to content

Instantly share code, notes, and snippets.

@creachadair
Last active September 20, 2025 04:52
Show Gist options
  • Select an option

  • Save creachadair/19b8dfa6ac40ffdc017d89d387e720a7 to your computer and use it in GitHub Desktop.

Select an option

Save creachadair/19b8dfa6ac40ffdc017d89d387e720a7 to your computer and use it in GitHub Desktop.
A Go syntax for compact function literals

A Go Syntax for Compact Function Literals

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.

Examples

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") }
call p.get() func() int { return p.get() }

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 }()

becomes

func(z int) bool { return func() bool { b, _ := do(z); return b }() }

Potentially, the compiler could be taught to do reverse beta conversion (for function literals specifically) which would make this:

func(z int) bool { b, _ := do(z); return b }

Though, at that point you might as well just write out the literal directly. 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(v)) to get:

func(v int) bool { return noError(do(v)) }

The 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()
}

Translation

call(sig1...) <call-expr>

where <call-expr> has result type (sig2...), becomes

func(sig1...) (sig2...) { [return] <call-expr> }

As a special case, call <call-expr> is equivalent to call() <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.

Grammar

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.

Type Inference

One interesting question is whether the compiler could reliably infer the types of named call parameters, e.g., given

func h(s string) int { return len(s) }

could call(s) h(s) infer func(s string) int { return h(s) }? In this simple case maybe so, but it's less clear in more complex cases with nested calls where types may be boxed by interfaces. In theory that shouldn't prevent inference, but there are a already cases today where the compiler declines to infer a type when there is no a priori principal type.

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