[Luau] Add arrow/lambda functions

It would be nice to be able to use JS-like arrow functions. For example turning:

myList.filter(function (entry) return entry.Value > 100 end)

into:

myList.filter((entry) => entry.Value > 100)
-- return two values
testAandB((atest, btest) => (atest(), btest()))

-- Typing and assignment
myList.filter((entry: IntValue) => entry.Value > 100)

local generateHelloMsg: (string) -> (string) = (name) => "hello, " .. name
print(generateHelloMsg("you")) --> "hello, you"
6 Likes

How would this syntax work for multiple returns?

local a = 2
-- would this
foo((a)=>a+1,a+2)
-- be equivalent to
foo(function(a)return a+1,a+2 end)
-- or
foo(function(a)return a+1 end,a+2)

This syntax would also complicate parsing

Example
local a,b,c
-- ...
foo((a:typeof(b))=>a)
--  ^
-- to figure out the meaning of the second opening parenthesis
foo((a:typeof(b))=>a)
--   ^
-- the name could be a parameter name or variable which is used
foo((a:typeof(b))=>a)
--    ^
-- the colon could be giving a type to the parameter or it could be a method call
foo((a:typeof(b))=>a)
--     ^^^^^^
-- could be the name of a method or typeof in a type context
foo((a:typeof(b))=>a)
--           ^
-- could be a method call or calling typeof in a type context
foo((a:typeof(b))=>a)
--            ^
-- could be a variable passed in or used to get the type from
foo((a:typeof(b))=>a)
--             ^
-- could be ending the method call or typeof call in a type context
foo((a:typeof(b))=>a)
--              ^
-- ends closing parenthesis, but still unclear what the meaning of the parenthesis is
foo((a:typeof(b))=>a)
--               ^^
-- disambiguating token, now the meaning of the parenthesis is known

You should also add use cases for this syntax, when would having this shorthand syntax for functions be useful?

6 Likes

In your first example, it would be equivalent to the second option, you would need to wrap the returns like such to achieve the first option: foo((a)=>(a+1,a+2)).

In your parsing examples list I’m not sure if any of those are valid syntax, the first set of () can only contain argument names, not a:typeof(b) calls… I’m not sure I understand your point there?

1 Like

This would be unusual, normally multiple values seperated by commas aren’t allowed inside of parenthesis (other than function calls).

local a,b = (1,2) -- not allowed

Treating parenthesis would specially have another problem too

-- would this
foo((a)=>(a+1)*2)
-- be equivalent to
foo((a)=>((a+1)*2))
-- or
foo(((a)=>(a+1))*2)

Luau allows specifying the types of all variables

local x:number = 1
local function f(x:number):number
	return x
end

It would make sense to allow specifying the type of parameters using this syntax.

3 Likes

I don’t think what is “normal” matters here since I’m suggesting changing the norm and adding new language features…

foo((a)=>(a+1)*2) would be equivalent to foo((a)=>((a+1)*2)). There is no ambiguity there either… You would need to type foo(((a)=>(a+1))*2) out to achieve the second option.

The typed variable api should still work here without changes:
testFunc((x: number) => x*20)

The problem is that if the parenthesis are treated specially to allow for multiple values, then there can’t be an operator which acts on the result of the parenthesized expression as there could be multiple values.

foo((a)=>(a,a)*2)
-- in this case it would only make sense to multiply by the function by 2
foo((a)=>(a)*2)
-- so it would be weird for it to be different in this case
foo((a)=>(bar(a))*2)
-- this would be problematic even if there was some special case
-- as bar could return multiple results which wouldn't be so unlike the first case

foo((a)=>(a,a)*2) is invalid syntax, you cant multiply a function by an integer. You’re trying to do foo((function(a) return a, a end)*2), which would error, so it’s still consistent.

1 Like

It generates an error, but it’s correct syntactically. Not all errors are syntactic. Metamethods could be used to attribute meaning to multiplying a function

local t = setmetatable({},{__mul=function(f)return f end})
local f = function(a)return a,a end*t
print(f"a") --> a a

