What is the use of the third argument on extend-protocol ? Suppose I have
(defprotocol my-protocol
(foo [x]))
(extend-protocol my-protocol
java.lang.String ; this
(foo [x] (.length x)))
(foo "fooooo") ; 6
Transformed to:
(defprotocol my-protocol
(foo [x]))
(extend-protocol my-protocol
java.lang.Long ; this
(foo [x] (.length x)))
(foo "fooooo") ; it gave an output 6, while I expect it will throws, since I'm extending from Long, where it doesn't have length()
; In the end, it will just check on the args ? (in this case it's x)
There I gave it java.lang.String, if I change it to java.lang.Long, call on foo does not throw any exception, while Long doesn't have length() on it. The only case where it throws is when the argument to foo is doesn't have length().
If you change String to Long, then you will get an exception when calling foo with a long:
;; calling foo with a Long:
user=> (foo 1)
IllegalArgumentException No matching field found: length for class java.lang.Long clojure.lang.Reflector.getInstanceField (Reflector.java:271)
And if you don't extend your protocol to String, you won't be able to call foo on strings:
;; calling foo with a String with no implementation provided:
user=> (foo "asdf")
IllegalArgumentException No implementation of method: :foo of protocol: #'user/my-protocol found for class: java.lang.String clojure.core/-cache-protocol-fn (core_deftype.clj:537)
Of course if you extend my-protocol to String first and then, in a separate extend-protocol form, extend it again to Long, then foo will work fine with strings.
Clojure makes no attempt to determine statically whether (.length x) makes sense given the type you're extending your protocol too. I suppose it could in this instance, because String is a final class, but not in the general case (non-final class / interface) -- or at least not without changing its dynamic semantics.
Related
I have a small function used for debugging:
(set! *warn-on-reflection* true)
(defn debug [x] (doto x (->> println :>)))
When I call my function in a loop, I get the following reflection warning:
(loop [i 5] (when (pos? i) (recur (debug (dec i)))))
form-init14269737395101875093.clj:1 recur arg for primitive local: i is not matching primitive, had: Object, needed: long
Auto-boxing loop arg: i
I want to solve the reflection warning. How can I make my function "inherit" the type information from the parameter without explicitly specifying it or replacing it with a macro?
Here is a way that works:
(loop [i (Integer. 5)]
(when (pos? i)
(recur (debug (dec i)))))
with a warning-free result:
lein test tst.demo.core
4
3
2
1
0
It looks like using just plain 5 causes the compiler to use a primitive, which can't be type hinted. Explicitly creating an Integer object sidesteps the problem. I also tried (int 5) which didn't work.
Is there a reason you want to turn on reflection warnings? I normally never use them, especially for debugging.
Update
Note that if you wrap the code in a function like so:
(defn stuff
[arg]
(loop [i arg]
(when (pos? i)
(recur (debug (dec i))))))
there is no problem calling (stuff 5) since function args must always be passed as objects (via autoboxing if necessary).
the problem is that the return type of debug can't be deduced.
this is usually solved with type hints
in your case the following should do the trick:
(defn debug ^long [x] (doto x (->> println :>)))
user> (loop [i 5] (when (pos? i) (recur (debug (dec i)))))
4
3
2
1
0
nil
If, for whatever the reason, you do not want to use a macro, you may want to have a look at definline which seems to preserve type information:
(definline debug2 [x] (doto x (->> println :>)))
The call below, for instance, does not result in a reflection warning:
(.add (debug2 (ArrayList.)) 5)
My initial thought would have been to use a Java class with overloaded methods to achieve something along these lines, of which one method would take a generic argument T and return a T. In addition to this generic method, you would have had to overload that method for the few primitive types because generics only work with boxed values AFAIK. You could then reuse the class below for your debugging purposes:
import clojure.lang.IFn;
public class PassThrough {
private IFn _fn;
public PassThrough(IFn fn) {
_fn = fn;
}
public <T> T invoke(T x) {
_fn.invoke(x);
return x;
}
public long invoke(long x) {
_fn.invoke(x);
return x;
}
public double invoke(double x) {
_fn.invoke(x);
return x;
}
}
This will not work for reference types, though, because of type erasure. So if I would do something like this, I would still get a warning:
(defn debug [x] (doto x (->> println :>)))
(def pt-debug (PassThrough. debug))
(.add (.invoke ^PassThrough pt-debug (ArrayList.)) 9) ;; <-- Reflection warning here when calling .add
I am defining a function "true-or-false" that will take an argument and print "1" if it is true and "0" if it is false but when I run my function with the argument:
(= 5 4)
it returns the error: "ClassCastException java.lang.Boolean cannot be cast to clojure.lang.IFn"
Code:
(defn true-or-false [x] (if (x)
(println "1")
(println "0")))
(def a (= 5 4))
(true-or-false a)
The clojure.lang.IFn interface provides access to invoking functions, but what you are passing to true-or-false appears to be a number. You shouldn't be wrapping x in parentheses inside if – that would mean you are invoking the x function call (see clojure.org reference on the if special form).
When using prismatic/schema, validation of enum on defrecord doesn't work, as shown here:
(s/defrecord Action [type :- (s/enum :a :b)])
#'user/strict-map->Action
user> (Action. "3") ; this should fail
#user.Action{:type "3"}
user> (Action. 1) ; this should fail
#user.Action{:type 1}
user> (Action. "abc") ; this should fail
#user.Action{:type "abc"}
However, when I change enum to long, it works as expected:
(s/defrecord ThisWorks [type :- long])
#'user/strict-map->ThisWorks
user> (ThisWorks. 3)
#user.ThisWorks{:type 3}
user> (ThisWorks. "abc")
ClassCastException java.lang.String cannot be cast to java.lang.Number user/eval11888 (form-init4803894880546699153.clj:1)
Does anybody know? Thank you so much.
Because you can switch on and off validation during runtime your Records aren't actually checked until you pass them into a function:
(s/defrecord Action [type :- (s/enum :a :b)])
(s/defn process-action [x :- Action])
(process-action (Action. "3")) ;; => Exception
Regarding long magically working. This is just special clojure behavior due to primitives:
fields can have type hints, and can be primitive
note that currently a
type hint of a non-primitive type will not be used to constrain the
field type nor the constructor arg, but will be used to optimize its
use in the class methods
constraining the field type and constructor
arg is planned
(s/defrecord PrimitveRec [foo :- long])
(s/defrecord NonPrimitveRec [foo :- String])
(.? NonPrimitveRec :field #"foo" :type)
;=> (java.lang.Object)
(.? PrimitveRec :field #"foo" :type)
;=> (long)
Where .? is from Vinyasa.
In a function definition:
(defn ^boolean =
;;other arities omitted...
([x y]
(if (nil? x)
(nil? y)
(or (identical? x y)
^boolean (-equiv x y))))
what does the ^boolean part in function definition mean? Does it only extend the metadata and signify the type of return, or does it have any deeper meaning? In other words, does it add any more value than simply making the code more self-described?
It is a type hint. See
https://www.safaribooksonline.com/library/view/clojure-programming/9781449310387/ch09s05.html
http://clojure-doc.org/articles/language/functions.html
or your favorite book. PLEASE NOTE: the compiler does not enforce that the actual type matches the type hint! Example w/o type hint:
(defn go []
"banana" )
(println (go))
;=> banana
(defn ^long go []
"banana" )
(println (go))
;=> Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number,
If I do this:
(eval (let [f (fn [& _] 10)]
`(~f nil)))
It returns 10 as expected.
Although if I do this:
(eval (let [f (constantly 10)]
`(~f nil)))
It throws an exception:
IllegalArgumentException No matching ctor found for
class clojure.core$constantly$fn__... clojure.lang.Reflector.invokeConstructor
Since both are equivalent why is the code with constantly not working?
This question has two answers, to really get it.
First, to clarify why your eval form is not giving you the expected result in the second case, note that f has been assigned to be equal to the function (fn [& _] 10). This means when that form is evaluated, the function object is again evaluated--probably not what you had in mind.
tl;dr: f is evaluted when it is bound, and again (with ill-defined results) when the form you create is eval'd.
The reason why one (the anonymous function) works, while the other fails means we have to look at some of the internals of the evaluation process.
When Clojure evaluates an object expression (like the expression formed by a function object), it uses the following method, in clojure.lang.Compiler$ObjExpr
public Object eval() {
if(isDeftype())
return null;
try
{
return getCompiledClass().newInstance();
}
catch(Exception e)
{
throw Util.sneakyThrow(e);
}
}
Try this at the REPL:
Start with an anonymous function:
user=> (fn [& _] 10)
#<user$eval141$fn__142 user$eval141$fn__142#2b2a5dd1>
user=> (.getClass *1)
user$eval141$fn__142
user=> (.newInstance *1)
#<user$eval141$fn__142 user$eval141$fn__142#ee7a10e> ; different instance
user=> (*1)
10
Note that newInstance on Class calls that class' nullary constructor -- one that takes no arguments.
Now try a function that closes over some values
user=> (let [x 10] #(+ x 1))
#<user$eval151$fn__152 user$eval151$fn__152#3a565388>
user=> (.getClass *1)
user$eval151$fn__152
user=> (.newInstance *1)
InstantiationException user$eval151$fn__152 [...]
Since the upvalues of a closure are set at construction, this kind of function class has no nullary constructor, and making a new one with no context fails.
Finally, look at the source of constantly
user=> (source constantly)
(defn constantly
"Returns a function that takes any number of arguments and returns x."
{:added "1.0"
:static true}
[x] (fn [& args] x))
The function returned by constantly closes over x so the compiler won't be able to eval this kind of function.
tl;dr (again): Functions with no upvalues can be evaluated in this way and produce a new instance of the same function. Functions with upvalues can't be evaluated like this.