Go: proposal: Go 2: Lightweight anonymous function syntax

Created on 17 Aug 2017  Â·  53Comments  Â·  Source: golang/go

Many languages provide a lightweight syntax for specifying anonymous functions, in which the function type is derived from the surrounding context.

Consider a slightly contrived example from the Go tour (https://tour.golang.org/moretypes/24):

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

var _ = compute(func(a, b float64) float64 { return a + b })

Many languages permit eliding the parameter and return types of the anonymous function in this case, since they may be derived from the context. For example:

// Scala
compute((x: Double, y: Double) => x + y)
compute((x, y) => x + y) // Parameter types elided.
compute(_ + _) // Or even shorter.
// Rust
compute(|x: f64, y: f64| -> f64 { x + y })
compute(|x, y| { x + y }) // Parameter and return types elided.

I propose considering adding such a form to Go 2. I am not proposing any specific syntax. In terms of the language specification, this may be thought of as a form of untyped function literal that is assignable to any compatible variable of function type. Literals of this form would have no default type and could not be used on the right hand side of a := in the same way that x := nil is an error.

Uses 1: Cap'n Proto

Remote calls using Cap'n Proto take an function parameter which is passed a request message to populate. From https://github.com/capnproto/go-capnproto2/wiki/Getting-Started:

s.Write(ctx, func(p hashes.Hash_write_Params) error {
  err := p.SetData([]byte("Hello, "))
  return err
})

Using the Rust syntax (just as an example):

s.Write(ctx, |p| {
  err := p.SetData([]byte("Hello, "))
  return err
})

Uses 2: errgroup

The errgroup package (http://godoc.org/golang.org/x/sync/errgroup) manages a group of goroutines:

g.Go(func() error {
  // perform work
  return nil
})

Using the Scala syntax:

g.Go(() => {
  // perform work
  return nil
})

(Since the function signature is quite small in this case, this might arguably be a case where the lightweight syntax is less clear.)

Go2 LanguageChange Proposal

Most helpful comment

I support the proposal. It saves typing and helps readability.My use case,

// Type definitions and functions implementation.
type intSlice []int
func (is intSlice) Filter(f func(int) bool) intSlice { ... }
func (is intSlice) Map(f func(int) int) intSlice { ... }
func (is intSlice) Reduce(f func(int, int) int) int { ...  }
list := []int{...} 
is := intSlice(list)

without lightweight anonymous function syntax:

res := is.Map(func(i int)int{return i+1}).Filter(func(i int) bool { return i % 2 == 0 }).
             Reduce(func(a, b int) int { return a + b })

with lightweight anonymous function syntax:

res := is.Map((i) => i+1).Filter((i)=>i % 2 == 0).Reduce((a,b)=>a+b)

All 53 comments

I'm sympathetic to the general idea, but I find the specific examples given not very convincing: The relatively small savings in terms of syntax doesn't seem worth the trouble. But perhaps there are better examples or more convincing notation.

(Perhaps with the exception of the binary operator example, but I'm not sure how common that case is in typical Go code.)

Please no, clear is better than clever. I find these shortcut syntaxes
impossibly obtuse.

On Fri, 18 Aug 2017, 04:43 Robert Griesemer notifications@github.com
wrote:

I'm sympathetic to the general idea, but I find the specific examples
given not very convincing: The relatively small savings in terms of syntax
doesn't seem worth the trouble. But perhaps there are better examples or
more convincing notation.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/21498#issuecomment-323159706, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAAcAxlgwt-iPryyY-d5w8GJho0bY9bkks5sZInfgaJpZM4O6pBB
.

I think this is more convincing if we restrict its use to cases where the function body is a simple expression. If we are required to write a block and an explicit return, the benefits are somewhat lost.

Your examples then become

s.Write(ctx, p => p.SetData([]byte("Hello, "))

g.Go(=> nil)

The syntax is something like

[ Identifier ] | "(" IdentifierList ")" "=>" ExpressionList

This may only be used in an assignment to a value of function type (including assignment to a parameter in the process of a function call). The number of identifiers must match the number of parameters of the function type, and the function type determines the identifier types. The function type must have zero results, or the number of result parameters must match the number of expressions in the list. The type of each expression must be assignable to the type of the corresponding result parameter. This is equivalent to a function literal in the obvious way.

There is probably a parsing ambiguity here. It would also be interesting to consider the syntax

λ [Identifier] | "(" IdentifierList ")" "." ExpressionList

as in

s.Write(ctx, λp.p.SetData([]byte("Hello, "))

A few more cases where closures are commonly used.

(I'm mainly trying to collect use cases at the moment to provide evidence for/against the utility of this feature.)

I actually like that Go doesn't discriminate longer anonymous functions, as Java does.

In Java, a short anonymous function, a lambda, is nice and short, while a longer one is verbose and ugly compared to the short one. I've even seen a talk/post somewhere (I can't find it now) that encouraged only using one-line lambdas in Java, because those have all those non-verbosity advantages.

In Go, we don't have this problem, both short and longer anonymous functions are relatively (but not too much) verbose, so there is no mental obstacle to using longer ones too, which is sometimes very useful.

The shorthand is natural in functional languages because everything is an expression and the result of a function is the last expression in the function's definition.

Having a shorthand is nice so other languages where the above doesn't hold have adopted it.

But in my experience it's never as nice when it hits the reality of a language with statements.

It's either nearly as verbose because you need blocks and returns or it can only contain expressions so it's basically useless for all but the simplest of things.

Anonymous functions in Go are about as close as they can get to optimal. I don't see the value in shaving it down any further.

It's not the func syntax that is the problem, it's the redundant type declarations.

Simply allowing the function literals to elide unambiguous types would go a long way. To use the Cap'n'Proto example:

s.Write(ctx, func(p) error { return p.SetData([]byte("Hello, ")) })

Yes, it's the type declarations that really add noise. Unfortunately, "func (p) error" already has a meaning. Perhaps permitting _ to substitute in for an inferenced type would work?

s.Write(ctx, func(p _) _ { return p.SetData([]byte("Hello, ")) })

I rather like that; no syntactic change at all required.

I do not like the stutter of _. Maybe func could be replaced by a keyword that infers the type parameters:
s.Write(ctx, λ(p) { return p.SetData([]byte("Hello, ")) })

Is this actually a proposal or are you just spitballing what Go would look like if you dressed it like Scheme for Halloween? I think this proposal is both unnecessary and in poor keeping with the language's focus on readability.

Please stop trying to change the syntax of the language just because it _looks_ different to other languages.

I think that having a concise anonymous function syntax is more compelling in other languages that rely more on callback-based APIs. In Go, I'm not sure the new syntax would really pay for itself. It's not that there aren't plenty of examples where folks use anonymous functions, but at least in the code I read and write the frequency is fairly low.

I think that having a concise anonymous function syntax is more compelling in other languages that rely more on callback-based APIs.

To some extent, that is a self-reinforcing condition: if it were easier to write concise functions in Go, we may well see more functional-style APIs. (Whether that is a good thing or not, I do not know.)

I do want to emphasize that there is a difference between "functional" and "callback" APIs: when I hear "callback" I think "asynchronous callback", which leads to a sort of spaghetti code that we've been fortunate to avoid in Go. Synchronous APIs (such as filepath.Walk or strings.TrimFunc) are probably the use-case we should have in mind, since those mesh better with the synchronous style of Go programs in general.

I would just like to chime in here and offer a use case where I have come to appreciate the arrow style lambda syntax to greatly reduces friction: currying.

consider:

// current syntax
func add(a int) func(int) int {
    return func(b int) int {
        return a + b
    }
}

// arrow version (draft syntax, of course)
add := (a int) => (b int) => a + b

func main() {
    add2 := add(2)
    add3 := add(3)
    fmt.Println(add2(5), add3(6))
}

Now imagine we are trying to curry a value into a mongo.FieldConvertFunc or something which requires a functional approach, and you'll see that having a more lightweight syntax can improve things quite a bit when switching a function from not being curried to being curried (happy to provide a more real-world example if anyone wants).

Not convinced? Didn't think so. I love go's simplicity too and think it's worth protecting.

Another situation that happens to me a lot is where you have and you want to now curry the next argument with currying.

now you would have to change
func (a, b) x
to
func (a) func(b) x { return func (b) { return ...... x } }

If there was an arrow syntax you would simply change
(a, b) => x
to
(a) => (b) => x

@neild whilst I haven't contributed to this thread yet, I do have another use case that would benefit from something similar to what you proposed.

But this comment is actually about another way of dealing with the verbosity in calling code: have a tool like gocode (or similar) template a function value for you.

Taking your example:

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

If we assume we had typed:

var _ = compute(
                ^

with the cursor at the position shown by the ^; then invoking such a tool could trivially template a function value for you giving:

var _ = compute(func(a, b float64) float64 { })
                                            ^

That would certainly cover the use case I had in mind; does it cover yours?

Code is read much more often than it is written. I don't believe saving a little typing is worth a change to the language syntax here. The advantage, if there is one, would largely be in making code more readable. Editor support won't help with that.

A question, of course, is whether removing the full type information from an anonymous function helps or harms readability.

I don't think this kind of syntax reduces readability, almost all modern programming languages have a syntax for this and thats because it encourages the use of functional style to reduce the boilerplate and make the code clearer and easier to maintain. It's a great pain to use anonymous functions in golang when they are passed as parameters to functions because you have to repeat yourself typing again the types that you know you must pass.

I support the proposal. It saves typing and helps readability.My use case,

// Type definitions and functions implementation.
type intSlice []int
func (is intSlice) Filter(f func(int) bool) intSlice { ... }
func (is intSlice) Map(f func(int) int) intSlice { ... }
func (is intSlice) Reduce(f func(int, int) int) int { ...  }
list := []int{...} 
is := intSlice(list)

without lightweight anonymous function syntax:

res := is.Map(func(i int)int{return i+1}).Filter(func(i int) bool { return i % 2 == 0 }).
             Reduce(func(a, b int) int { return a + b })

with lightweight anonymous function syntax:

res := is.Map((i) => i+1).Filter((i)=>i % 2 == 0).Reduce((a,b)=>a+b)

The lack of concise anonymous function expressions makes Go less readable and violates the DRY principle. I would like to write and use functional/callback APIs, but using such APIs is obnoxiously verbose, as every API call must either use an already defined function or an anonymous function expression that repeats type information that should be quite clear from the context (if the API is designed correctly).

My desire for this proposal is not even remotely that I think Go should look or be like other languages. My desire is entirely driven by my dislike for repeating myself and including unnecessary syntactic noise.

In Go, the syntax for function declarations deviates a bit from the regular pattern that we have for other declarations. For constants, types, variables we always have:

keyword name type value

For example:

const   c    int  = 0
type    t    foo
var     v    bool = true

In general, the type can be a literal type, or it can be a name. For functions this breaks down, the type always must be a literal signature. One could image something like:

type BinaryOp func(x, y Value) Value

func f BinaryOp { ... }

where the function type is given as a name. Expanding a bit, a BinaryOp closure could then perhaps be written as

BinaryOp{ return x.Add(y) }

which might go a long way to shorter closure notation. For instance:

vector.Apply(BinaryOp{ return x.Add(y) })

The main disadvantage is that parameter names are not declared with the function. Using the function type brings them "in scope", similar to how using a struct value x of type S brings a field f into scope in a selector expression x.f or a struct literal S{f: "foo"}.

Also, this requires an explicitly declared function type, which may only make sense if that type is very common.

Just another perspective for this discussion.

Readability comes first, that seems to be something we can all agree on.

But that said, one thing I want to also chime in on (since it doesn't look like anyone else said it explicitly) is that the question of readability is always going to hinge on what you're used to. Having a discussion as we are about whether it hurts or harms readability isn't going to get anywhere in my opinion.

@griesemer perhaps some perspective from your time working on V8 would be useful here. I (at least) can say I was very much happy with javascript's prior syntax for functions (function(x) { return x; }) which was (in a way) even heavier to read than Go's is right now. I was in @douglascrockford's "this new syntax is a waste of time" camp.

But, all the same, the arrow syntax _happened_ and I accepted it _because I had to_. Today, though, having used it a lot more and gotten more comfortable with it, I can say that it helps readability tremendously. I used the case of currying (and @hooluupog brought up a similar case of "dot-chaining") where a lightweight syntax produces code that is lightweight without being overly clever.

Now when I see code that does things like x => y => z => ... and it is much easier to understand at a glance (again... because I'm _familiar_ with it. not all that long ago I felt quite the opposite).

What I'm saying is: this discussion boils down to:

  1. When you aren't used to it, it seems _really_ strange and borderline useless if not harmful to readability. Some people just have or don't have a feeling one way or another on this.
  2. The more functional programming you're doing, the more the need for such a syntax pronounces itself. I would guess that this has something to do with functional concepts (like partial application and currying) that introduce a lot of functions for tiny jobs which translates to noise for the reader.

The best thing we can do is provide more use-cases.

In response to @dimitropoulos's comment, here's a rough summary of my view:

I want to use design patterns (such as functional programming) that would greatly benefit from this proposal, as their use with the current syntax is excessively verbose.

@dimitropoulos I've been working on V8 alright, but that was building the virtual machine, which was written in C++. My experience with actual Javascript is limited. That said, Javascript is a dynamically typed language, and without types much of the typing goes away. As several people have brought up before, a major issue here is the need to repeat types, a problem that doesn't exist in Javascript.

Also, for the record: In the early days of designing Go we actually looked at arrow syntax for function signatures. I don't remember the details but I'm pretty sure notation such as

func f (x int) -> float32

was on the white board. Eventually we dropped the arrow because it didn't work that well with multiple (non-tuple) return values; and once the func and the parameters where present, the arrow was superfluous; perhaps "pretty" (as in mathematically looking), but still superfluous. It also seemed like syntax that belonged to a "different" kind of language.

But having closures in a performant, general purpose language opened the doors to new, more functional programming styles. Now, 10 years down the road, one might look at it from a different angle.

Still, I think we have to be very careful here to not create special syntax for closures. What we have now is simple and regular and has worked well so far. Whatever the approach, if there's any change, I believe it will need to be regular and apply to any function.

In Go, the syntax for function declarations deviates a bit from the regular pattern that we have for other declarations. For constants, types, variables we always have:
keyword name type value
[…]
For functions this breaks down, the type always must be a literal signature.

Note that for parameter lists and const and var declarations we have a similar pattern, IdentifierList Type, which we should probably also preserve. That seems like it would rule out the lambda-calculus-style : token to separate variable names from types.

Whatever the approach, if there's any change, I believe it will need to be regular and apply to any function.

The keyword name type value pattern is for _declarations_, but the use-cases that @neild mentions are all for _literals_.

If we address the problem of literals, then I believe the problem of declarations becomes trivial. For declarations of constants, variables, and now types, we allow (or require) an = token before the value. It seems like it would be easy enough to extend that to functions:

FunctionDecl = "func" ( FunctionSpec | "(" { FunctionSpec ";" } ")" ).
FunctionSpec = FunctionName Function |
               IdentifierList (Signature | [ Signature ] "=" Expression) .

FunctionLit = "func" Function | ShortFunctionLit .
ShortParameterList = ShortParameterDecl { "," ShortParameterDecl } .
ShortParameterDecl = IdentifierList [ "..." ] [ Type ] .

The expression after the = token must be a function literal, or perhaps a function returned by a call whose arguments are all available at compile time. In the = form, a Signature could still be supplied to move the argument type declarations from the literal to the FunctionSpec.

Note that the difference between a ShortParameterDecl and the existing ParameterDecl is that singleton IdentifierLists are interpreted as parameter names instead of types.


Examples

Consider this function declaration accepted today:

func compute(f func(x, y float64) float64) float64 { return f(3, 4) }

We could either retain that (e.g. for Go 1 compatibility) in addition to the examples below, or eliminate the Function production and use only the ShortFunctionLit version.

For various ShortFunctionLit options, the grammar I propose above gives:

Rust-like:

ShortFunctionLit = "|" ShortParameterList "|" Block .

Admits any of:

func compute = |f func(x, y float64) float64| { f(3, 4) }
func compute(func (x, y float64) float64) float64 = |f| { f(3, 4) }



md5-c712da47cbcf3d0379ff810dfd76ce59



```go
func (
    compute(func (x, y float64) float64) float64 = |f| { f(3, 4) }
)



md5-8a4d86e5ac5f718d8d35839eaf9f1029



ShortFunctionLit = "(" ShortParameterList ")" "=>" Expression .



md5-e429c4db0e2a76fe83f1f524910c0075



```go
func compute(func (x, y float64) float64) float64 = (f) => f(3, 4)



md5-bcb7677c087284f6121b65ce14d46d93



```go
func (
    compute(func (x, y float64) float64) float64 = (f) => f(3, 4)
)



md5-bf0cf8ca5f55bbedf92dc2047d871378



ShortFunctionLit = "λ" ShortParameterList "." Expression .



md5-3c1a0d273a1aee09721883f5be8fcfce



```go
func compute(func (x, y float64) float64) float64) = λf.f(3, 4)



md5-87735958588cf5a763da8a89d1f9a675



```go
func (
    compute(func (x, y float64) float64) float64) = λf.f(3, 4)
)



md5-d613a37ac429244205560535e5401d63



ShortFunctionLit = "\" ShortParameterList "->" Expression .



md5-95523002741f1036dff7837c1701336d



```go
func compute(func (x, y float64) float64) float64) = \f -> f(3, 4)



md5-818e7097669fe3bc7a333787735e5657



```go
func (
    compute(func (x, y float64) float64) float64) = \f -> f(3, 4)
)



md5-af63df358fad8d4beffd23e2d0c337a4



ShortFunctionLit = "[" ShortParameterList "]" Block .



md5-f66b9b33e7dca8cce60726de14cfc931



```go
func compute(func (x, y float64) float64) float64) = [f] { f(3, 4) }



md5-13e2e0ab357ce95a5a0e2fbd930ba841



```go
func (
    compute(func (x, y float64) float64) float64) = [f] { f(3, 4) }
)

Personally, I find all but the Scala-like variants to be fairly legible. (To my eye, the Scala-like variant is too heavy on parentheses: it makes the lines much more difficult to scan.)

Personally I'm mainly interested in this if it lets me omit the parameter and result types when they can be inferred. I'm even fine with the current function literal syntax if I can do that. (This was discussed above.)

Admittedly this goes against @griesemer 's comment.

Whatever the approach, if there's any change, I believe it will need to be regular and apply to any function.

I don't quite follow this. Function declarations necessarily must include the full type information for the function, since there's no way to derive it with sufficient precision from the function body. (This isn't the case for all languages, of course, but it is for Go.)

Function literals, in contrast, could infer type information from context.

@neild Apologies for being imprecise: What I meant this sentence is that if there were new different syntax (arrows or what have you), it should be somewhat regular and apply everywhere. If it's possible that types can be omitted, that would be again orthogonal.

@griesemer Thanks; I (mostly) agree with that point.

I think the interesting question for this proposal is whether having some syntax is a good idea or not; what that syntax would be is important but relatively trivial.

However, I can't resist the temptation to bikeshed my own proposal a bit.

var sum func(int, int) int = func a, b { return a + b }

@neild's proposal feels right to me. It's pretty close to the existing syntax, but works for functional programming as it eliminates the repetition of the type specifications. It's not _that_ much less compact than (a, b) => a + b, and it fits well into the existing syntax.

@neild

var sum func(int, int) int = func a, b { return a + b }

Would that declare a variable, or a function? If a variable, what would the equivalent function declaration look like?

Under my declaration schema above, if I'm understanding correctly it would be:

ShortFunctionLit = "func" ShortParameterList Block .
func compute = func f func(x, y float64) float64 { return f(3, 4) }
func compute(func (x, y float64) float64) float64 = func f { return f(3, 4) }
func (
    compute = func f func(x, y float64) float64 { return f(3, 4) }
)
func (
    compute(func (x, y float64) float64) float64 = func f { return f(3, 4) }
)

I don't think I'm a fan: it stutters a bit on func, and doesn't seem to provide enough of a visual break between the func token and the parameters that follow.

Or would you leave out parens from the declaration, rather than assigning to literals?

func compute f func(x, y float64) float64 { return f(3, 4) }

I still don't like the lack of visual break, though...

Would that declare a variable, or a function? If a variable, what would the equivalent function declaration look like?

A variable. The equivalent function declaration would presumably be func sum a, b { return a+b }, but that would be invalid for obvious reasons--you can't elide parameter types from function declarations.

The grammar change I'm thinking of would be something like:

ShortFunctionLit = "func" [ IdentifierList ] [ "..." ] FunctionBody .

A short function literal is distinguished from a regular function literal by omitting the parentheses on the parameter list, defines only the names of the incoming parameters, and does not define the outgoing parameters. The types of the incoming parameters and the types and number of outgoing parameters are derived from the surrounding context.

I don't think there's any need to allow specifying optional parameter types in a short function literal; you just use a regular function literal in that case.

As @ianlancetaylor pointed out, the light-weight notation really only makes sense when it permits the omission of parameter types because they can be inferred easily. As such, @neild 's suggestion is the best and simplest I've seen so far. The one thing it doesn't permit easily though is a light-weight notation for function literals that want to refer to named result parameters. But perhaps in that case they should use the full notation. (It's just a bit irregular).

We might even be able to parse (x, y) { ... } as short form for func (x, y T) T { ... }; though it would require a bit of parser look-ahead, but perhaps not too bad.

As an experiment, I modified gofmt to rewrite function literals into the compact syntax and ran it against src/. You can see the results here:

https://github.com/neild/go/commit/2ff18c6352788aa8f8cbe8b5d5d4c73956ca7c6f

I didn't make any attempt to limit this to cases where it makes sense; I just wanted to get a sense for how the compact syntax might play out in practice. I haven't dug through it enough yet to develop any opinions on the results.

@neild Nice analysis. Some observations:

  1. The fraction of cases in which the function literal is bound using := is disappointing, since handling those cases without explicit type annotations would require a more complicated inference algorithm.

  2. The literals passed to callbacks are easier to read in some cases, but more difficult in others.
    For example, losing the return-type information for function literals that span many lines is a bit unfortunate, since that also tells the reader whether they're looking at a functional API or an imperative one.

  3. The reduction in boilerplate for function literals within slices is substantial.

  4. defer and go statements are an interesting case: would we infer the argument types from the arguments actually passed to the function?

  5. A couple of trailing ... tokens are missing from the examples.

defer and go are indeed a quite interesting case.

go func p {
  // do something with p
}("parameter")

Would we derive the type of p from the actual function parameter? This would be quite nice for go statements, although you can of course achieve much the same effect by just using a closure:

p := "parameter"
go func() {
  // do something with p
}()

I would totally support this. Frankly I don't care how much it "looks like other languages", I just want a less verbose way to use anonymous functions.

EDIT: Borrowing the composite literal syntax...

type F func(int) float64
var f F
f = F {      (i) (o) { o = float64(i); return } }
f = F {      (i) o   { o = float64(i); return } } // single return value
f = F { func (i) o   { o = float64(i); return } } // +func for good measure?

Just an idea:
Here is what OP's example would look like with an _untyped function literal_ with Swift's syntax:

compute({ $0 + $1 })

I believe this would have the advantage of being fully backwards compatible with Go 1.

I just found this because i was writing a simple tcp chat app,
basically i have a structure with a slice inside it

type connIndex struct {
    conns []net.Conn
    mu    sync.Mutex
}

and i'd like to apply some operations to it concurrently (adding connections, sending messages to all etc)

and instead of following the normal path of copy-pasting the mutex locking code, or using a daemon goroutine to manage access i thought i'd just pass a closure

func (c *connIndex) run(f func([]net.Conn)) {
    c.mu.Lock()
    defer c.mu.Unlock()
    f(c.conns)
}

for short operations its overly verbose (still better then lock and defer unlock())

conns.run(func(conns []net.Conn) { conns = append(conns, conn) })

This violates the DRY principle as i've typed out that exact function signature in the run method.

If go supported infering the function signature i could write it like this

conns.run(func(conns) { conns = append(conns, conn) })

I don't think this makes the code less readable, you can tell it is a slice because of append, and because i've named my variables well you can guess it is a []net.Conn without looking at the run method signature.

I'd avoid trying to infer the types of paramaters based on the function body, instead add inference only for cases where it is obvious (like passing closures to functions).

i'd say this does not harm readibility as it gives the reader an option, if they don't know the type of the paramater they can godef it or hover over it and get the editor to show it to them.

Sorta like how in a book they don't repeat the characters introduction, except we would have a button to show it / jump to it.

I'm bad at writing so hopefully you survived reading this :)

I think this is more convincing if we restrict its use to cases where the function body is a simple expression.

I dare to object. This still would lead to two ways of defining a function, and one of the reasons why I fell in love with Go is that while it has some verbosity here and there, it has a refreshing expressiveness: you see where a closure is because there is either a func keyword or the parameter is a func, if you trace it.

conns.run(func(conns []net.Conn) { conns = append(conns, conn) })
This violates the DRY principle as i've typed out that exact function signature in the run method.

DRY _is_ important, no doubt. But applying it to each and every part of programming for the sake of keeping up the principle at the cost of the ability to understand the code with the smallest amount of effort possible, is a bit overshooting the mark, imho.

I think the general problem here (and a few other proposals) is that the discussion is mostly about how to safe effort _writing_ the code, whereas imho it should be how to safe effort _reading_ the code. Years after one has written it. I have recently come along a poc.pl of mine and I am still trying to figure out what it does... ;)

conns.run(func(conns) { conns = append(conns, conn) })
I don't think this makes the code less readable, you can tell it is a slice because of append, and because i've named my variables well you can guess it is a []net.Conn without looking at the run method signature.

From my point of view, there are several issues with this statement. I do not know how others see it, but I _hate_ guessing. One might be right, one might be wrong, but surely one has to put effort into it - for the benefit of saving to „type“ []net.Conn. And the readability as well as the comprehensibility of code should be supported by good variable names, not based on it.

To conclude: I think the focus of the discussion should shift away from how to reduce minor efforts when writing code to how to reduce efforts to comprehend said code.

I close with quoting Dave Cheney quoting Robert Pike (iirc)

Clear is better than clever.

The tedium of typing out function signatures can be somewhat relieved by auto completion. For example, gopls offers completions that create function literals:
cb

I think this provides a good middle ground where the type names are still in the source code, there remains only one way to define an anonymous function, and you don't have to type out the entire signature.

will this be added or not?
... for those who don't like this feature can still use the old syntax.
... for us who want better simplicity, we can use this new feature hopefully, it's been 1 year since i wrote go, i am not sure if the community still thinks this is important,
... will this be added or not?

@noypi No decision has been made. This issue remains open.

https://golang.org/wiki/NoPlusOne

I back this proposal and I think this feature, in conjunction with generics, would make functional programming in Go more developer friendly.

Here is what I would like to see, roughly:

type F func(int, int) int

// function declaration
f := F (x, y) { return x * y}

// function passing 
// g :: func(F)
g((x, y) { return x * y })

// returning function
func h() F {
    return (x, y) { return x * y }
}

I'd love to be able to type (a, b) => a * b and move on.

I can't belive arrow functions are still not available in Go lang.
It is amazing how clear and simple is to work with in Javascript.

JavaScript can implement this trivially since it doesn't care about the parameters, the number of them, the values, or their types until they're actually used.

Being able to omit types in function literals would help a lot with the functional style I use for the Gio layout API. See the many "func() {...}" literals in https://git.sr.ht/~eliasnaur/gio/tree/master/example/kitchen/kitchen.go? Their actual signature should have been something like

func(gtx layout.Context) layout.Dimensions

but because of the long type names, the gtx is a pointer to a shared layout.Context that contains the incoming and outgoing values from each function call.

I'm probably going to switch to the longer signatures regardless of this issue, for clarity and correctness. Nevertheless, I believe my case is a good experience report in support of shorter function literals.

P.S. One reason I'm leaning towards the longer signatures is because they can be shortened by type aliases:

type C = layout.Context
type D = layout.Dimensions

which shortens the literals to func(gtx C) D { ... }.

A second reason is that the longer signatures are forward compatible with whatever resolves this issue.

I came here with an idea and found that @networkimprov had already suggested something similar here.

I like the idea of using a function type (could also be an unnamed function type or alias) as the specifier for a function literal, because it means that we can use the usual type inference rules for parameters and return values, because we know the exact types in advance. This means that (for example) auto-completion can work as usual and we wouldn't need to introduce funky top-down type-inference rules.

Given:

type F func(a, b int) int

my original thought was:

F(a, b){return a + b}

but that looks too much like a normal function call - it doesn't look like a and b are being defined there.

Throwing out other possibilities (I don't like any of them particularly):

F->(a, b){return a + b}
F::(a, b){return a + b}
(a, b := F){ return a + b }
F{a, b}{return a + b}
F{a, b: return a + b}
F{a, b; return a + b}

Perhaps there's some nice syntax lurking around here somewhere :)

A key point of composite literal syntax is that it doesn't require type information in the parser. The syntax for structs, arrays, slices, and maps is identical; the parser does not need to know the type of T to generate a syntax tree for T{...}.

Another point is that the syntax also does not require backtracking in the parser. When there is ambiguity whether a { is part of a composite literal or a block, that ambiguity is always resolved in favor of the latter.

I still rather like the syntax I proposed somewhere earlier in this issue, which avoids any parser ambiguity by retaining the func keyword:

func a, b { return a + b }

I removed my :-1:. I'm still not :+1: on it but I am reconsidering my position. Generics are going to cause an increase in short functions like genericSorter(slice, func(a, b T) bool { return a > b }). I also found https://github.com/golang/go/issues/37739#issuecomment-624338848 compelling.

There are two major ways being discussed for making function literals more concise:

  1. a short form for bodies that return an expression
  2. eliding the types in function literals.

I think both should be handled separately.

If FunctionBody is changed to something like

FunctionBody = Block | "->" ExpressionBody
ExpressionBody = Expression | "(" ExpressionList ")"

that would mostly help function literals with or without type elision and would also allow very simple function and method declarations to be lighter on the page:

func (*T) Close() error -> nil

func (e *myErr) Unwrap() error -> e.err

func Alias(x int) -> anotherPackage.OriginalFunc(x)

func Id(type T)(x T) T -> x

func Swap(type T)(x, y T) -> (y, x)

(godoc and friends could still hide the body)

I used @ianlancetaylor's syntax in that example, the major downside of which is that is that it requires the introduction of a new token (and one that would look odd in func(c chan T) -> <-c!) but it might be okay to reuse an existing token such as "=", if there's no ambiguity. I'll use "=" in the remainder of this post.

For type elision there are two cases

  1. something that always works
  2. something that only works in a context where the types can be deduced

Using named types like @griesemer suggested would always work. There seem to be some issues with the syntax. I'm sure that could be worked out. Even if they were, I'm not sure it would solve the problem. It would require a proliferation of named types. These would either be in the package defining the place where they're used or they would have to be defined in every package using them.

In the former you get something like

slices.Map(s, slices.MapFunc(x) = math.Abs(x-y))

and in the latter you get something like

type mf func(float64) float64
slices.Map(s, mf(x) = math.Abs(x-y))

Either way there's enough clutter that it doesn't really cut the boilerplate down much unless each name is used a lot.

A syntax like @neild's could only be used when the types could be deduced. A simple method would be like in #12854, just list every context where the type is known—parameter to a function, being assigned to a field, sent on a channel, and so on. The go/defer case @neild brought up seems useful to include, as well.

That approach specifically does not allow the following

zero := func = 0
var f interface{} = func x, y = g(y, x)

but those are cases where it would pay to be more explicit, even if it were possible to infer the type algorithmically by examine where and how those are used.

It does allow many useful cases, including the most useful/requested:

slices.Map(s, func x = math.Abs(x-y))
v := cond(useTls, FetchCertificate, func = nil)

being able to choose to use a block independent of the literal syntax also allows:

http.HandleFunc("/bar", func w, r {
  // many lines ...
})

which is a particular case increasingly pushing me toward a :+1:

One question that I haven't seen raised is how to deal with ... parameters. You could make an argument for either

f(func x, p = len(p))
f(func x, ...p = len(p))

I don't have an answer to that.

@jimmyfrasche

  1. eliding the types in function literals.

I believe this should be handled with the addition of function-type literals. Where the type replaces 'func' and the argument types are emitted (as they are defined by the type). This maintains readabillity and is fairly consistent with the literals for other types.

http.Handle("/", http.HandlerFunc[w, r]{
    fmt.Fprinf(w, "Hello World")
})
  1. a short form for bodies that return an expression

Refactor the function as its own type and then things become much cleaner.

type ComputeFunc func(float64, float64) float64

func compute(fn ComputeFunc) float64 {
    return fn(3, 4)
}

compute(ComputeFunc[a,b]{return a + b})

If this is too verbose for you, then type alias the function type inside your code.

{
    type f = ComputeFunc

    compute(f[a,b]{return a + b})
}

In the special case of a function with no arguments, the brackets should be omitted.

type IntReturner func() int

fmt.Println(IntReturner{return 2}())

I pick square brackets because the contracts proposal is already using extra standard brackets for generic functions.

@Splizard I stand by argument that that would just push the clutter out of the literal syntax into many extra type definitions. Each such definition would need to be used at least twice before it could be shorter than just writing the types in the literal.

I'm also not sure it would play too well with generics in all cases.

Consider the rather strange function

func X(type T)(v T, func() T)

You could name a generic type to be used with X:

type XFunc(type T) func() T

If only the definition of XFunc is used to derive the types of the parameters, when calling X you'd need to tell it which T to use even though that's determined by the type of v:

X(v, XFunc(T)[] { /* ... */ })

There could be a special case for scenarios like this to allow T to be inferred, but then you'd end up with much of the machinery as would be needed for type elision in func literals.

You could also just define a new type for every T you call X with but then there's not much savings unless you call X many times for each T.

Was this page helpful?
0 / 5 - 0 ratings