26

Given the following piece of code:

(map Integer/parseInt ["1" "2" "3" "4"])

Why do I get the following exception unless I wrap Integer/parseInt in an anonymous function and call it manually (#(Integer/parseInt %))?

clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to find static field: parseInt in class java.lang.Integer
heyarne
  • 1,127
  • 9
  • 33
  • 3
    in addition to the excellent answers you have already - Clojure is a fairly thin abstraction over the jvm, and there is no such thing as a first class method, so a method can't be an argument to a Clojure function. That is, on a byte code level, a method can't be an argument. – noisesmith Feb 04 '16 at 13:13

3 Answers3

37

the documentation on java interop says the following:

The preferred idiomatic forms for accessing field or method members are given above. The instance member form works for both fields and methods. The instanceField form is preferred for fields and required if both a field and a 0-argument method of the same name exist. They all expand into calls to the dot operator (described below) at macroexpansion time. The expansions are as follows: ... (Classname/staticMethod args*) ==> (. Classname staticMethod args*) Classname/staticField ==> (. Classname staticField)

so you should remember, that Class/fieldName is just a sugar for getting a static field, neither static method call, nor reference to the static method (java method is not a clojure function really), so there is no static field parseInt in Integer class, while (Class/fieldName arg) calls a static method, they are two totally different operations, using the similar sugary syntax.

so when you do (map #(Integer/parseInt %) ["1" "2" "3" "4"]) it expands to

(map #(. Integer parseInt %) ["1" "2" "3" "4"])

(you can easily see it yourself with macroexpansion),

and (map Integer/parseInt ["1" "2" "3"]) expands to

(map (. Integer parseInt) ["1" "2" "3"])

It fails when it is trying to get a field (which you think is getting a reference to a method).

leetwinski
  • 17,408
  • 2
  • 18
  • 42
6

Integer/parseInt is a static method of Integer class, not a clojure function. Each clojure function is compiled to java class which implements clojure.lang.IFn interface. map expects clojure function (which implements IFn interface) as a first argument, however, Integer/parseInt is not.

You can check that in the clojure repl.

user=> (type map)
clojure.core$map
user=> (type Integer)
java.lang.Class
user=> (type Integer/parseInt)

CompilerException java.lang.RuntimeException: Unable to find static field: parseInt in class java.lang.Integer, compiling:(/private/var/folders/p_/psdvlp_12sdcxq07pp07p_ycs_v5qf/T/form-init4110003279275246905.clj:1:1)

user=> (defn f [] 1)
#'user/f
user=> (type f)
user$f
user=> (type #(1))
user$eval9947$fn__9948

Perhaps reading this stackoverflow question will help you understand what is going on.

Community
  • 1
  • 1
ntalbs
  • 28,700
  • 8
  • 66
  • 83
  • Thinking in terms of Clojure (rather than Java implementation), you would say that the function argument to the `map` function is the first argument. Bit confusing to hear it called the second argument. – Chris Murphy Feb 04 '16 at 12:04
  • @ChrisMurphy You're correct. It was a mistake. I updated the post. – ntalbs Feb 04 '16 at 12:14
3

You might prefer to do it without the Java interop:

(map read-string ["1" "2"])

or for a more safe variant:

(map clojure.edn/read-string ["1" "2"])

I personally prefer to minimize the use of Java as much as possible in Clojure code.

As for why you can't just pass the Java function, because map expects a function in Clojure, not a function in Java.

johnbakers
  • 24,158
  • 24
  • 130
  • 258
  • 1
    `read-string` is almost always a bad idea. documentation says: "Note that read-string can execute code (controlled by *read-eval*), and as such should be used only with trusted sources. For data structure interop use clojure.edn/read-string". try this one in your repl `(read-string "#=(spit \"./filename.sh\" \"evil code\")")` . As you almost always use data conversion for external data, `read-string` is highly unsafe here – leetwinski Feb 04 '16 at 12:28