From 6b9427008ec1038c2907417a85ecdc8d7f4f9e33 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Wed, 9 Jun 2021 16:25:10 +0300 Subject: [PATCH] Use the alternative for the design since the original couldn't be parsed without backtracking --- rfcs/syntax-lambda-function-expressions.md | 127 +++++++++++---------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/rfcs/syntax-lambda-function-expressions.md b/rfcs/syntax-lambda-function-expressions.md index d3344beb..c32aace0 100644 --- a/rfcs/syntax-lambda-function-expressions.md +++ b/rfcs/syntax-lambda-function-expressions.md @@ -2,34 +2,83 @@ ## Summary -Introduce a way to define anonymous functions using a more terse syntax. +Introduce a way to define anonymous functions using a terse syntax. ## Motivation Current function syntax takes up a lot of space when used for short anonymous that are passed in function calls. -By introducing a shorter syntax, we make code with heavy use of anonymous functions easier to write and arguably, easier to read as well: +By introducing a shorter syntax, we make code with heavy use of anonymous functions easier to write and arguably, easier to read as well. + +Existing syntax: ```lua -local a = sort(c, function(a, b) return a.x > b.x end) local b = filter(map(c, function(a) return a * 2 end), function(a) return a > 0 end) +local b = filter(c, function(a) return a.good end) +local a = sort(c, function(a, b) return a.x > b.x end) ``` -Proposed: +This proposal: ```lua -local a = sort(c, (a, b) -> a.x > b.x) local b = filter(map(c, a -> a * 2), a -> a > 0) -``` -Type annotations are supported are also supported, but with improvements to bi-directional type inference, typechecker should be able to deduce types in many cases: -```lua -local a = sort(c, (a: Bar, b: Bar) -> a.x > b.x) -local b = filter(map(c, (a: number) -> a * 2), (a: number) -> a > 0) +local c = filter(c, a -> a.good) +local a = sort(c, do(a, b) -> a.x > b.x) ``` -Terse syntax for anonymous functions like this is used in various programming languages like C#, D, Java, JavaScript and Julia (ignoring `=>` vs `->`). +Type annotations are also supported, but with improvements to bi-directional type inference, typechecker should be able to deduce types in many cases: +```lua +local b = filter(map(c, do(a: number) -> a * 2), do(a: number) -> a > 0) +local c = filter(c, do(a): boolean -> a.good) +local a = sort(c, do(a: Bar, b: Bar) -> a.x > b.x) +``` ## Design -Syntax of *prefixexp* is extended with additional options: +Syntax of *prefixexp* is extended with an additional option: + +Before: +``` +prefixexp ::= NAME | `(' expr `)' +``` +After: +``` +prefixexp ::= NAME [`->' subexpr] | `(' expr `)' +``` + +Parsing of the this new option is easy, we check a single token for `->` after the NAME. + +Syntax of *simpleexp* is extended with additional option: +``` +| `do` `(' [parlist] `)' [`:' ReturnType] `->' subexpr +``` + +We use `do` as an introduction token here to keep the grammar simple without requiring look-ahead and backtracking. + +While the `->` token here is not strictly required, it is more consistent with the single argument syntax and improves readability when return type annotations are used. + +--- +Note that these lambda function expressions support only a single return value using the *subexpr* grammar, compared to *explist* grammar that supports multiple comma-separated expressions. + +When we are done with parsing, we re-use the existing AST for function expression, but the single *subexpr* that we have is placed inside AstStatReturn. + +## Drawbacks + +### Single return value +In Luau, multiple return value support is a first-class citizen, but the syntax becomes ambiguous if we allow that: +```lua +f(a, b -> b.a, b.b, c) -- how many values does the b return or the f accept? +``` +As a solution, regular anonymous function definition can be used when multiple return values are required. + +### A `function` with extra steps + +This feature introduces an unfamiliar syntax of function definition for people coming from Lua and it adds little real benefit when we already have syntax for anonymous functions. + +## Alternatives + +### Drop the `do` introduction token + +Original version of the proposal had the following syntax extension: + Before: ``` prefixexp ::= NAME | `(' expr `)' @@ -40,56 +89,10 @@ funchead ::= `(' [parlist] `)' [`:' ReturnType] prefixexp ::= NAME [`->' subexpr] | funchead `->' subexpr | `(' expr `)' ``` -Note that an anonymous function with a single argument is allowed to have a type annotation only when it's inside `()`. +Terse syntax for anonymous functions like this is used in various programming languages like C#, D, Java, JavaScript and Julia (ignoring `=>` vs `->`). -We extend the syntax of *prefixexp* instead of *simpleexp* because we parse expressions inside parenthesis at this level. - -### Parsing ``NAME [`->' subexpr]`` - -Parsing of the first option is easy, we check a single token for `->` after the NAME. - -### Parsing ``funchead `->' subexpr`` - -Parsing of the function argument list is in conflict with an expression in `` `(' expr `)'``. -To resolve this issue, we are going to perform the switch to potential function arguments after we consume the common `(` token. - -At that point, if the next token is a `NAME`, we use a lookahead token to check if it's one of `:`, `,` or `)`. -In case it's `:` or `,` we proceed with function argument parsing. -In case it's `)` we will consume both `NAME` and `)` and try to figure out is we had a reference to a binding or if the next token is `->` or `:` in which case that's an anonymous function and we got one argument name. - ---- -When we are done with parsing, we re-use the existing AST for function expression, but the single expression that we have is placed inside AstStatReturn. - -## Drawbacks - -Support for the proposed syntax complicates parse of *prefixexp* and nears a boundary where we almost step into backtracing. - -This feature also introduces an unfamiliar syntax of function definition to Luau for little real benefit. - -## Alternatives - -### Different token instead of `->` - -Other programming languages use `=>` token, but in Luau it is not used right now. - -### Introduction token - -To avoid complications with parsing, we may use an introduction token and get rid of the `->` token. -One option might be the `do` token like in Ruby. Use of existing token preserves backwards compatibility: +The problem with this option is that parsing with limited look-ahead and without backtracing becomes very complicated, and unavoidable in cases like: ```lua -local a = sort(c, do(a, b) a.x > b.x) -local b = filter(map(c, do(a) a * 2), do(a) a > 0) -``` -```lua -local a = sort(c, do(a: Foo, b: Foo) a.x > b.x) -local b = filter(map(c, do(a: number) a * 2), do(a: number) a > 0) -``` - -Syntax of *simpleexp* is extended with a new option: -``` -| `do` funchead subexpr -``` -where -``` -funchead ::= `(' [parlist] `)' [`:' ReturnType] +f((x):number(2)) ``` +Only after the return type annotation can we see that there is no `->` token and this was a member function call.