I’m not sure how this causes inconsistencies unless you just mean it’s unintuitive?
It would error on foo((a)=>(a,a)*2) but not error on foo((a)=>(a,a)*t)

I think you’d be better off using the same syntax (or near enough) used in JavaScript/Typescript:

--!strict
local hello: string = (name: string) => "hello, " .. name

local hello: string = (name: string) => {
    return "hello, " .. name
}

-- possible solutions for typed anonymous functions
((name: string) => "hello, " .. name): string)("world")
((name: string)<string> => "hello, " .. name)("world")
((name: string): string => "hello, " .. name)("world")
2 Likes

I would be perfectly happy with this approach! I mentioned that I just need JS-like arrow functions, I don’t have very specific preferences for how it works syntactically as long as it’s familiar.

hello would be a string?

Curly brackets would conflict with table syntax

local a = (b) => {b()}
-- is this a function that just calls the first argument
-- or a function that calls the first argument and puts all the results in a table?
local a = (b) => {b();}
-- this also has the same problem

The first example is missing an opening parenthesis? It would also result in an ambiguous syntax because it conflicts with method calling syntax (e.g. ((name:string)=>"hello, "..name):typeof("s") could be interpreted as a method call on the function).
<T> would be confusing for specifying the return type as it looks like it would be a specifying generic types, which it isn’t.

This syntax still has the same problem with complicate parsing when determining if an opening parenthesis is an arrow function or a parenthesized expression.


The inconsistency would be that if the parenthesized expression had a comma in it then it would be treated differently from if it didn’t.

foo((a)=>(a)*t)
-- would become
foo(function(a)return a*t end)

foo((a)=>(a,a)*t)
-- would become
foo(function(a)return a,a end*t)

This feature seems more effort than it is worth, the grammar would be complex to parse, there would have to be weird semantics to make multiple return values work correctly, and the syntax to make it work with types and generic functions would be weird (<T>(v:T):(T,T)=>(v,v)?).

1 Like
-- Current Syntax
local hello: (string) -> (string) = function (name) return "hello, " .. name end

-- Proposed Syntax
local hello: (string) -> (string) = (name) => "hello, " .. name

Your proposed syntax is currently valid Lua code. You’re suggesting a fundamental change to the language’s semantics at that point.

If we’re going to be serious about this, I would suggest something similiar to Rust’s syntax, which doesn’t have any conflicts with Lua:

local hello: (string) -> (string) = |name| "hello, " .. name

local hello2: (string) -> (string) = |name| { return "general " .. name }

The problem with the latter is that it of course introduces the idea of brackets being equivalent to do blocks, which wouldn’t be the case anywhere else in the language.

Up to the Luau team to consider if they care about that or not, but they probably should.

=> in an expression is never valid currently (the closest thing is >=), but the syntax is problematic to parse.

Using curly brackets for separating expressions from lists of statements (do blocks) results in ambiguities because of table constructor syntax.

local Vec = |X,Y,Z|{
	X = X;
	Y = Y;
	Z = Z;
}

This could be interpreted as a function which returns a table (<T1,T2,T3>(T1,T2,T3)->{X:T1,Y:T2,Z:T3}) or one which does nothing ((any,any,any)->()).

Sorry, wrote that when I was tired and thought that was >=.

In the same vein, I forgot that functions can return other functions which can then be called with the table syntax. Still would prefer Rust’s syntax over JS/TS, but either way you’d probably have to use something like do for blocks so it’ll be gross regardless.

I may also be in favor of a shorter syntax for anonymous functions as it makes higher order functions a bit cleaner and nicer to look at. Although I can’t think of any other purpose for lambda functions (in javascript there are quite a few reasons to use the arrow function other than shorter syntax). As such, allowing multiple expressions seems unnecessary and disallowing it would make syntax a lot simpler.

1 Like

We have explored several options for short lambda function expressions and decided not to go in this direction at this time.
Proposal and discussion can be viewed at RFC: Lambda function expressions by vegorov-rbx · Pull Request #49 · Roblox/luau · GitHub

5 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.