virgil
virgil copied to clipboard
Lambda support
https://github.com/titzer/virgil/blob/3709c05fc9e61734732d5eedb269acff3b8a0ed0/doc/tutorial/Functions.md?plain=1#L174
I guess this means anonymous functions with closures?
For example:
def intSeq() -> () -> int {
var i = 0;
return () -> int {
i = i + 1;
return i;
}
}
Yeah. Internally I decided to call the feature funcexpr
(function expressions) and the AST node FuncExpr
.
I added a bunch of tests to test/funcexpr
and I added parser support, but I disabled that on purpose until I do a stable revision (i.e. check in the results of aeneas bootstrap
as the next stable binary), because I don't want stable to support incomplete features.
Personally I kind of like the fat arrow =>
from JavaScript lambda syntax. I went down that route but it requires backtracking to reinterpret expressions when the =>
is hit. I wrestled with some alternative syntaxes before eventually just settling on using the def
keyword to introduce a function expression.
//@execute = 112
def main() -> int {
return (def () => 112)();
}
I'd like to further expand the =>
operator to allow it to denote that a method has a body that consists of a single expression and its return type is implicitly that expression's type.
@execute 33=33
class C(val: int) {
def inc() => val++;
}
def main(a: int) => C.new(a).inc();
What is the reason for a separate lambda syntax, why not just have anonymous nested methods? This is the route taken by Go, it's more general and there's one less concept e.g. https://gobyexample.com/closures
Actually I think what I am proposing is really close to what Go does.
The fat arrow =>
is just a shorthand that is actually independent of whether you're declaring a method or creating a closure with an anonymous function (function expression).
E.g. a method:
def foo() => 0;
// is equivalent to:
def foo() -> int {
return 0;
}
And a function expression:
var x = def() => 0;
// is equivalent to:
var x = def() -> int { return 0; }
Have a look at some of the examples in test/funcexpr
, I think it will be clearer.
Have a look at some of the examples in test/funcexpr, I think it will be clearer.
Much clearer, thanks.
The test examples are all single-statement anonymous functions. Are multi-statement anonymous functions allowed? For example is this closure over a multi-statement anonymous function valid?
def intSeq() -> def() -> int {
var i = 0;
return def() -> int {
i = i + 1;
return i;
}
}
Yes, that would be valid (in terms of syntax[1]), though I think I want to disallow closing over mutable locals and only allow closing over def
locals, loop variables, and assigned-once variables.
[1] with a minor correction in the return type:
def intSeq() -> () -> int {
var i = 0;
return def() -> int {
i = i + 1;
return i;
}
}
Java also doesn't allow closing over mutable locals. So you could, e.g. use an array.
def intSeq() -> () -> int {
def v = [0];
return def() -> int {
return v[0]++;
}
}
// or, shorter
def intSeq() -> () -> int {
def v = [0];
return def() => v[0]++;
}
as long as they allow closing over something in outer scopes, Virgil would be able to idiomatically express Knuth's so-called "Man or Boy test" as depicted here for other programming languages - https://rosettacode.org/wiki/Man_or_boy_test
(idiomatically, as opposed to simulating such lambda-closures with classes)
Yes, the idea is that you can indeed close over immutable variables in function scopes.