bakpakin / Fennel

Lua Lisp Language
https://fennel-lang.org
MIT License
2.44k stars 126 forks source link

Macro treat vararg as first parameter... Unless quoted and unqoted. #458

Closed Renegatto closed 1 year ago

Renegatto commented 1 year ago
(macro vararg-macro [...]
  (case ...
    [a b] b ; this branch was expected to be evaluated
    x x)) ; but this one was evaluated
(vararg-macro 1 2)
return 1

In this case ... is treated as 1 i.e. number

(macro vararg-macro [...] (type ...))
(vararg-macro 1 2)
return "number"

However, the code begins to work after quote and unqote:

(macro vararg-macro [...]
  (case `(,...)
    [a b] b ; this branch was evaluated
    x x))
(vararg-macro 1 2)
return 2
andreyorst commented 1 year ago

If you want to match on multiple values. you need to use multiple value binding syntax:

(macro vararg-macro [...]
  (case ...
    (a b) b
    x x))
(vararg-macro 1 2) ;=> 2

In case of your macro it didn't match because you didn't provide a table.

I'm curious why it matches in the second case, but I assume that `(,...) somehow was interpreted as a list, which can be matched with the sequential table.

Renegatto commented 1 year ago

@andreyorst also why ... is interpreted as 1 instead of {1 2} and (type ...) as "number" instead of "table"? It seems like a bug that we can pattern-match on number and successfully match table.

XeroOl commented 1 year ago

I think you are misunderstanding how ... works. ... evaluates as (values 1 2), not 1 nor [1 2]. (type ...) expands to (type 1 2), and since type only reads its first argument, it evaluates to number.

When you quote and unquote it, you are also wrapping it in a list, which is why you see the change in your code's behavior.

`(,...)
;; is the same as
(list ...)

You can use [] patterns to destructure lists and tables, but you need to use () patterns to match multiple values.

... works the same way outside of macros, and inherits its behavior from lua:

local function example_fn(...)
   print(...) -- => 1   2
   print(type(...)) -- => number
   local x = ...
   print(x) -- => 1

   local args = {...} -- => {1, 2}
end

example_fn(1, 2)
technomancy commented 1 year ago

This is one of the most confusing things about Fennel; unfortunately we can't do much about it since we're stuck with this from Lua.

However, there's really no reason to ever use ... in macros. You seem to be assuming it works like a table, but if what you want is a table, you can get a table using &:

(macro vararg-macro [& args]
  (case args
    [a b] b
    x x))

Perhaps we should update our documentation to prefer this style since it's so much less confusing.

technomancy commented 1 year ago

I've updated the docs to use the nicer style for rest args: https://git.sr.ht/~technomancy/fennel/commit/7d5d8fa575ca087ebfd16b2d19291df2feb811aa

I think we can close this out but feel free to ask if anything else isn't clear.