On Mon, Nov 22, 2010 at 10:10 AM, nicolas.o...@gmail.com
<nicolas.o...@gmail.com> wrote:
> Clojure also offers an alternative to the full duck-typing/reflection scheme
> while being more dynamic than interface: protocols.
>
> As many LISPs, it offers dynamicity with possible static typing
> optimization when it is useful.

Type hints don't reduce flexibility AT ALL.

user=> (defn t1 [x] (.length x))
#'user/t1
user=> (t1 "foo")
3
user=> (t1 'foo)
#<CompilerException java.lang.IllegalArgumentException: No matching
field found: length for class clojure.lang.Symbol
(NO_SOURCE_FILE:141)>

Since it calls a String method it doesn't work if you call it with a non-String.

user=> (defn t1 [^String x] (.length x))
#'user/t1
user=> (t1 "foo")
3
user=> (t1 'foo)
#<CompilerException java.lang.ClassCastException: clojure.lang.Symbol
cannot be cast to java.lang.String (NO_SOURCE_FILE:144)>

The only change here is the exact exception thrown in the case where
it wouldn't have worked anyway.

user=> (defn t2 [x] (seq x))
#'user/t2
user=> (t2 "foo")
(\f \o \o)
user=> (t2 [1 2 3])
(1 2 3)

Now it's not an interop call.

user=> (defn t2 [^String x] (seq x))
#'user/t2
user=> (t2 "foo")
(\f \o \o)
user=> (t2 [1 2 3])
(1 2 3)
user=>

And now it works even if you pass it a non-String and the argument is
type hinted as a String.

The one thing a type hint affects is an interop call, such as a Java
method call.

(.foo x)

becomes a bunch of calls through java.lang.reflect in most cases.
These generate the IAE on type mismatch. With a String type hint
it just becomes bytecode equivalent to Java

((String)x).foo();

and you get a ClassCastException when x is not a String.

One slight caveat: ALL interop calls on the thing get a cast to the
exact class in the hint (and not, say, the closest-to-Object
superclass that has a matching method as decided at compile time).
Hence:

user=> (defn t3 [^String x] (.hashCode x))
#'user/t3
user=> (t3 "foo")
101574
user=> (t3 [1 2 3])
#<CompilerException java.lang.ClassCastException:
clojure.lang.PersistentVector cannot be cast to java.lang.String
(NO_SOURCE_FILE:153)>

even though every Object implements .hashCode and x is surely an Object:

user=> (.hashCode [1 2 3])
30817

If you need maximum flexibility, then, you need to type hint
separately for each interop call on a value, e.g.

(defn foo [x]
  (let [^Foo a x]
    (if (and (.someFooSpecificMethod a) (other-criteria))
      (let [^Bar b x]
        (do-something-with (.aBarMethod b)))
      (do-something-else))))

if you need this to work if x is a non-Bar Foo (in which case, it will
work if the if condition is only met for Foos that are Bars, but would
have thrown a CCE at the call to .someFooSpecificMethod if you'd type
hinted the function's argument as a Bar; you could in this case have
gotten away with type hinting the function's argument as a Foo and
leaving out the first let, since you don't have branches that call
Object interop methods like hashCode).

You can also launder type hints by calling a Clojure function that
calls your superclass (e.g. Object) interop method:

user=> (let [^String x 1] (.toString x))
#<CompilerException java.lang.ClassCastException: java.lang.Integer
cannot be cast to java.lang.String (NO_SOURCE_FILE:155)>
user=> (let [^String x 1] (str x))
"1"

but not by nesting an unhinted let:

user=> (let [^String x 1] (let [y x] (.toString y)))
#<CompilerException java.lang.ClassCastException: java.lang.Integer
cannot be cast to java.lang.String (NO_SOURCE_FILE:157)>

The type hint propagates to y, but not to str's function argument
since str's function argument can come from any source whereas y can
only ever come from x and you've assured the compiler that y is
supposed to be a String.

So one reason to be free with type hints in performance-critical code
is that it generally can't cause any harm unless you get the type
hints' classes wrong for the interop you're using.

The other reason is of course the performance gain, often a
10x-or-more speedup of the affected calls. Reflection is *slow*, and,
on top of that, reflective method calls can't be inlined by JIT, so
e.g. Integer boxing can't be eliminated across the call by JIT, and
etc.; a whole cascade effect of missed JIT optimization opportunities
in the wake of every case where reflection is used.

The type hints replace the reflection with much faster casts, which
allow the JIT optimizations noted above and in some cases can
themselves be optimized away entirely and otherwise are likely to be
effectively optimized away by your CPU's branch prediction (in working
code that never actually triggers the CCE throw on type error).
Nobody's ever complained that Java's casts make Java code slow. (They
complained that its running on a VM made it slow; so Sun introduced
JIT. They complained that its garbage collector made it slow, so Sun
introduced the generational collector and (now as a part of Oracle) is
now hard at work on something called G1.)

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to