bakpakin / Fennel

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

varargs in hashfn #209

Closed jaawerth closed 4 years ago

jaawerth commented 4 years ago

There's currently no way to refer to ... in hashfn's. Initially this made sense to me, as they're really just meant as shorthand sugar for very simple functions, but there are a few instances where it would really come in handy, as well as some powerful usecases I think we're missing out on.

A couple usecases

Function composition

While we could introduce a comp macro, we wouldn't really need it if we had this. Assuming, for the case of argument, hashfn varargs take the form of $&, left-right composition is as simple as

#(-> $& func1 func2 func3)

There's still an issue with multiple returns making this harder, but if we introduced as-> (separate issue forthcoming) or another means to concisely limit and manipulate function args, that too cleans up nicely.

Wrapping other functions

One of the primary usecases for hashfn is to wrap other functions or operators, and it obviates the need for some kind of placeholder special for doing positional partial application. This is somewhat limited by the lack of ....

What should it be?

Benaiah commented 4 years ago

Since ... isn't lexically scoped, it seems to me that another possible way forward is to allow the use of ... itself in hashfns, since any current use of that would be met with an "unexpected vararg" error.

jaawerth commented 4 years ago

That's a good point. My one concern there is that normally when ... is used, you can see on the screen where it's being bound in the args, and implicitly binding it under the hood could lead to confusion, even if it isn't actually semantically ambiguous.

Worth adding to the list, though!

technomancy commented 4 years ago

since any current use of that would be met with an "unexpected vararg" error.

I actually think the fact that this signals an error is valuable, because the lack of proper scoping makes it so easy to screw up.

I'd be inclined to use $... as I feel it communicates the intent most clearly.

jaawerth commented 4 years ago

Agreed. In regular functions, where ... came from is explicit, since you have to put it in the function signature to use it. From a readability standpoint, making an explicit version for hashfn like $... prevents ambiguity from creeping in.

jaawerth commented 4 years ago

Upon further reflection, introducing $... can lead to some hard-to-read code because contents of $... would be determined by the highest $n used, so

(local pp #(print (view $)))
(pp (#[$5 $1 $...] :a :b :c :d :e))
(pp (#[$4 $1 $...] :a :b :c :d :e))
(pp (#[$3 $1 $...] :a :b :c :d :e))
(pp (#[$2 $1 $...] :a :b :c :d :e))

would result in

["e" "a"]
["d" "a" "e"]
["c" "a" "d" "e"]
["b" "a" "c" "d" "e"]
XeroOl commented 4 years ago

@jaawerth I don't believe the contents of $... would need to be determined by the highest used $n. Maybe I didn't read carefully enough at first, but the way I initially interpreted this issue was that $... would contain all arguments passed in, not just the trailing arguments. ie

(fn generated [...]
    (local ($1 $2 $3 $4 $5 $6 $7 $8 $9) ...) 
    ,body)

I feel like this would be simpler to implement, and would resolve your hard-to-read code examples, but may not be what you were looking for initially.

jaawerth commented 4 years ago

Hmm, that could definitely work. It would be less confusing than my example, for sure. The question is whether those semantics would be confusing if we used the symbol $..., since in regular functions the length of ... gets smaller the more named arguments there are.

On the other hand, adding some other magic symbol like $& may just confuse things even more...

jaawerth commented 4 years ago

Made an initial pass yesterday in hashfn-varargs. It works, with $... and $1, $2, $3... mutually exclusive for the time being (we can always open up that restriction later and just require positional args to be contiguous); before I make a PR it just needs an update to the reference, plus a little cleanup and commenting.

I'll also see if I can further generalize walk/walkAST into something that can be used on more than just ASTs.