News for hackers. Stuff that matters.
Re:Why Fennel? by robertlagrant on Wednesday September 13, 2023 @01:21PM (#37499149)
> Another common criticism of Lua is that it lacks arity checks; that is, if you call a function without enough arguments, it will simply proceed instead of indicating an error. Fennel allows you to write functions that work this way (fn) when it's needed for speed, but it also lets you write functions which check for the arguments they expect using lambda.

I don't understand this; why is this a speed consideration?

Re:Why Fennel? by bnert on Wednesday September 13, 2023 @01:31PM (#37499247)
Arity explanation: https://en.wikipedia.org/wiki/Arity

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 is 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.

Re:Why Fennel? by nmz on Wednesday September 13, 2023 @01:55PM (#37499532)
So it basically does

  function M(...)
    if select("#",...)>3 then
      error("failed arity check on M()")
    end
  end
Re:Why Fennel? by ravi-delia on Wednesday September 13, 2023 @03:06PM (#37500388)
pretty much, yeah
Re:Why Fennel? by rpdillon on Wednesday September 13, 2023 @01:28PM (#37499218)
I'm not an expert, but my understanding is the Lua has a very limited grammar that is specifically designed to be parsed in a single pass. This precluded Lua from performing arity checks on function calls.

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.

Re:Why Fennel? by vore on Wednesday September 13, 2023 @03:37PM (#37500772)
The grammar shouldn't have anything to do with arity checking – at runtime, you're going to know if you passed a function 2 arguments or 3 arguments, and due to the presence of dynamic evaluation, knowing how many arguments a function takes is not statically knowable anyway. My best guess is that they want to avoid the overhead of a runtime check for arity.
Re:Why Fennel? by iimblack on Wednesday September 13, 2023 @01:25PM (#37499182)
I think they meant speed of implementation.
Re:Why Fennel? by gumby on Wednesday September 13, 2023 @01:56PM (#37499559)
My memory of this is hazy, in part because I wasn't a C programmer in those days, but ISTR C used to do this too. I think prototypes were added (and required) only by the time ANSI got involved.

Maybe some other OF can confirm/contradict?

Re:Why Fennel? by ahoka on Wednesday September 13, 2023 @02:40PM (#37500072)
Fun fact: in C, if you have a signature like “int foo()”, it means any number of arguments can be passed (vs “int foo(void)” which means no args).
Re:Why Fennel? by sovietswag on Wednesday September 13, 2023 @02:44PM (#37500133)
Good reference for that: https://jameshfisher.com/2016/11/27/c-k-and-r/

This was also used to implement variadic functions like printf before varargs, see https://retrocomputing.stackexchange.com/a/20517

Re:Why Fennel? by fulafel on Wednesday September 13, 2023 @02:47PM (#37500161)
And there is no way for the callee to check the nr of passed args, unlike in Lua.
Re:Why Fennel? by mdaniel on Wednesday September 13, 2023 @02:50PM (#37500190)
that's ... (amazing|C for ya)

  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.
Re:Why Fennel? by astrange on Wednesday September 13, 2023 @03:59PM (#37501000)
No longer true in C23, it means void now.
Re:Why Fennel? by munificent on Wednesday September 13, 2023 @02:04PM (#37499663)
Caveat: I haven't used Fennel but just inferring from what makes sense...

The 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.

Re:Why Fennel? by mpenet on Wednesday September 13, 2023 @02:32PM (#37499959)
Because it’s an extra runtime check vs a direct lua call
Re:Why Fennel? by Joker_vD on Wednesday September 13, 2023 @02:58PM (#37500291)
IIRC (and I remember it quite vaguely from some random blog post on the Internet) the Lua implementation uses some trick that greatly speeds up setting up and tearing down activation frames (and has also something to do with the ability to easily cross-call into C back to Lua again?), but it hinges on that argument-passing/result-returning semantics being "use nils for missing stuff, throw away the extras" so e.g. Python can't use it speed its implementation of function calls.

Unfortunately, I can't find that blog post so take my words with a grain of salt.

Re:Why Fennel? by throwway120385 on Wednesday September 13, 2023 @05:18PM (#37501849)
This is mostly based on my experience in the past with Lua circa 2015 when I developed a GUI using Crank Storyboard Engine and needed to write some stuff in C because the Lua equivalent code was too CPU-intensive.

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.

Re:Why Fennel? by Verdex on Wednesday September 13, 2023 @05:47PM (#37502174)
Lua is a register based VM.
Re:Why Fennel? by neutrono on Wednesday September 13, 2023 @06:35PM (#37502604)
I think he means that the C API is stack based?
Re:Why Fennel? by brennopost on Thursday September 14, 2023 @08:01AM (#37507768)
It was stack based before 5.0
Re:Why Fennel? by sullyj3 on Wednesday September 13, 2023 @06:28PM (#37502548)
It's a runtime check. The following 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_2
Re:Why Fennel? by robertlagrant on Thursday September 14, 2023 @06:14AM (#37507034)
Gotcha - thanks!
Re:Why Fennel? by oedo on Wednesday September 13, 2023 @08:12PM (#37503304)
Declaring a function with `lambda` prepends runtime nil checks for each argument to the function body (excluding optional args-- specified with a question mark, eg. `?x`-- and variable arguments, eg. `...`[1]).

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.