2

I have this function which creates a vector of symbols:

(defn makevars [n] 
"Gives a list of 'n' unique symbols" 
(let [vc (repeatedly n #(gensym ))] vc)) 

This is the problematic macro:

(defmacro make-strings [syms]
;eg given 2 strings ["abc" "ab"] => ("aa" "ab" "ba" "bb" "ca" "cb")  
(let [nl (count syms) vs (makevars nl) forargs (vec (interleave vs syms))]  
`(for ~forargs (str ~@vs))))

When the macro executes with (make-strings ["abc" "ab"]) the desired output occurs. However if the macro is run as (make-strings sy), where sy is defined as ["abc" "ab"] this error occurs:

UnsupportedOperationException count not supported on this type: Symbol  clojure.lang.RT.countFrom (RT.java:545)

What have I done incorrectly and how can it be fixed? Being new to macro lore I expect something is amiss with my understanding.

2
  • You need a function not a macro. OR use eval : (eval `(make-strings ~sy)) Commented Jan 21, 2013 at 9:01
  • Now the difference between passing hard data and a var in macro args makes sense. Thanks. Going the macro root wasn't my first choice, but it gave me some success, but very limited. Commented Jan 21, 2013 at 11:01

3 Answers 3

4

To start off, I would strongly recommend you to drop macros here, and implement the logic you want to achieve using plain functions: macros are only useful when you want to create some handy custom syntax, and you only want to calculate the cartesian product of two sets of characters. The easiest way would be by using the contrib lib math.combinatorics:

user=> (def sy ["abc" "ab"])
user=> (map #(apply str %) (apply cartesian-product sy))
("aa" "ab" "ba" "bb" "ca" "cb")

That said, the problem you experience is that macros are executed at compile time, and thus receive the unevaluated forms you pass as arguments. This means that when you call:

(make-strings sy)

Instead of passing the vector referred to by sy, the very symbol sy is given to the make-strings macro, so that the invocation

(count sy)

will then fire the above mentioned exception. As macros can't evaluate their inputs because of their compile time nature, you can't ever get the value behind a symbol from within the macro: all operations on such symbols must be done in the returned form of the macro itself, which is then executed at run time.

The above makes your strategy to craft the for bindings not feasible when you want to be able to pass either a literal or a symbol.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for this idea. I had considered cartesian-product but rejected it, stupidly now, because I assumed it would deal only with 2 strings only, rather than any # using 'apply'. I will remember to test the built-in functions more. I am most impressed with clojure. I also wrote this function in Java, approx 18 lines.
I'm writing clojure since more than three years now, and I continuously (re)discover fn's in the standard or contrib libs that help improving my code. I'd get used to the feeling if I was in your shoes ;-)
3

I think skuro gives a good answer of what goes wrong and why, and also suggests a very useful library which you can use to solve your original goal. "So why am I reading this?" I hear you ask. I just thought it would fun to share how you could generalize applying functions to the cartesian products of things with only a few lines of code--without tresspassing into scary macro-land.

When I say generalize, I mean that the goal would be to be able to do something like:

user> (permute str ["abc" "ab"])
=> ("aa" "ab" "ba" "bb" "ca" "cb")

The nice thing, if we develop this so-far-non-existent permute function, is that we could use the same function to do something like:

user> (permute + [[1 2 3] [10 20 30]])
=> (11 21 31 12 22 32 13 23 33)

This is a toy example, but hopefully conveys the flexibility you would gain from generalizing this way.

Well, here's a very concise way I came up with:

(defn permute [f [coll & remaining]]
  (if (nil? remaining)
    (map f coll)
    (mapcat #(permute (partial f %) remaining) coll)))

The core idea I started with was iterating with map or mapcat for as many iterations as there are different strings to combine. I started with your example, and wrote a "verbose" non-general solution:

user> (mapcat (fn [i] (map (partial str i) "ab")) "abc")
=> ("aa" "ab" "ba" "bb" "ca" "cb")

This mapcats a function down "abc". Specifically, it mapcats a function down "abc" that maps string-ing together the single element it is using from "abc" at this time (i), with each element from "ab".

In order for me to understand how to generalize this into a function, I had to go one "level deeper", and try it with a third string.

user> (mapcat (fn [i] (mapcat (fn [j] (map (partial str i j) "def")) "ab")) "abc")
=> ("aad" "aae" "aaf" "abd" "abe" "abf" "bad" "bae" "baf" "bbd" "bbe" "bbf" "cad" "cae" "caf" "cbd" "cbe" "cbf")

This mapcats a function that mapcats a function that maps string-ing together elements in a combinatorial way. Phew. Now I started to see how I could generalize. The innermost expresion would always be maping some sort of partial str function down the final string in the list of strings to re-combine. The outer expressions are just mapcats with the successively more front-ward strings, to the point where the outermost is using the first string in the list of strings to re-combine.

I guess from here I noticed that I needn't define the entire partial str function at once, but rather I could "build it up" as I recursively called permute.

Hopefully I've now given enough context to explain how the function works. The last iteration of permute happens when there are no remaining colls (i.e. (nil? remaining) returns true). It simply maps whatever function it is given down the last coll.

When there are remaining colls, it mapcats a permute variant down the current coll. This permute variant is using a partial function of f with the anonymous argument, and is permuteing down the remaining colls. By doing this, it will incrementally build up a partial function which will ultimately be invoked once it reaches the end of the list of colls. Then, in my head, I imagine it tracing backwards, calling nested mapcats until it finally unravels with the resultant re-combined colls.

I imagine this function, although concise, is probably not optimal. Frankly, I don't have much of a CS background, but from what I've picked up reading about Clojure, using loop/recur instead of self-recursive calls tends to be much more "efficient". I imagine it would be fairly straightforward to re-work the function to use loop/recur if optimization were important to you.

1 Comment

Very good idea. Over the course of the next many months I will likely need further similar functions so generalization makes good sense assuming my clojure skills improve.
1

Macro parameters aren't evaluated. When you give ["abc" "ab"] as parameter to your macro it gets it as vector of strings because strings are self-evaluating objects. But when you send sy macro gets just a symbol 'sy. That's why error occurs.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.