Calling Files/readAttributes fails with "IllegalArgumentException: No matching method" - clojure

I'd like to call java.nio.file.Files/readAttributes, but doing so always fails with No matching method. For example:
user=> (java.nio.file.Files/readAttributes (-> "/etc/passwd" clojure.java.io/file .toPath) "posix:group")
CompilerException java.lang.IllegalArgumentException: No matching method: readAttributes, compiling:(NO_SOURCE_PATH:1:1)
user=> (java.lang.System/getProperty "java.version")
"1.7.0_25"
For a sanity check, doing the equivalent works fine from the scala repl:
scala> java.nio.file.Files.readAttributes((new java.io.File("/etc/passwd")).toPath, "posix:group")
res11: java.util.Map[String,Object] = {group=root}

It turns out that to call java variadic methods, you have to explicitly pass the vararg part as an array, even if you don't care about it. (see the question Java Interop — Netty + Clojure)
The following works:
user=> (java.nio.file.Files/readAttributes (-> "/etc/passwd" clojure.java.io/file .toPath) "posix:group" (into-array java.nio.file.LinkOption []))
{"group" #<Group root>}

Related

Extend Clojure Regular Expressions with IFn to support map

I want to be able to call map on regular expressions, like so:
(map #"ab+c*" ["abbb" "ac" "abbcc"])
=> ("abbb" "abbcc")
How do I extend regular expressions to support the IFn interface? Or is there a different way to do it?
ClojureScript:
(extend-type js/RegExp
IFn
(-invoke
([match s] (re-find match s))
([match replacement s]
(clojure.string/replace s match replacement))))
Now you can call regular expressions as functions and even pass them to map:
(#"abc+" "abcccc")
=> "abcccc"
(map #"abc+" ["abcccc" "abcccccccc"])
=> ("abcccc" "abcccccccc")
Unfortunately, IFn is not a protocol in Clojure, so you cannot extend it. That's unfortunate.
Since IFn isn't a protocol in core Clojure, I don't believe that this is possible.
The closest I could get is creating a wrapper type that implements IFn:
(defrecord R [^java.util.regex.Pattern regex]
clojure.lang.IFn
(invoke [this s]
(re-find regex s))
(invoke [this replacement s]
(clojure.string/replace s regex replacement)))
(map (->R #"abc+") ["abcccc" "abcccccccc"])
=> ("abcccc" "abcccccccc")
The trouble with trying to do this is that it's not directly obvious what you're trying to do with the regular expression - Particularly when most of your production code will look like (map #"ab+" entries)
Regular expressions are about a pattern matching only, they don't directly imply what transformation you want from them, so you really should steer clear of trying to shoehorn that into it.
If it's a once-off, just use
(map #(clojure.string/replace % #"ab+c*" "ab") ["ab" "ac" "abbcc"])
=> ("ab" "ac" "ab")
(It's not immediately obvious how your example is supposed to work? You have less elements in your result - are you filtering and transforming? How are you getting to the "abbb" element?)
If you're using this a lot, I would recommend simply creating a helper function in a common namespace that you can use with map instead of trying to extend the IFn interface.. Since creating a function is, in effect, a direct way to extend from IFn, but it's a named function with very specific semantics that you can customize precisely.
As CmdrDats says, using re-find in an anonymous function is definitely the way to go:
(filter #(re-find #"ab+c*" %) ["abbb" "ac" "abbcc"])
=> ("abbb" "abbcc")
I sometimes use a helper function to emphasize that I want just true/false output (not the match nor a sequence of matches), and since I'm always forgetting the differences between the re-xxx functions:
(ns demo.core
(:require [schema.core :as s]))
(s/defn contains-match? :- s/Bool
"Returns true if the regex matches any portion of the intput string."
[search-str :- s/Str
re :- s/Any]
#?(:clj (assert (instance? java.util.regex.Pattern re)))
(boolean (re-find re search-str)))

Clojure: Unable to find static field

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

mark a function returned by builtin hight-level fn as const failed

I defined a function that shouldn't be changed, so I was thinking I could use const meta to restrict that. It works good until I use builtin high-level function that generate another function
snitch.core=> (defn gen-foo [] (fn [_] true))
#'snitch.core/gen-foo
snitch.core=> (def ^:const foo (gen-foo))
#'snitch.core/foo
snitch.core=> (foo 1)
true
snitch.core=> (def ^:const foo (every-pred even?))
#'snitch.core/foo
snitch.core=> (foo 1)
IllegalArgumentException No matching ctor found for class clojure.core$every_pred$ep1__6420 clojure.lang.Reflector.invokeConstructor (Reflector.java:163)
snitch.core=> (def ^:const foo (constantly 3))
#'snitch.core/foo
snitch.core=> (foo)
IllegalArgumentException No matching ctor found for class clojure.core$constantly$fn__4085 clojure.lang.Reflector.invokeConstructor (Reflector.java:163)
I'm confused by the error msg.
Also why function I defined could being used, but function generated by builtin function couldn't?
You misunderstood the meaning of :const metadata. In Clojure all variables are persistent and can not be changed.
:const metadata in Clojure works pretty much like inline directive in C++, telling Clojure to replace variable with its value during compilation, instead of dereferencing it on runtime.
Please, see How does Clojure ^:const work?

How do I convert clojure.lang.ArraySeq to a Java String[]?

I have a Java class with a constructor taking a variable number of String arguments like this:
public Foo(String...args);
I'm trying to create a make-foo multimethod in Clojure to handle this:
(defmethod make-foo clojure.lang.ArraySeq [& args] (new Foo (into-array args)))
But when I call it with
(make-foo ["one" "two"])
I get: IllegalArgumentException No matching ctor found
I'd also like to be able to call it with
(make-foo '("one" "two"))
I see there are to-array variants for ints, floats, etc, but no String. So how do I handle this case?
make-foo as written would work if you called it like (make-foo "one" "two"), or you could remove the & from its definition and then pass it sequences.

clojure instance? single argument

I am a little confused by the clojure instance? function. It seems quite happy to take a single argument. So
(instance? String)
works fine, but always returns false.
Am I missing something here? I've done this twice in two days, and both times it took me a quite a long time to debug (yes, I agree, to make the mistake once might be regarded as misfortune, but twice looks like carelessness).
Why doesn't it break, with an arity error?
Note added later:
As of Clojure 1.6 this has been fixed!
http://dev.clojure.org/jira/browse/CLJ-1171
Interesting... even though instance? is defined in core.clj, it appears that there is special handling built in to clojure.lang.Compiler for (instance?) forms.
Compiler.java, line 3498:
if(fexpr instanceof VarExpr && ((VarExpr)fexpr).var.equals(INSTANCE))
{
if(RT.second(form) instanceof Symbol)
{
Class c = HostExpr.maybeClass(RT.second(form),false);
if(c != null)
return new InstanceOfExpr(c, analyze(context, RT.third(form)));
}
}
I interpret that to mean that, when you compile/evaluate an (instance?) form, the function defined in core.clj is ignored in favor of the hard-wired behavior, which does interpret a missing second argument as nil. I'm guessing this is done for performance reasons, as a sort of in-lining.
Apparently this special handling only applies in certain cases (and I'm not familiar enough with the compiler to know what they are). As illustrated by Ankur's answer, there are ways of calling instance? that cause the function defined in core.clj to be invoked.
I think it is a bug. If you define a new version of instance?, e.g.
(def
^{:arglists '([^Class c x])
:doc "Evaluates x and tests if it is an instance of the class
c. Returns true or false"
:added "1.0"}
foo? (fn foo? [^Class c x] (. c (isInstance x))))
you will get the expected exception
user=> (foo? String "bar")
true
user=> (foo? String 1)
false
user=> (foo? String)
ArityException Wrong number of args (1) passed to: user$foo-QMARK- clojure.lang.AFn.throwArity (AFn.java:437)
If you look at the instance? code you will see that the method isInstance of Class is called:
(def
^{:arglists '([^Class c x])
:doc "Evaluates x and tests if it is an instance of the class
c. Returns true or false"
:added "1.0"}
instance? (fn instance? [^Class c x] (. c (isInstance x))))
Looks like under the hood, nil (or false) is considered as the default value for x parameter when passed to the isInstance and that returns false.
Hmm....interesting... all the below calls fails (which is how it is supposed to be):
user=> (.invoke instance? String)
ArityException Wrong number of args (1) passed to: core$instance-QMARK- clojure.lang.AFn.throwArity (AFn.java:437)
user=> (instance? (type ""))
ArityException Wrong number of args (1) passed to: core$instance-QMARK- clojure.lang.AFn.throwArity (AFn.java:437)
user=> (apply instance? String [])
ArityException Wrong number of args (1) passed to: core$instance-QMARK- clojure.lang.AFn.throwArity (AFn.java:437)
user=> (#'instance? Long)
ArityException Wrong number of args (1) passed to: core$instance-QMARK- clojure.lang.AFn.throwArity (AFn.java:437)
Event creating a new instance of "instance?" function object works as it is supposed to work:
user=> (def a (.newInstance (aget (.getConstructors (type instance?)) 0) (into-array [])))
#'user/a
user=> (a String)
ArityException Wrong number of args (1) passed to: core$instance-QMARK- clojure.lang.AFn.throwArity (AFn.java:437)
user=> (a String "")
true