I want to call an overloaded Java method from Clojure, and passing in null (or nil in Clojure) has special meaning. How would I ensure that the correct Java method is called if the argument is nil?
e.g. suppose the overloaded Java methods were as follows:
public class Foo {
public String bar(String s) {
return (s == null) ? "default" : s;
}
public String bar(Integer i) {
return (i == null) ? "0" : i.toString();
}
}
I want to yield the value "default", so I want to do the following in my Clojure code:
(.bar (Foo.) (cast String nil))
I've tested this in two slightly different environments and I do not get consistent results. And it appears that casting a nil value in Clojure does not yield the expected type, e.g. --
(type (cast String nil))
; => nil
cast doesn't do what you think it just ensures the second argument is of the specified class and returns it (the 2nd argument, not the class) or throws an exception.
You need to add a type hint but you can't add it directly to nil:
=> (String. nil)
CompilerException java.lang.IllegalArgumentException: More than one matching method found: java.lang.String
Here I have an exception because nil matches too many overloads and the compiler doesn't know which to pick.
=> (let [^String cnil nil] (String. cnil))
NullPointerException java.lang.String.<init> (String.java:152)
Here I have an exception but from the constructor: the type hint allowed the compiler to select the proper overload (which overload complains about being passed a nil...).
Related
When I work in Scala, I like that I can pattern match on type and the type-checker will make use of that type:
val x : Any = "boop"
x match {
case y : String => do-something-stringy(y);
case z : Int => .... etc
}
I know in core.typed, a conditional will help the type checker to resolve the exact type. I tried to replicate this using core.match:
(ann do-something-stringy [String -> String])
(defn do-something-stringy [message]
(str "Hello " message))
;; Doesn't work
(ann do-things [Object -> String])
(defn do-things [foo]
(match [(type foo)]
[String] (do-something-stringy foo)
:else "Nope"))
This fails with an error:
Function do-something-stringy could not be applied to arguments:
Domains:
String
Arguments:
Object
Ranges:
String
with expected type:
String
Is there a way to get this to work using core.match?
Thanks!
Try dispatching on class rather than type. core.typed has no real support for type due to its weirdness around metadata.
You can hint a return type in a protocol
(defprotocol Individual
(^Integer age [this]))
and the compiler will make your methods comply:
(defrecord person []
Individual
(^String age [this] "one"))
; CompilerException java.lang.IllegalArgumentException: Mismatched return type: age, expected: java.lang.Object, had: java.lang.String, ...
But you don't have to honour the type-hint:
(defrecord person []
Individual
(age [this] "one"))
(age (new person))
; "one"
Does the type-hint have any effect?
This is a follow up to Can you specify the return type of a method in a clojure defrecord?
The return type hint goes to the protocol function age as tag. From there, the tag is used in local type inference. To observe this in action:
- (.longValue (age (new person)))
ClassCastException java.lang.String cannot be cast to java.lang.Integer
net.bendlas.lintox/eval18038 (form-init4752901931682060526.clj:1)
;; longValue is a method of Integer, so a direct cast has been inserted
If the type hint had been left off, or if you call a method not on the hinted type, the compiler inserts a (slow) call into the reflector, instead of the plain cast:
- (.otherMethod (age (new person)))
IllegalArgumentException No matching field found: otherMethod for class java.lang.String clojure.lang.Reflector.getInstanceField (Reflector.java:271)
public class Hello {
public static void test(int i) {
System.out.println("int");
}
public static void test(long l) {
System.out.println("long");
}
}
user=> (def foo 1)
#'user/foo
user=> (type foo)
java.lang.Long
user=> (Hello/test 1)
long
nil
user=> (Hello/test foo)
int
nil
user=> (Integer. 1)
1
user=> (Integer. foo)
1
user=> (Short. 1)
IllegalArgumentException No matching ctor found for class java.lang.Short clojure.lang.Reflector.invokeConstructor (Reflector.java:183)
I couldn't figure out the rules.
In short, Clojure does not have the same overloading behavior as Java. It is a different language (that runs on the JVM) with its own set of rules. So if you define a method test(short s) it will work because you have a specific matching method signature, but the Clojure compiler won't otherwise do a widening conversion to the "closest" method.
For a more complete discussion with links, see my answer to this question:
Clojure overloaded method resolution for Longs
I recently asked why interfaces and protocols could be incompletely implemented in clojure:
user=> (defprotocol P (foo [self]))
P
user=> (extend-type Long P)
nil
user=> (extends? P Long)
true
user=> (foo 1)
IllegalArgumentException No implementation of method: :foo of protocol: #'user/P found for class: java.lang.Long clojure.core/-cache-protocol-fn (core_deftype.clj:527)
and was told that this was for interop reasons and that it wouldn't be a problem in practice. Sure.
But apparently extends? really tells me nothing about the relationship between a protocol and a class: just as (extends? P C) does not imply that I can call foo on objects of class C, (not (extends? P C)) does not imply that I cannot call foo on objects of class C:
user=> (defprotocol P (foo [self]))
P
user=> (extend-type Object P (foo [self] 1))
nil
user=> (extends? P Long)
false
user=> (foo 1)
1
Now I am very confused about what information extends? is supposed to give me... satisfies?, on the other hand, handles the second case correctly, but not the first one.
When in doubt check the code :). The implementation of extends? is this:
(defn extends?
"Returns true if atype extends protocol"
{:added "1.2"}
[protocol atype]
(boolean (or (implements? protocol atype)
(get (:impls protocol) atype))))
So it just check if the atype has been extended by the protocol and it doesn't mean it has implemented all the methods of the protocol.
The :impls is a map where key is the type that extended the protocol and the value is map which has the methods implementation of the protocol for the type.
user=> (defprotocol hello (a [self]))
hello
user=> (:impls hello)
nil
user=> (extend-type String hello)
nil
user=> (:impls hello)
{java.lang.String {}}
user=> (extend-type String hello (a [self] 10))
nil
user=> (:impls hello)
{java.lang.String {:a #<user$eval613$fn__614 user$eval613$fn__614#1d978ea>}}
On the other hand to satisfies? you need to pass the object on which you want to check for protocol and not the type as in case of extends? So if you look at satisfies? code it is a bit more complex then extends as it has to check the base classes of the object being passed for being extended by protocol. But both the functions just check whether there the type (or base types) has extended the protocol or not AND they don't check if you have actually implemented the protocol methods or not.
So there's list?, seq?, vector?, map? and so on to determine what type of collection the argument is.
What's a good way of telling the difference between
a map (i.e. something that contains key-value pairs)
a collection (i.e. something that contains values)
a non collection value like a string.
Is there a better way than
#(or (seq? %) (list? %) etc)
using seq? is about as concise and clean as it gets.
clojure.contrib.core defines:
seqable?
function
Usage: (seqable? x)
Returns true if (seq x) will succeed, false otherwise.
http://clojure.github.com/clojure-contrib/core-api.html
it does what you proposed with one big or statement of
already a seq
an instance of clojure.lang.Seqable
nil
instance of Iterable
an array
a string
instance of java.util.Map
Let's not forget about sequential?:
user=> (sequential? [])
true
user=> (sequential? '())
true
user=> (sequential? {:a 1})
false
user=> (sequential? "asdf")
false
The function seq right now does only this:
(. clojure.lang.RT (seq coll))
In RT.java in the latest version of Clojure, you'll find:
static public ISeq seq(Object coll){
if(coll instanceof ASeq)
return (ASeq) coll;
else if(coll instanceof LazySeq)
return ((LazySeq) coll).seq();
else
return seqFrom(coll);
}
static ISeq seqFrom(Object coll){
if(coll instanceof Seqable)
return ((Seqable) coll).seq();
else if(coll == null)
return null;
else if(coll instanceof Iterable)
return IteratorSeq.create(((Iterable) coll).iterator());
else if(coll.getClass().isArray())
return ArraySeq.createFromObject(coll);
else if(coll instanceof CharSequence)
return StringSeq.create((CharSequence) coll);
else if(coll instanceof Map)
return seq(((Map) coll).entrySet());
else {
Class c = coll.getClass();
Class sc = c.getSuperclass();
throw new IllegalArgumentException("Don't know how to create ISeq from: " + c.getName());
}
}
An ASeq or a LazySeq is already a seq. A Seqable is something that knows how to return a seq of itself.
That leaves things like Java core classes, which should be seqable but which Clojure can't alter to add a seq method. Those are currently hard-coded into this list. I wouldn't be surprised if the implementation changed someday, maybe using protocols to extend the Java core classes instead?
All seqables implement clojure.lang.Seqable marker:
(instance? clojure.lang.Seqable x)
Clojure 1.9 provides seqable?