Reputation: 27
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 fn
s 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
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
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:
Your example wishing to use it for multiple symbols, nested within subforms, will thus not work.
Upvotes: 1
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