braveandtrue

https://www.braveclojure.com/
2018-09-05T12:33:18.000100Z

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

2018-09-05T12:36:57.000100Z

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.

2018-09-05T12:39:43.000100Z

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.

2018-09-05T12:41:37.000100Z

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.

2018-09-05T12:43:37.000100Z

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

2018-09-05T12:45:51.000100Z

Let’s look at your code.

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

2018-09-05T12:48:15.000100Z

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.

2018-09-05T12:53:02.000100Z

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.

2018-09-05T12:55:13.000100Z

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

2018-09-05T12:59:15.000100Z

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.

1👍1😁
moo 2018-09-05T13:59:22.000100Z

@manutter51 Thanks that helps.

moo 2018-09-05T13:59:38.000200Z

Can you tell what’s up with my macro?

moo 2018-09-05T13:59:57.000100Z

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

moo 2018-09-05T14:00:19.000100Z

As in, this works:

moo 2018-09-05T14:00:30.000100Z

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

2018-09-05T14:06:15.000100Z

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

2018-09-05T14:06:50.000100Z

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

2018-09-05T14:07:57.000100Z

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

2018-09-05T14:09:14.000100Z

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

2018-09-05T14:14:13.000100Z

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

2018-09-05T14:17:56.000100Z

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

moo 2018-09-05T14:26:24.000200Z

🙂

moo 2018-09-05T14:26:57.000100Z

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

2018-09-05T14:27:53.000100Z

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

1
moo 2018-09-05T14:29:05.000100Z

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

moo 2018-09-05T14:29:41.000200Z

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)

2018-09-05T14:32:41.000100Z

Ok, I didn’t know you could execute multiple def or defns in a single macro, but looks like that’s the point of the exercise.

moo 2018-09-05T14:33:32.000100Z

I tried it manually with do

2018-09-05T14:33:55.000100Z

I’m going to assume that will work 🙂

2018-09-05T14:35:16.000100Z

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

moo 2018-09-05T14:35:42.000100Z

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

2018-09-05T14:36:18.000100Z

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

moo 2018-09-05T14:37:32.000100Z

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

2018-09-05T14:38:32.000300Z

Oh, there’s a backtick missing in there

2018-09-05T14:38:43.000100Z

before the (do

moo 2018-09-05T14:38:49.000100Z

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

moo 2018-09-05T14:39:15.000100Z

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

moo 2018-09-05T14:39:19.000100Z

(with that backtick)

2018-09-05T14:40:59.000100Z

You need the tildes too

2018-09-05T14:41:10.000100Z

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

1👍
moo 2018-09-05T14:43:12.000100Z

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

moo 2018-09-05T14:44:06.000100Z

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

2018-09-05T14:44:08.000100Z

It’s possible the destructuring was just confusing things

2018-09-05T14:44:23.000100Z

Correct, I got rid of the destructuring to simplify things

moo 2018-09-05T14:44:55.000100Z

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

2018-09-05T14:45:21.000200Z

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

moo 2018-09-05T14:45:32.000100Z

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

moo 2018-09-05T14:46:15.000100Z

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

2018-09-05T14:46:43.000100Z

right

2018-09-05T14:47:31.000200Z

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

moo 2018-09-05T15:45:57.000100Z

isn’t that 3-arity?

moo 2018-09-05T15:46:11.000100Z

p1, p2, p3 and p3=[]?

moo 2018-09-05T15:46:35.000100Z

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

moo 2018-09-05T15:46:44.000100Z

(I’m asking to understand better… ) 🙂

2018-09-05T16:35:54.000100Z

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

2018-09-05T16:36:55.000100Z

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

2018-09-05T16:40:02.000100Z

Best way to handle that is avoid the whole problem

2018-09-05T16:41:48.000100Z

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

2018-09-05T17:11:01.000100Z

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.

2018-09-05T17:11:32.000100Z

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