janet-lang / janet-lang.org

Website for janet
https://janet-lang.org
MIT License
90 stars 59 forks source link

Document `&keys` better. #205

Closed amano-kenji closed 3 months ago

amano-kenji commented 7 months ago

Until recently, I didn't recognize the example for &keys on https://janet-lang.org/docs/functions.html was using destructuring. I thought the destructured struct was the only allowed syntax for &keys.

I didn't think it was a destructured struct. I thought it was just the only syntax for &keys.

(defn test
  [&keys {:argument1 arg1 :argument2 arg2}]
  (print arg1)
  (print arg2))

For years, I didn't know something like this was possible.

(defn test
  [&keys struct]
  (pp struct))

https://janet-lang.org/docs/functions.html should mention that what follows after &keys is actually a destructured struct.

sogaiu commented 7 months ago

Thanks for elaborating. Perhaps the text could expand on things a bit more. May be a PR would be accepted toward that end.

Note though that currently the following text exists at the bottom of the destructuring page:

Destructuring works in many places in Janet, including let expressions, function parameters, and var.

amano-kenji commented 7 months ago

I didn't recognize that there was struct destructuring after &keys.... I thought it was a special syntax...

CFiggers commented 7 months ago

I thought the same thing!

I saw your original issue over in janet-lang/janet (agree that this is a better place for it). Before that I didn't realize that [&keys foo] was valid syntax.

This is a useful thing to know, because sometimes I need to pass the whole struct received after &keys to another function. But if I've destructured it in the main function's params, then there's no symbol bound to the whole struct and I have to manually reconstruct it in the arguments of the sub function:

(defn foo [&keys {:a apples :b bananas}]
  (print apples)
  (print bananas)
  (count-calories {:a apples :b bananas}))

This small example is already annoying, but it's much worse the more keys are in the struct.

That kinda turned me off from using &keys at all almost ever:

(defn foo [fruitmap]
  (print (fruitmap :a))
  (print (fruitmap :b))
  (count-calories fruitmap))

But it turns out, this is valid:

(defn foo [&keys fruitmap]
  (print (fruitmap :a))
  (print (fruitmap :b))
  (count-calories fruitmap))

So &keys might get more use in my Janet code moving forward.

What would be really nice is if I could have the best of both worlds:

(defn foo [&keys {:a apples :b bananas} :as fruitmap]
  (print apples)
  (print bananas)
  (count-calories fruitmap))

But I don't think Janet's defn/approach to destructuring in general currently supports this.

sogaiu commented 7 months ago

Yeah an :as sort of thing could be nice.

Not sure of the specifics though.

For reference, the fennel folks seem to do this sort of thing (inspired by Clojure perhaps?):

{:expr expr#
 :res result#
 :debugger-id ,(tostring debugger-id)
 :id id#
 &as data#}

Ignore the # and , stuff -- it's from within a macro definition (^^;

pepe commented 7 months ago

I see :as as Clojurism :-), as it brings another layer of magic into the restructuring. All the puns intended :-D.

CFiggers commented 7 months ago

I'm sure if there was anything really bothering me I could find a way to fix it with macros, at least to my own satisfaction. 🙂 No need to change the language, unless more people than just me would find something of that kind useful.

More on the topic of this issue, I do think it would be helpful to clarify the docs around &keys syntax.

sogaiu commented 7 months ago

Not sure there is really a nice solution, but IIUC ATM there is a trade-off one must make between:

I think you can't have your cake and eat it too...though may be this cake is not tasty enough (^^;


Back to the topic (as CFiggers mentioned), perhaps an additional example in the docs (plus some text) that demonstrates the point would be worthwhile.

amano-kenji commented 7 months ago

Haskell provides an enhanced destructuring syntax.

For example, in haskell, something like this is possible

(defn
  [arg1 &keys attrs@{:abc abc :bcd bcd}]
  (pp attrs)
  (pp abc)
  ...)
sogaiu commented 7 months ago

Regarding possible changes to the documentation, perhaps an additional example can be added to the end of the keyword-style arguments section along with some explanatory text.

Here's a first draft:

Note that what follows `&keys` does not have to be a 
struct and can instead be a single symbol.  The
symbol can then be used as a name within the function
body for a struct containing the passed keys and values:

(defn feline-counter
  [&keys animals]
  (if-let [n-furries (get animals :cat)]
    (printf "Awww, there are %d cats!" n-furries)
    (print "Shucks, where are my friends?")))

(feline-counter :ant 1 :bee 2 :cat 3)
amano-kenji commented 7 months ago

That's what I meant.

sogaiu commented 7 months ago

Thanks for clarifying.

I submitted PR #214.

sogaiu commented 3 months ago

Looks like this issue may have been address by the merging of #214. Perhaps it can be closed.