Hi @jm.moreau sorry I didn’t get to this sooner, but I was travelling yesterday
Maybe it would help to think of destructuring as a shortcut for assigning values to names. So take a simple function like this:
(defn foo [a b]
:do-something)
That’s obviously an arity-2 function, and you can call it with (foo :bar :baz)
, and inside the function body, a
will have the value :bar
and b
will have the value :baz
.The first gotcha is that lists and vectors and maps and sets are all values too. That is to say, [1 2 3]
is a value, singular. It’s a vector with 3 other values inside of it, but the vector itself is a value, singular. That means if you define a function that takes a vector as its argument, you’ll be writing an arity-1 function, no matter how many values are inside the vector.
So as far as arity is concerned, a vector counts as 1 argument. Arity does not care how many other values are inside the vector, it only cares that it’s a value.
Destructuring, on the other hand, says “Ok, arity doesn’t care what’s inside the vector, but I do. I want to pull out the individual values inside the vector, and give the first value the name fnname
and the second value the name attribute
, and if there’s any other values inside the vector, just stuff them inside remaining
.”
Let’s look at your code.
(defn mytest [[fnname attribute & remaining] v] "nothing")
(:arglists (meta #'mytest))
;=> ([[fnname attribute & remaining] v])
You’re defining mytest
as an arity-2 function. Let’s look at arity checking first. If you call (mytest 1 2)
it will throw an error, but it won’t be an arity problem. As far as arity is concerned, you said “`mytest` takes 2 arguments” and you gave it 2 arguments, so your arity is good. The error you’ll get is when it tries to do the destructuring.
Your function definition says the first argument needs to be a vector, but 1
obviously is not a vector. Well, technically, it just has to be a seq — any collection whose items can be ordered like “first, second, third, etc.” So if you call (mytest 1 2)
you’ll get an Illegal Argument error complaining that it can’t create an ISeq from a Long.
And of course, v
, your second argument, can be pretty much any value, since you’re not trying to destructure it.
So to summarize:
1. "Arity" means the number of values you pass to a function.
2. A value can be something simple like `1`, `:foo`, or `"thing"`, or it can be something that contains other values like `[]`, `'()`, or `{}` (or anonymous functions).
3. Arity _never_ looks inside values like `[]` that can contain other values--it only counts the "top-level" value.
4. Destructuring _does_ look inside values that contain other values, as a shortcut to make it easier to assign the inner values their own names.
@manutter51 Thanks that helps.
Can you tell what’s up with my macro?
2-arity version works. So, it’s the recursion that’s giving me trouble again
As in, this works:
(defmacro create-attrs-fn
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))
It's possible that you don't want the 1-arity/destructuring version, you just want a straight n-arity macro
(defmacro create-attrs-fn
([fnname attribute & remaining]
(do (create-attrs-fn fnname attribute)
(create-attrs-fn ~@remaining)))
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))
Oh wait, I see how you're using destructuring there
I'm not sure, though, that what you're trying to do will actually work the way you intend.
do you have an example of how you call that macro?
Also, are you supposed to be creating a macro for this? What’s the problem you’re solving here?
🙂
I’m working on ex. 3 in the macro chapter. https://www.braveclojure.com/writing-macros/
Ok, let me check that out so I’m up to speed
I’m happy that this works
(defmacro defattr ; singular so not defattrs
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))
(macroexpand '(defattr c-int :intelligence))
; => (def c-int (clojure.core/comp :intelligence :attributes))
so, that’s basically my starting point to write a recursive version defattrs
that could be called like so:
(defattrs c-int :intelligence
c-str :strength
c-dex :dexterity)
Ok, I didn’t know you could execute multiple def
or defn
s in a single macro, but looks like that’s the point of the exercise.
I tried it manually with do
I’m going to assume that will work 🙂
I don’t do a lot with macros, so I’ll have to figure this out as I go
This works:
(do
(def c-int2 (clojure.core/comp :intelligence :attributes))
(def c-str2 (clojure.core/comp :strength :attributes)) )
So I just need to create a macro that can make thatI think you’re better off using the ~@
expansion so you don’t need the destructuring, like I had in my code example up above
(defmacro create-attrs-fn
([fnname attribute & remaining]
(do (create-attrs-fn fnname attribute)
(create-attrs-fn ~@remaining)))
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))
; => CompilerException clojure.lang.ArityException: Wrong number of args (1) passed to: user/create-attrs-fn, compiling:(/Users/moo/Sync/braveandtrue/Ch08_macros.clj:4:8)
Oh, there’s a backtick missing in there
before the (do
I suppose, I could tackle a simplier problem like this, given (x1 y1 x2 y2, … x_k y_k) how do I write a function that outputs (do (str x1 y1) ... )
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Symbol clojure.lang.RT.seqFrom (RT.java:542)
(with that backtick)
You need the tildes too
(defmacro create-attrs-fn
([fnname attribute & remaining]
`(do (create-attrs-fn ~fnname ~attribute)
(create-attrs-fn ~@remaining)))
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))
oh, that works. So what was my problem? Does that mean that remaining
was vector of 1-arity and ~@
exploded it?
also, you’re not doing any destructuring so we have a 2-arity and and n-arity version. Is that correct?
It’s possible the destructuring was just confusing things
Correct, I got rid of the destructuring to simplify things
awesome. So, my approach wasn’t too far off. Yay. Thanks for helping me with that.
Yeah, you were on the right track, and I tested your code (with the tildes added) and it looks like it’s doing what you want, so yay
How does one refer to something like [fnname attribute & remaining] in terms of arity. It’s like >= 3 arity?
b/c the & something makes it n-arity, but the first two args are required right?
right
It would actually be >= 2 arity, because you don’t have to have a value for anything after the & (in which case it just sets remaining
to []
)
isn’t that 3-arity?
p1, p2, p3 and p3=[]?
because it seems you can’t have two overloads with the same airty
(I’m asking to understand better… ) 🙂
Hmm, you know what, yeah, that's a problem
If you call (create-attrs-fn foo :foo)
, should it call the 2 arity version or the version with & remaining
? Both are equally valid
Best way to handle that is avoid the whole problem
(defmacro create-attrs-fn
([fnname attribute & remaining]
(if (seq remaining)
`(do (create-attrs-fn ~fnname ~attribute)
(create-attrs-fn ~@remaining))
`(def ~fnname (comp ~attribute :attributes)))))
To answer your question, (defn foo [a b & c] ...)
counts as a 2-arity, since c
is optional. I’m not 100% sure, but I think if you define
(defn foo
([a b] (prn {:a a :b b]))
([a b & c]
(do
(foo a b)
(apply foo c))))
you could get stuck in an infinite loop where (do (foo a b)...
calls back to the & c
version.But even if it doesn’t, you should still never have [a b]
and [a b & c]
as different arities of the same fn.