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