I don't understand this; why is this a speed consideration?
I don't understand this; why is this a speed consideration?
Fennel does support arity checks but it comes with a runtime cost since it's implemented as a separate Lua call after being transpiled from Fennel.
(fn add-1 [x] (+ x 1))
(lambda add-2 [x] (+ x 2))
transpiles to the following lua: local function add_1(x)
return (x + 1)
end
local function add_2(x)
_G.assert((nil ~= x), "Missing argument x on /home/sullyj3/tmp/fn-vs-lambda/fnl/x.fnl:3")
return (x + 2)
end
return add_2Unfortunately, I can't find that blog post so take my words with a grain of salt.
If I remember correctly, Lua is a stack-based VM. What this means is that every piece of data has a corresponding location on a stack data structure in-memory. Function arguments are pushed into the stack as-needed and popped in the function context, and vice-versa for the function return values.
If you wanted arity-checking in this context, you'd have to confirm that you got exactly the right number of elements on the stack, meaning there would be an extra branch in every function call. This might reduce performance if the branch predictor gets it wrong. Plus there would need to be extra instructions to count and to check the count for each pop off the stack in the context of the function call.
This is from the perspective of calling C functions in Lua, so it's probably more complicated for the VM itself when it's running native Lua code.
This Fennel:
(fn foo [x y ...]
(print x y ...))
(lambda bar [x ?y ...]
(print x ?y ...))
...transpiles to this Lua: local function foo(x, y, ...)
return print(x, y, ...)
end
local function bar(x, _3fy, ...)
_G.assert((nil ~= x), "Missing argument x on test.fnl:3")
return print(x, _3fy, ...)
end
[1] Fennel's alternative `& xs` vargs format does create a runtime check in functions declared with lambda, but it always evaluates to true because it simply nil-checks the table (here `xs`) that it dumps Lua `...` vargs into.Maybe some other OF can confirm/contradict?
This was also used to implement variadic functions like printf before varargs, see https://retrocomputing.stackexchange.com/a/20517
int foo() {
return 0;
}
int main(int argc, char* argv[]) {
return foo("alpha", "beta", 1);
}
$ gcc-13 -Wall -Werror -o foo foo.c
# oh, sads
$ clang -o foo ./foo.c
./foo.c:5:34: warning: too many arguments in call to 'foo'
return foo("alpha", "beta", 1);
~~~ ^
./foo.c:5:15: warning: passing arguments to 'foo' without a prototype is deprecated in all versions of C and is not supported in C2x [-Wdeprecated-non-prototype]
return foo("alpha", "beta", 1);
^
2 warnings generated.From the blurb you quoted, it sounds like Lua doesn't check function argument arity natively, and fennel can. This means that fennel applies a runtime check to validate if a function was called with an expected arity, and if not, propagates an error (however Lua does that, I do not know), hence why arity checked functions are not as performant.
function M(...)
if select("#",...)>3 then
error("failed arity check on M()")
end
endThe Lua VM that they compile to already handles missing arguments with some attendant but unavoidable performance cost.
Fennel's own `lambda` form that they layer on top does check for missing arguments, but it must do so by generating extra Lua code to do those checks. That additional code has a runtime cost.
Using `fn` avoids that extra generated code and its cost.
If Fennel had its own runtime and VM, then the performance story for how missing arguments are handled would be different.