New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Lambda function expressions #49
RFC: Lambda function expressions #49
Conversation
This is mentioned in the RFC but this would also be the first time (I think?) that we have two syntactical variants for the same thing. The parsing of It's unclear how to incorporate return type annotations into this syntax. |
I'm not sure it makes sense to get rid of the Keeping the let a: Instance[] = [ new Part(), new Folder(), new Part() ];
// TS type inference infers 'Part[]' here, otherwise 'Instance[]' without return type annotation
let filtered = a.filter((x: any): x is Part => x instanceof Part); The same will apply in Luau. local a: {Instance} = { Instance.new("Part"), Instance.new("Folder"), Instance.new("Part") }
local filtered = filter(a, do(x): x :: Part -> x:IsA("Part")) |
|
I don't see a problem with return type annotations, I even mention how we resolve the parsing of |
I missed that, I think that runs into more issues.
|
There wasn't any mention of a return type annotation, which is why I was bringing it up. But yeah, like I said it's not as nice to read. The points that @zeux brought up earlier is also the exact reason why I am a big proponent for having the starting token. It keeps the language simple for the parser as well as readers. |
I think of the opposite, that it is consistent to use ':' as the function already uses that. And we have a function here, not a function type.
This is a problem I haven't though about. |
Sorry, that was my mistake. I have updated the document to correctly reference function header grammar instead of function argument grammar. |
Also, since the RFC doesn't make a mention of it as well, do we want multiple returns? I imagine yes, but this will run into ambiguous syntax as well: How many arguments are we actually passing?
I'd probably prefer to solve it by requiring explicit parentheses syntactically around the lambda expression if it is found in expression list context and the lambda expression has more than 1 value. If the parser detects this ambiguity, it should throw a parse error pointing out this ambiguity. |
@alexmccord Thanks - multiple returns seem to be a problem with any syntax here that doesn't require extra parentheses (something that we've avoided in the past for other features like |
…sed without backtracking
I've changed the proposal to use the alternative with an introduction token. Both previous and current proposals define grammar to only support a single return value (subexpr). |
Thanks everyone for the feedback. My overall thoughts are that we should table this proposal for now. This proposal introduces syntax sugar - it doesn't unlock new capabilities. Ergonomics are important, but what we're discovering is that it's difficult to reach this ergonomics without both internal and user-facing complexity. In particular:
Overall it feels that while the feature is tempting, there's significant complications with the design and pretty much every variation has severe issues one way or the other. Because of this and because this is purely sugar, unlike something like |
C.c. @vegorov-rbx & @zeux I wanted to jump in to this to add to the convo since it has been a while. I considered making a new separate RFC for this, but I figured I would reply to this existing one instead with my suggestions. In short, my thoughts are that perhaps stealing syntax from Rust would just kind of work. In general, this alternative syntax is a lot easier to parse. Both visually, and by the language, especially when compared to some of the potential alternatives above. It is visually obvious, and low conflict for readability and parsing due to the lack of How this addresses the conflicts above
Exploring this syntax in luauIn luau it would look like this: local lessThan = |a: number, b: number|: boolean a < b
local sortedAsc = |a: number, b: number|: (number, number) math.min(a, b), math.max(a, b)
local getOne = || 1 The parsing for this is very similar to the parsing of a typical anonymous function expression, but instead of parsing a function body, we are parsing what is basically just a return statement in disguise. The main problems are:
Multi-value ambiguityWhen it comes to the ambiguity with multiple values, I believe it is reasonable for the lambda to just be greedy and eat everything. This makes parsing easier here, but also in a lot of other cases too, which I think makes it pretty natural over other alternatives which generally would involve limiting the functionality of the lambda. local sortedAsc = (|a: number, b: number|: (number, number) math.min(a, b), math.max(a, b))
local lambdaMin, max = (|a: number, b: number|: (number, number) math.min(a, b)), math.max(a, b)
-- With the ambiguous case being equivalent to sortedAsc above
local sortedAscAmbig = |a: number, b: number|: (number, number) math.min(a, b), math.max(a, b) While it is probably possible to display linter warnings in some cases, in a lot of cases the type checker ends up indicating what's going on in a way that I think most programmers would probably understand, and despite still definitely being a flaw, I think that it's an easy enough flaw to overcome.I don't recall what the sort of official stance for this style of function call is, but I figured I would mention it as a possibility. The unique syntax also allows for this open call syntax to be valid, and it looks consistent enough with the others not to seem weird or out of place, especially because there isn't a leading token. someFunction "abc"
someFunction { a = 123 }
someFunction |a, b| a / b, b / a More detail on parsingGoing into more depth about how similar this syntax is to existing anonymous function syntax, everything effectively is just a bunch of syntax substitutions in order.
Just for reference again, these two statements would be identical to eachother: local a = function(a: number, b: number, ...: number): (number, number, number...)
return a, b, ...
end
local a = |a: number, b: number, ...: number|: (number, number, number...) a, b, ... The problem of determining when the actual expression/statement ends is pretty much exactly the same as the end of an assignment, which is also a solved problem: local a, b = 1, 2 -- Here we go from expression -> new statement on the next line
local a = || 1, 2 -- Here we go from expression -> new statement on the next line
local a = (|| 1, 2)
local a, b = (|| 1, 2), 3
local a, b = 1, || 2, 3 |
|
Like this maybe: foldLeft(table, (|a ,b| a + b), 0)
-- Multiple returns
multiple(|a, b, c| (a, b, c), "Hello World")
-- And if you want to use statements you should use the normal anon function syntax:
callback(function(message)
print(message)
local something = 10
return something
end) |
That's inconsistent with other places in the grammar though, where parenthesizing suppresses multiple value evaluation, eg |
Personally, I expect |
I agree that this is the sensible expectation. But this is also why we've decided to not go ahead with this - it's too error prone. |
No description provided.