Fork me on GitHub
#braveandtrue
<
2018-09-05
>
manutter5112:09:18

Hi @jm.moreau sorry I didn’t get to this sooner, but I was travelling yesterday

manutter5112:09:57

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

manutter5112:09:43

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.

manutter5112:09:37

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.

manutter5112:09:37

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`.”

manutter5112:09:51

``````(defn mytest [[fnname attribute & remaining] v] "nothing")
(:arglists (meta #'mytest))
;=> ([[fnname attribute & remaining] v])``````

manutter5112:09:15

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.

manutter5112:09:02

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.

manutter5112:09:13

And of course, `v`, your second argument, can be pretty much any value, since you’re not trying to destructure it.

manutter5112:09:15

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

👍 4
😁 4
moo13:09:22

@manutter51 Thanks that helps.

moo13:09:38

Can you tell what’s up with my macro?

moo13:09:57

2-arity version works. So, it’s the recursion that’s giving me trouble again

moo14:09:19

As in, this works:

moo14:09:30

``````(defmacro create-attrs-fn
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))``````

manutter5114:09:15

It's possible that you don't want the 1-arity/destructuring version, you just want a straight n-arity macro

manutter5114:09:50

``````(defmacro create-attrs-fn
([fnname attribute & remaining]
(do (create-attrs-fn fnname attribute)
(create-attrs-fn ~@remaining)))
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))``````

manutter5114:09:57

Oh wait, I see how you're using destructuring there

manutter5114:09:14

I'm not sure, though, that what you're trying to do will actually work the way you intend.

manutter5114:09:13

do you have an example of how you call that macro?

manutter5114:09:56

Also, are you supposed to be creating a macro for this? What’s the problem you’re solving here?

moo14:09:57

I’m working on ex. 3 in the macro chapter. https://www.braveclojure.com/writing-macros/

manutter5114:09:53

Ok, let me check that out so I’m up to speed

4
moo14:09:05

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))
``````

moo14:09:41

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)
``````

manutter5114:09:41

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.

moo14:09:32

I tried it manually with `do`

manutter5114:09:55

I’m going to assume that will work 🙂

manutter5114:09:16

I don’t do a lot with macros, so I’ll have to figure this out as I go

moo14:09:42

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 that

manutter5114:09:18

I think you’re better off using the `~@` expansion so you don’t need the destructuring, like I had in my code example up above

moo14:09:32

``````(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) ``````

manutter5114:09:32

Oh, there’s a backtick missing in there

manutter5114:09:43

before the `(do`

moo14:09:49

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) ... )`

moo14:09:15

``IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Symbol  clojure.lang.RT.seqFrom (RT.java:542)``

moo14:09:19

(with that backtick)

manutter5114:09:59

You need the tildes too

manutter5114:09:10

``````(defmacro create-attrs-fn
([fnname attribute & remaining]
`(do (create-attrs-fn ~fnname ~attribute)
(create-attrs-fn ~@remaining)))
([fnname attribute]
`(def ~fnname (comp ~attribute :attributes))))``````

👍 4
moo14:09:12

oh, that works. So what was my problem? Does that mean that `remaining` was vector of 1-arity and `~@` exploded it?

moo14:09:06

also, you’re not doing any destructuring so we have a 2-arity and and n-arity version. Is that correct?

manutter5114:09:08

It’s possible the destructuring was just confusing things

manutter5114:09:23

Correct, I got rid of the destructuring to simplify things

moo14:09:55

awesome. So, my approach wasn’t too far off. Yay. Thanks for helping me with that.

manutter5114:09:21

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

moo14:09:32

How does one refer to something like [fnname attribute & remaining] in terms of arity. It’s like >= 3 arity?

moo14:09:15

b/c the & something makes it n-arity, but the first two args are required right?

manutter5114:09:31

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 `[]`)

moo15:09:57

isn’t that 3-arity?

moo15:09:11

p1, p2, p3 and p3=[]?

moo15:09:35

because it seems you can’t have two overloads with the same airty

moo15:09:44

(I’m asking to understand better… ) 🙂

manutter5116:09:54

Hmm, you know what, yeah, that's a problem

manutter5116:09:55

If you call `(create-attrs-fn foo :foo)`, should it call the 2 arity version or the version with `& remaining`? Both are equally valid

manutter5116:09:02

Best way to handle that is avoid the whole problem

manutter5116:09:48

``````(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)))))``````

manutter5117:09:01

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.

manutter5117:09:32

But even if it doesn’t, you should still never have `[a b]` and `[a b & c]` as different arities of the same fn.