barinska
barinska

Reputation: 27

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?

Upvotes: 2

Views: 181

Answers (3)

Svante
Svante

Reputation: 51501

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

Upvotes: 3

amalloy
amalloy

Reputation: 91857

The documentation for -<>> is quite clear that it doesn't behave the way you wish it did:

"the 'diamond spear': top-level insertion of x in place of single positional '<>' symbol within the threaded form if present, otherwise mostly behave as the thread-last macro. Also works with hash literals and vectors."

It performs replacement:

  • Of a single symbol
  • At the top level of the threaded form

Your example wishing to use it for multiple symbols, nested within subforms, will thus not work.

Upvotes: 1

Alan Thompson
Alan Thompson

Reputation: 29958

You have made a mistake leaving off the <> symbol in most of your forms in the failing case. Here is a working version (using the similar it-> macro in place of the Swiss Arrows). I also cleaned up the pow a bit.

(defn pow-int [a b] (Math/round (Math/pow a b)))

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

(armstrong? 153) => true
(armstrong? 154) => false

You can find the docs on it-> here.


If you leave off the (collection) arg to a function like map, it returns a transducer; from the docs:

(map f)(map f coll)(map f c1 c2)(map f c1 c2 c3)(map f c1 c2 c3 & colls)

Returns a lazy sequence consisting of the result of applying f to
the set of first items of each coll, followed by applying f to the
set of second items in each coll, until any one of the colls is
exhausted.  Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments. Returns a transducer when
no collection is provided.

And, always refer to the Clojure CheatSheet!

Upvotes: 0

Related Questions