Short Anonymous Functions

lua-users home
wiki

Sometimes Lua's anonymous function syntax, function() return ... end, feels verbose for short functions, such as lambda functions in functional programming styles. For example, in Lua a map function [1] function might be used as such:

local y = map(function(p) return translate[p] end, x)

In some languages like Perl, this is written more succinctly as

my @y = map { $translate{$_} } @x;
or even
my @y = @translate{@x};

Here are some alternative approaches for Lua.

Pattern: Stringified Anonymous Functions

We can improve this in Lua with a utility function fn that creates an anonymous function from a shorter string representation. It might be used like this:

local y = map(fn("L1[P1]", translate), x)

fn takes as arguments a string representation of an expression defining the return value of the anonymous function to create and a list of local variables that are referred to as L1, L2, L3, ..., L9 in the expression. The arguments of the anonymous function are referred to with P1, P2, P3, ..., P9. fn can be defined in this way:

function fn(s, ...)
  local src = [[
    local L1, L2, L3, L4, L5, L6, L7, L8, L9 = ...
    return function(P1,P2,P3,P4,P5,P6,P7,P8,P9) return ]] .. s .. [[ end
  ]]
  return loadstring(src)(...)
end

Here's the rest of the code for running this example:

function map(f, t)
  local t2 = {}
  for k,v in pairs(t) do t2[k] = f(v) end
  return t2
end

local translate = {["hello"] = "hola", ["bye"] = "adiós", ["sir"] = "señor"}
local t = map(fn("L1[P1]", translate), {"hello", "sir"})
print(table.concat(t, " ")) --> hola señor

Note, however, that it is inefficient if fn is called repeatedly (e.g. in a loop) on the same stringified expression since each call invokes loadstring (which involves code generation). This can be improved via memoization (see FuncTables) of loadstring. This basic pattern (with memoization) was used in CodeGeneration.

If your anonymous function has no local variables, then the syntax is shorter. For example, fn"P1 > 0" is a function that checks whether its argument is greater than zero.

Some may suggest supporting an arbitrary number of Ln and Pn variables; however, remember that this technique is only intended for short one-line expressions.

Also, in some cases one can move the string->function conversion into the map function to get

local y = map("P1 * 2", x)

Here is another syntax:

getmetatable("").__call =
  function(s, ...) return assert(loadstring("return " .. s))()(...) end
("function(x,y) print(x+y) end")(2,3)  -- prints 5

This technique here has also been called "string lambdas" [1], which have been implemented in JavaScript and Erlang:

[1] http://debasishg.blogspot.com/2007/11/erlang-string-lambdas.html

The approach has its uses but is less-than-ideal because lexical variables cannot be used directly inside the lambda. (A possible solution to that is StringInterpolation.)

--DavidManura, 2007-02

An implementation of string lambdas is available with the PenlightLibraries. Two forms are supported, the first similar to the one implemented by MetaLua and the second resembling Scala lambdas:

> require 'pl'
> L = utils.string_lambda
> = L'|x| x+2' (1)
3
> = L'_+2' (0)
2
> ls = List{'one','two','three'}
> = ls:map(L'_:upper()')
{ONE,TWO,THREE}

The results are cached using the Memoization pattern. Originally, any function in Penlight expecting a function could be passed a string, and an attempt would be made to parse that string as a string lambda, but eventually we felt that this would introduce too much magic. (This most definitely applies to any modification of string metatables to make them directly callable.)

Steve Donovan, 2012

Lua Source Filtering

The following approach does source filtering using only Lua, so it is fully self-contained:

assert(loadstring((([[--filtered
function pass(f) f() end
function fail(f)
  if pcall(f) then error 'fail expected' end
end
pass << x = 1 + 2 >>
fail << x = 1 + nil >>
print 'DONE'
]]):gsub('<<', '(function(a,b) '):gsub('>>', ' end)'))))()

It's important to have some text (e.g. "--") after "[[" on the first line to ensure the first line is counted in the line numbering in any error messages. Also, the text on this line is displayed in error messages and so preferably should convey some meaning.

Here's what syntax and run-time errors then look like:

lua: src.lua:1: [string "--filtered..."]:4: 'then' expected near 'thenn'
stack traceback:
        [C]: in function 'assert'
        src.lua:1: in main chunk
        [C]: ?

$ lua src.lua
lua: [string "--filtered..."]:6: attempt to call global 'Pass' (a nil value)
stack traceback:
        [string "--filtered..."]:6: in main chunk
        src.lua:9: in main chunk
        [C]: ?

A disadvantage of this approach may be that syntax highlighters color the entire main code as a string (however, it colors fine under XEmacs). You could work around that by doing a loadfile on an external file rather than a loadstring.

--DavidManura

Metalua

MetaLua [2] provides a syntax |x,y| x*y for function(x,y) return x*y end.

The Lambda Function Patch [3] provides the same syntax by patching the Lua parser.

do end patch

The "do patch" in LuaPowerPatches makes = do ... end be syntactic sugar for = function() ... end.

RiscLua

RiscLua provides the syntactic sugar \ for function and => for return. Thus

curry = \(f) => \(x) => \(y) => f(x,y) end end end

Concise Anonymous Functions patch

The Concise Anonymous Functions patch in LuaPowerPatches makes = [ ... ] be syntactic sugar for = function() ... end, and = [| ... ] be short for = function() return ... end

Placeholders

Placeholder expressions a la Boost (e.g. tablex.map(_1*_1,{1,2,3,4}) --> {1,4,9,16}) are described in "Placeholder Expressions" in [4], LuaList:2009-03/msg00452.html , and LuaList:2009-04/msg00069.html .

See Also


RecentChanges · preferences
edit · history
Last edited July 4, 2012 9:37 am GMT (diff)