Last active
August 21, 2025 23:01
-
-
Save trentgill/9e84f777b0ba976938972dda91d47024 to your computer and use it in GitHub Desktop.
write quick anonymous functions in lua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- lua funtimes! | |
-- tiny functions that make writing anonymous functions way faster | |
-- no runtime overhead vs writing them yourself | |
-- inspired by fennel's hashfn syntax | |
-- useful for functional programming where you need to create small transformer functions | |
-- or more generally for partial application of arguments | |
-- eg: function makenote(pitch, velocity) | |
-- makepitch = fn'makenote($, 1) | |
-- makepitch(1.3) --> calls makenote(1.3, 1) | |
-- but note the above is just to explain. you generally don't want to assign these to variables | |
-- instead, just plug them into other functions expecting function arguments | |
-- here's a demo using it with table.sort to sort a table alphabetically | |
-- table.sort(a, fn'string.lower($1) < string.lower($2)') | |
-- there are (currently) 3 functions: | |
-- fn(str) converts the string to a function returning the (last) value created | |
-- fnB(str) converts the string to a function that doesn't return (void fn) | |
-- op(str) returns a function that performs the provided operator's function | |
--- limitations | |
-- currently only supports 3 arguments. trivial to add more, but not sure if it adds runtime overhead | |
-- if you need more than 3 args, you should probably write a real function rather than modify this lib | |
-- error handling is poor. if you don't *know* it's correct, then write a real func | |
--- usage | |
-- $: the first argument | |
-- $n: the nth argument ($ is alias for $1) | |
local function examples() | |
addOne = fn'$ + 1' | |
squared = fn'$ * $' | |
mac = fn'$1 * $2 + $3' | |
lerp = fn'$1 + $3 * ($2 - $1)' | |
chain = fn'print "the answer"; 42' | |
-- results | |
addOne(3) --> 4 | |
squared(2) --> 4 | |
mac(2,3,4) --> 10 | |
lerp(1,2,0.5) --> 1.5 | |
chain() --> [prints: the answer] 42 | |
do_cmd = fnB'print "hi"' | |
do_2cmds = fnB'print "hello"; print "world"' | |
-- results | |
do_cmd() -- prints: hi | |
do_2cmds() -- prints: hello\nworld | |
add = op'+' | |
lshift = op'<<' | |
-- results | |
add(3,4) --> 7 | |
lshift(4,2) --> 16 | |
end | |
local ft = {} | |
function ft.fn(str) | |
-- NOTE: we replace numbered elems first | |
-- then do bare $, as it would capture the former | |
str = str:gsub('%$(%d)', '_Z%1') -- replace $n with _Zn | |
str = str:gsub('%$', '_Z1') -- replace $ with _Z1 | |
local last_statement = str:match('.*();') | |
if last_statement then | |
str1 = str:sub(1, last_statement) | |
str2 = str:sub(last_statement+1) | |
str = string.format('return function(_Z1,_Z2,_Z3) %s return %s end', str1, str2) | |
else | |
str = string.format('return function(_Z1,_Z2,_Z3) return %s end', str) | |
end | |
-- note we have to wrap our anon fn in a block, then call it to get the fn | |
return load(str)() | |
end | |
-- no return | |
function ft.fnB(str) | |
str = str:gsub('%$(%d)', '_Z%1') -- replace $n with _Zn | |
str = str:gsub('%$', '_Z1') -- replace $ with _Z1 | |
print(string.format('return function(_Z1,_Z2,_Z3) %s end', str)) | |
return load(string.format('return function(_Z1,_Z2,_Z3) %s end', str))() | |
end | |
ft._ops = { | |
['+'] = function(a,b) return a+b end, | |
['-'] = function(a,b) return a-b end, | |
['*'] = function(a,b) return a*b end, | |
['/'] = function(a,b) return a/b end, | |
['//'] = function(a,b) return a//b end, | |
['%'] = function(a,b) return a%b end, | |
['^'] = function(a,b) return a^b end, | |
['-'] = function(a) return -a end, | |
['&'] = function(a,b) return a&b end, | |
['|'] = function(a,b) return a|b end, | |
['~'] = function(a,b) return a~b end, | |
['>>'] = function(a,b) return a>>b end, | |
['<<'] = function(a,b) return a<<b end, | |
['=='] = function(a,b) return a==b end, | |
['~='] = function(a,b) return a~=b end, | |
['<'] = function(a,b) return a<b end, | |
['>'] = function(a,b) return a>b end, | |
['<='] = function(a,b) return a<=b end, | |
['>='] = function(a,b) return a>=b end, | |
} | |
function ft.op(str) | |
return ft._ops[str] | |
end | |
-- a hack to pull them into global namespace | |
-- for k,v in pairs(ft) do | |
-- if type(v) == 'function' then | |
-- _G[k] = v | |
-- end | |
-- end | |
-- normal return style for require | |
return ft |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment