Search code examples
clojuremacros

Why does clojure attempt to resolve this symbol?


I am working through the Armstrong Numbers exercise on Exercism's Clojure track. An armstrong number is a number equal to the sum of its digits raised to the power of the number of digits. 153 is an Armstrong number, because: 153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153. 154 is not an Armstrong number, because: 154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190.

The test file for this exercise will call the armstrong? function, pass in a number, and expects true if the number is an Armstrong number. I have already solved the problem with this code:

(ns armstrong-numbers)

(defn pow [a b]
  (reduce * 1 (repeat b a)))

(defn armstrong? [num]
  (->> num
       (iterate #(quot % 10))
       (take-while pos?)
       (map #(mod % 10))
       ((fn [sq]
          (map #(pow % (count sq))
               sq)))
       (apply +)
       (= num)))

but now I am trying to refactor the code. This is what I would like the code to look like:

(ns armstrong-numbers
  (:require [swiss.arrows :refer :all]))

(defn pow [a b]
  (reduce * 1 (repeat b a)))

(defn armstrong? [num]
  (-<>> num
        (iterate #(quot % 10))
        (take-while pos?)
        (map #(mod % 10))
        (map #(pow % (count <>))
             <>)
        (apply +)
        (= num)))

A link to the package required above: https://github.com/rplevy/swiss-arrows.

In the first code section, I create an implicit function within the thread-last macro because the sequence returned from the map form is needed in two different places in the second map form. That implicit function works just fine, but I just wanted to make the code sleeker. But when I test the second code block, I get the following error: java.lang.RuntimeException: Unable to resolve symbol: <> in this context.

I get this error whether I use #(), partial , or fn inside the second map form. I have figured out that because all of the preceding are macros (or a special form in fns case), they cannot resolve <> because it's only meaningful to the -<>> macro, which is called at a later point in macroexpansion. But why do #(), partial, and fn attempt to resolve that character at all? As far as I can see, they have no reason to know what the symbol is, or what it's purpose is. All they need to do is return that symbol rearranged into the proper s-expressions. So why does clojure attempt to resolve this (<>) symbol?


Solution

  • The <> symbol is only valid in the topmost level of a clause (plus literals for set, map, vector directly therein). -<> and -<>> do not establish bindings (as in let) for <>, but do code insertion at macro expansion time.

    This code insertion is done only at toplevel, because making it work deeper is not only much more complex (you need a so-called code walker), but also raises interesting questions regarding the nesting of arrow forms. This complexity is likely not worth it, for such a simple effect.

    If you want a real binding, you can use as-> (from clojure.core).