Did you know, you don't need type hints? No! Really! You
don't need them. There is only one situation where you
might
need them: on call sites for host interop.
Clojure is dynamically typed. That doesn't mean that things don't have a type, but that the compiler doesn't check it at compile time.
Now, there's a problem here. The JVM hardwires the method to call in the byte code. So it needs to know the type of things, doesn't it?
Clojure solves this problem by using the IFn
interface.
It describes callable things—mostly functions. The latter
are represented as lists in Clojure. Under the hood they
are translated into a call to the .invoke
method of the
function object.
user=> (defn foo [] :hello)
#'user/foo
user=> (foo)
:hello
user=> (.invoke foo)
:hello
By implementing IFn
also other types may take part in
“function” calls. Most prominently are Clojure's data
structures.
user=> ({:foo :bar} :foo)
:bar
However, IFn
defines all arguments of .invoke
to be of
type Object
. So you can pass anything you like to a
Clojure function. It doesn't matter which type it is, because
everything is an Object
anyway. (Assume no primitive numerics
for the moment.)
So hinting the arguments to a Clojure function never makes sense.
This whole approach works, because the first element of a
list is known to be a IFn
. So the byte code is hardwired
for this interface. When you pass in a wrong type you get a
cast error.
user=> (4711)
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn
But what if you want to do some host interop? Then things are not as clear and clearly depend on the context.
Sometimes Clojure is able to determine the type of a variable. One such situation is when the instance is created in the local environment.
user=> (let [x (String. "foo")]
(.indexOf x "o"))
1
A situation where this information is not available are
function arguments. As detailed above they are always of type
Object
. So Clojure has no chance of knowing the type up
front.
user=> (defn foo [x] (.indexOf x "o"))
Reflection warning, REPL:3 - call to indexOf can't be resolved.
#'user/foo
So the compiler has to resort to reflection which is of course
slow. So here it would make sense to hint x
with String
, so
that the compiler can avoid reflection.
BTW: The reflection also depends on the other method arguments. It is not sufficient to hint the method target.
user=> (defn foo [^String x o] (.indexOf x o))
Reflection warning, REPL:4 - call to indexOf can't be resolved.
#'user/foo
A method with two different signatures needs all arguments to be hinted properly.
So one might think: “It doesn't help, but it doesn't hurt either, so I sprinkle hints all over the place to give myself some info on what this function returns.” And indeed this thinking is wrong.
By now it should be clear that type hints are a low-level construct. Using them in the above mentioned way over specifies the types the functions take and return. You basically lock the code which could in theory be host independent into one platform.
Take for example str
. It is hinted to return a String
. Is this
wrong? No. It uses a host dependent way to efficiently create strings.
So you need a custom str
version a on different host anyway, which
may then be hinted with a different type.
Take for example the following str-cat
function:
(defn ^String str-cat
[& xs]
(apply str xs))
Is the type hint wrong? Yes! This function would work on any host in the same way. But by needlessly hinting it, we lock it into the JVM.
Only add type hints when you really need them. That is in host interop call sites. And even then you should check, that your really need them.
Sometimes you may even choose to go with the reflective call. But beware the library author. Others will have to live with it!
*warn-on-reflection*
is your friend.
Just adding type hints doesn't turn Clojure magically into a statically typed language. Type hints are not Typed Clojure.
Published by Meikel Brandmeyer on .
I'm a long-time Clojure user and the developer of several open source projects mostly involving Clojure. I try to actively contribute to the Clojure community.
My most active projects are at the moment VimClojure, Clojuresque and ClojureCheck.
Copyright © 2009-2014 All Right Reserved. Meikel Brandmeyer