I'm trying the following
(def myMap (HashMap.))
(doto (myMap) (.put "a" 1) (.put "b" 2))
I get as a result:
Reflection warning, core.clj:20:3 - call to method put can't be resolved (target class is unknown).
Reflection warning, core.clj:20:3 - call to method put can't be resolved (target class is unknown).
Am I doing anything wrong?
You need to remove the brackets around myMap:
(doto myMap (.put "a" 1) (.put "b" 2))
(myMap) will attempt to call myMap as a function.
Not really. The compiler is just warning you that its emitting code that uses reflection. You can type hint Clojure to get rid of this:
(def ^HashMap myMap (HashMap.))
(doto myMap (.put "a" 1) (.put "b" 2))
Update: Ha, I didn't read the error message very well :) However, as soon as you remove the parens from (myMap) you'll get an actual reflection warning (assuming (set! *warn-on-reflection* true)) which can be resolved by my example above.
Related
I find a macro .?. on the website https://clojure.github.io/clojure-contrib/core-api.html. But I can't use it. I tried the example,
(.?. "foo" .toUpperCase (.substring 1))
the clojure repl returns an error:
user=> (.?. "foo" .toUpperCase (.substring 1))
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: .toUpperCase in this context
I notice the namespace of .?. is deprecated. Can't we use this macro anymore?
It has been moved to core.incubator, like #phipsgabler said.
You should use some-> from clojure.core.
(some-> "foo" (.toUpperCase) (.substring 1))
It has been moved to a separate module core.incubator.
With Clojure 1.9-beta2, the reader and writer now support a compact syntax for maps. The syntax avoids repeating the namespace in the case where all the keys are qualified keywords with the same namespace:
> (pr-str {:a/foo 1 :a/bar 2})
"#:a{:foo 1, :bar 2}"
That causes problem when sending such a serialized map to a Clojure 1.8 process: the old reader running there will fail to read it and throw a java.lang.RuntimeException: Reader tag must be a symbol.
Luckily, the printer only does this when the dynamic variable *print-namespace-maps* is truthy, and it's falsey by default, so my app continues to work in production. However, the REPL sets it to true, so when I work in the REPL and do something that ends up sending a request to a Clojure 1.8 service, it fails. How can I disable the new syntax in the REPL also?
I thought that maybe I could just (set! *print-namespace-maps* false) in my repl or add {:user {:repl-options {:init (set! *print-namespace-maps* false)}}} to my ~/.lein/profiles.clj, but that doesn't seem to work. I think the reason may be that the REPL uses binding to create thread-local bindings for a bunch of variables including this one, and set! does not work for local variable bindings.
You can redefine print-method for maps, which should work regardless of environment.
(defmethod print-method clojure.lang.IPersistentMap [m, ^java.io.Writer w]
(#'clojure.core/print-meta m w)
(#'clojure.core/print-map m #'clojure.core/pr-on w))
Using (set! *print-namespace-maps* false) works for me. (But this is some 5 years later.)
This also works, and can sometimes be more desirable:
(binding [*print-namespace-maps* false]
(pr-str {:a/foo 1 :a/bar 2}))
=> "{:a/foo 1, :a/bar 2}"
I'm relatively new to Clojure and going through the Clojure chapter in Seven Languages in Seven Weeks, and I can't figure out why this code from the book isn't working for me. I'm using Leiningen and Clojure version 1.5.1. As far as I can tell after careful checking, I typed the code exactly as it reads in the book.
Here is the code:
(ns seven-languages.compass)
(defprotocol Compass
(direction [c])
(left [c])
(right [c]))
(def directions [:north :east :south :west])
(defn turn [base amount]
(rem (+ base amount) (count directions)))
(defrecord SimpleCompass [bearing]
Compass
(direction [_] (directions bearing))
(left [_] (SimpleCompass. (turn bearing 3)))
(right [_] (SimpleCompass. (turn bearing 1)))
Object
(toString [this] (str "[" (direction this) "]")))
I'm running "lein repl" from within the directory ~/clojure/seven-languages (created by running "lein new seven-languages" in ~/clojure). Relative to this directory, my .clj files are in src/seven_languages. So far I've been able to successfully import and use them from the repl by typing (use 'seven-languages.filenamehere).
So, after saving the code I listed above as src/seven_languages/compass.clj, I run this from the REPL:
user=> (use 'seven-languages.compass)
nil
But then when I try to define an "instance" of the SimpleCompass, typing it exactly like in the book, this happens:
user=> (def c (SimpleCompass. 0))
CompilerException java.lang.IllegalArgumentException: Unable to resolve classname: SimpleCompass, compiling:(NO_SOURCE_PATH:1:8)
I also tried loading the file using (load-file "src/seven_languages/compass.clj"), but got the same results. Since the actual loading seemed to work as expected, I wonder if there has been some change in how defprotocol or defrecord works in versions of Clojure subsequent to when Seven Languages in Seven Weeks was written. In the introduction to the Clojure chapter, the author writes, "I’m using a prerelease version of Clojure 1.2, and it should be fully ready by the time this book is in your hands."
Can anyone tell why this code isn't working properly? If it's a version issue, how would you update this code for Clojure 1.5.1?
EDIT: Aha! I figured it out after finding this:
Clojure - deftype ignored - unable to resolve classname on tests
It's a namespace issue. I'm guessing this is a change since version 1.2 when 7LI7W was written. For whatever reason, while functions in imported files are "automatically handled" so that you can just use them directly, types are not automatically handled. You have to include the full path to the type, and make sure you use the actual path with underscores, not hyphens. I got my code to work by substituting SimpleCompass with the full path, seven_languages.compass.SimpleCompass:
user=> (def c (seven_languages.compass.SimpleCompass. 0))
#'user/c
user=> c
#seven_languages.compass.SimpleCompass{:bearing 0}
user=> (left c)
#seven_languages.compass.SimpleCompass{:bearing 3}
user=> (right c)
#seven_languages.compass.SimpleCompass{:bearing 1}
Apart from always fully qualifying the class name, you can import it and use the short name afterwards:
(import seven_languages.compass.SimpleCompass)
;; (SimpleCompass. 0) etc. will work now
Also, it's worth pointing out that defrecord creates to factory functions for you, one positional and one taking a map:
(defrecord Foo [x])
(->Foo 1)
;= #user.Foo{:x 1}
(map->Foo {:x 1})
;= #user.Foo{:x 1}
These are just regular functions and so will have been pulled in by your use call.
Relatedly, deftype, as of Clojure 1.5.1, creates the positional factory only.
Although I can get turn a simple js object into a clojure object with something like;
(-> "{a: 2, b: 3}" js* js->clj)
I'm apparently not being able to do so with a particular object, goog.events.BrowserEvent, in a handler function like:
(defn handle-click [e]
...
(-> e .-evt js->clj keys) ;; <-------------
...
The function does get applied, but the resulting object doesn't respond to sequence functions like countor first, although I can fetch items using aget. The error message I get, in chrome's console, is;
Uncaught Error: No protocol
method ISeqable.-seq defined for type object: [object Object]
Why is this happening? Shouldn't js->clj work with all objects?
How can I fix this?
Thanks!
The js->clj only changes something that is exactly a JavaScript object (it is implemented using instance? instead of isa?, and with good reasons), when you pass a descendant of js\Object js->clj returns the same object. aget (and aset) works because it compiles down to the object[field-name] syntax on JavaScript.
You can extend the ISeq protocol (or any other protocol) to the goog.events.BrowserEvent and all functions that works with ISeq will work with goog.events.BrowserEvent. There is a talk by Chris Houser where he showed how to extend a bunch of protocols to a goog Map. I recommend watching the whole talk, but the part that are relevant to your question begins at approximately 14 minutes.
First, I found out functions in google closure to get the keys and values of an object:
(defn goog-hash-map [object]
(zipmap (goog.object/getKeys object) (goog.object/getValues object)))
Then, by studying the source of cljs.core, I realized all I had to do was to extend the IEncodeClojure interface with it:
(extend-protocol IEncodeClojure
goog.events.BrowserEvent
(-js->clj
([x {:keys [keywordize-keys] :as options}]
(let [keyfn (if keywordize-keys keyword str)]
(zipmap (map keyfn (gobj/getKeys x)) (gobj/getValues x))))
([x] (-js->cljs x {:keywordize-keys false}))))
The original code doesn't work on this object, because its type must be exactly Object. I tried to change the comparison function to instance?, ie,
(instance? x js/Object) (into {} (for [k (js-keys x)]
[(keyfn k) (thisfn (aget x k))]))
but that didn't work either, wielding the following error, which made me settle for the previous approach.
Uncaught TypeError: Expecting a function in instanceof check,
but got function Object() { [native code] }`.
(ns scratch.fastflip
(:gen-class
:extends java.util.Random
:implements clojure.lang.IFn))
(defn -invoke [^java.util.Random this]
(.next this 1))
Loading the file I get the warning:
;scratch.coin=> Reflection warning, /home/user/scratch/src/scratch/fastflip.clj:8 - call to next can't be resolved.
#'scratch.fastflip/-invoke
Note I want to get rid of the warning via eliminating the reflection, not via setting warning mechanism to false.
If you're on 1.3, this is probably because next takes an int, not a long, and 1 is an int. But are you sure next is what you want to call? If you're doing coin-flipping, I would just use nextInt(2), as next looks like implementation internals.
Edit: Here's syntax you can use to do what you want without a reflection warning.
(ns test-genclass.core
(:gen-class
:extends java.util.Random
:implements [clojure.lang.IFn]
:exposes-methods {next inner}))
(set! *warn-on-reflection* true)
(defn -invoke [^test_genclass.core this]
(.inner this 1))
(defn -main [& args]
((test_genclass.core.)))