ply
ply copied to clipboard
Terse lambda syntax
Ply encourages programming with first-class functions, but the Go syntax makes them ugly. Wouldn't it be nice if, instead of writing:
xs.filter(func(x int) bool { return x > 3 }).
morph(func(x int) bool { return x % 2 == 0 }).
fold(func(acc, x bool) bool { return acc && x }, true)
We could write something like:
xs.filter(|x| -> x > 3).
morph(|x| -> x % 2 == 0).
fold(|acc x| -> acc && x)
Or even:
xs.filter(|x > 3|).morph(|x % 2 == 0|).fold(|x && y|)
There's lots of fun bikeshedding in store for the exact syntax, but at any rate, I think this would be an excellent feature to have.
Of course, there's a reason for Go's verbosity in this regard: types. Since the new lambda syntax has no type information, it can only be used when the type-checker already knows what sort of function it's supposed to be. Obviously, you can't write:
fn := |x| -> len(x)
because the compiler can't infer the type of x. But it is legal to write:
var fn func(c chan string) int
fn = |x| -> len(x)
because now the compiler knows that fn takes a chan string and returns an int. Since len(x) is a valid expression on a chan string that produces an int, the lambda is legal.
But there is a complication here! Some Ply functions, like morph, can't be fully type-checked without first type-checking their argument. So when we write:
xs.morph(|x| -> strconv.Itoa(x))
How can the type-checker determine the type of morph?
Actually, in this case it's pretty easy: we don't know what type the lambda ought to return, but we know the value it ought to take: an int. And once we know that x is an int, we can type-check the lambda body. Since the expression produces a string, we know the full signature of the lambda, so we can finish type-checking morph and generate a function body for it.
However, this process may be trickier than I'm imagining. I guess only time will tell.
As an interesting corollary, it should be possible to assign any generic function or method (e.g. min) to a variable whose type is known. For example:
var fn func(uint8, uint8) uint8 = min
This isn't currently legal, but perhaps it should be. At any rate, it would be a good first step towards type-checking lambdas, since both involve inferring types via assignment.
One more complication: the lambdas in this post consist of a single expression, but what if we want multiple statements? Should lambdas keep return, or should they evaluate to their last statement? Different languages have different approaches here, so it will be worth doing some research.
Lastly, I want to make it clear that I'm very hesitant to modify Go's syntax. I want Ply to feel as natural as possible for Go developers, and once you start changing a little syntax, you've set foot on a slippery slope indeed. Ply already breaks any sort of Go tool that performs type-checking, but this syntax (or any new syntax) would also break any tool that parses Go code, include gofmt. That's a big step to take, and I'm not going to take it until I'm 100% confident that it's the right thing to do.
As an addendum to the last paragraph:
It may be possible to design a lambda syntax that is still legal Go, at least as far as the parser is aware. For example, if the syntax is just an expression with placeholder variables, tools like gofmt won't complain:
xs.filter(λ > 3)
The type-checker will, though, because λ > 3 is a boolean, not a function. But that isn't a problem because we've already forked the type-checker.
However, I'm not sure it's possible to accomplish this for multi-statement lambdas. Furthermore, I don't really like the "placeholder" style; it's better to clearly define the parameters. Otherwise it complicates the scoping rules (i.e. what if λ is already defined?).