small medium large xlarge

06 Jun 2016, 23:06
James Elliott (2 posts)

I may be missing something here, but does the type hint ^clojure.lang.LazySeq for range in threading_examples.clj on page 69 of physical book 1.0 actually do anything? Looking at the source code of the -> macro on the preceding page, it is only when form is a seq that with-meta is used to preserve metadata, so in this case where form is just the symbol range, does it get discarded? Or is it somehow used when constructing the list?

I could not see anything when calling (meta ...) on the results of macro-expand-1.

(Thanks, by the way, for a terrific book which covers all the details I was looking for in one place, at precisely the right depth and pace!)

07 Jun 2016, 00:19
Colin Jones (3 posts)

Yes, it does help out and prevent reflection. You can (set! *warn-on-reflection* true) and see for yourself. Here’s why:

user=> (def expr (macroexpand-1 '(-> 10
    ^clojure.lang.LazySeq range
    (doto .next .next)
user=> expr
(.next (doto (.iterator (range 10)) .next .next))
user=> (first (last (second (last expr)))) ;; I did NOT get this right the first time :)
user=> (meta (first (last (second (last expr)))))
{:tag clojure.lang.LazySeq}

It’s the range symbol itself that has the metadata attached, which happens at read time, before macroexpansion. -> does special work to reattach metadata only because it’s constructing its own new lists, and those lists might need to have metadata attached to them. In general, the idea of macros losing metadata usually hits us when we’re constructing new expressions based on old ones.

The type hint here doesn’t turn out to have much to do with macros, since we have :

user=> (.iterator (^clojure.lang.LazySeq range 10))
#<SeqIterator clojure.lang.SeqIterator@68c87fc3>
user=> (.iterator (range 10))
Reflection warning, NO_SOURCE_PATH:19:1 - reference to field iterator can't be resolved.
#<SeqIterator clojure.lang.SeqIterator@4682eba5>

This means it’s a terrible example for showing how metadata gets preserved (which means it’s a bug in the book text), but the code itself is right in this case.

It gets worse, though! Clojure 1.7 and 1.8 both cause this example to fail with ClassCastException clojure.lang.LongRange cannot be cast to clojure.lang.LazySeq :(

Thanks so much for the issue report, and also for the kinds words about the book. We’ll keep this issue in mind for future updates.

14 Jun 2016, 17:58
James Elliott (2 posts)

Thanks for the tremendously detailed and well-researched explanation! There is so much to learn everywhere I look in this community.

You must be logged in to comment