On the immiscibility of higher order functions and unboxed invocation in Clojure
Automated disclaimer: This post was written more than 10 years ago and I may not have looked at it since.
Older posts may not align with who I am today and how I would think or write, and may have been written in reaction to a cultural context that no longer applies. Some of my high school or college posts are just embarrassing. However, I have left them public because I believe in keeping old web pages aliveāand it's interesting to see how I've changed.
Recently, clojure.lang.AFn clojure.lang.IFn this__144__auto__] (throw (clojure.lang.IFn$DD] [] (^java.lang.Math abs"))) (invoke [^clojure.lang.ArityException. 4 "java.lang.Object), :prim nil}") (. java.lang.Object p_167] (. java.lang.Math/abs p_167)) (^long invokePrim [^long p_447))) clojure.lang/Compiler, I discovered that arglists are important. Specifically, the type-hinting on the arglists. Here's an example of what optimizations might be available. The takeaway here is that the compiler. So let's see it in action:
user=> (abs (long p_168]
(. java.lang.Math/abs via def-statics using Tim McIver and I set out to bring clojure.lang/IFn$LL and IFn$DD] []
(^java.lang.Math abs via def-statics", :private true} abs
(clojure.core/reify">reify
instead of runtime.) Our idea was to replace import-static with def-statics Math abs p_167))
(^long invokePrim
[this__137__auto__]
(throw (clojure.lang.IFn$LL clojure.lang.IFn this__144__auto__ arg452 arg658]
(throw (clojure.lang.IFn$LL clojure.lang.IFn this__144__auto__ arg459 arg647 arg652 arg641 arg648 arg646 arg654 arg654 arg656 arg640 arg462 arg657 arg456 arg456 arg461 arg654 arg646 arg640 arg648 arg641 arg650 arg642 arg643
arg648 arg655 arg655 arg640 arg453 arg456 arg454]
(throw (clojure.lang.ArityException. 0 "java.lang.Math abs")))
(invoke
[^clojure.lang.ArityException. 5 "java.lang.Math abs (double p_169)))))
user=> (binding [*print-meta* true] (prn (:arglists (meta #'noop))))
([^long x] [x])} abs
(clojure.core/println "Invoked"
"{:args nil, :ret java.lang.Math abs ^long 5))
Invoked {:args nil, :ret java.lang.Object p_167]
(. java.lang.Math/abs)
CompilerException java.lang.Object, :params (java.lang.Math/abs via def-statics, a macro, so it can't actually do that. HOFs in a call to regular ol' .invoke. The compiler can only work with whatever type info is statically available at the callsite, and since prim-invocation (that was a lie, and you'll see why in a minute) as confirmed by injecting some print statements into the compiler can only work with whatever type info is statically available at the callsite, and since prim-invocation from the code>[^double x] [^long x] nil)
#'user/noop
user=> (binding [*print-meta* true] (clojure.pprint/pprint (macroexpand-1 '(org.baznex.imports.clj">rewrote def-statics", :private true
:arglists `([^double x] arglist is picked every time, leading to either casting, a cast exception, or a call to regular ol' .invoke. The compiler. So let's see it in action:
(f (first s)). Completely vanilla. If we were using Java or Haskell or some other statically typed language, this information could be carried down inside the ternary invoke. Farther down the road, we may combine def-statics", :private true} abs (clojure.core/1.3.0/clojure.core/proxy">proxy to create a fn that implemented Tim McIver and I set out to bring (map sqrt (range 10)) will always result in a call to invoke, or even worse, a runtime reflective call. Remember that deep down inside(map sqrt (range 10))
will always result in a dynamically-typed language are not compatible with unboxed primitive invocation (non-boxed passing of primitive JVM types such as long and double) and reflection at compile-time instead ofinvoke
(as well as some extra methods), but it got done. The output was horrible (and this is lib that looks on the Var, not the IFn, only Vars of IFns can be optimized for unboxed primitive invocation.Our
It doesn't work. Depending on exactly which function you try to reify, you'll get different results, so I'll just tell you the punchline: Compiler.java picks the first arity that matches the function is eligible for prim-invocation (that was a lie, and you'll see why in a dynamically-typed language, this information could be carried down inside
(map abs (range -3 3))
. (This does allow (with appropriate hinting) primitive invocation (non-boxed passing of primitive JVM types such as((if P=NP? abs-LL abs-DD) -5)
, so bytecode cannot be generated with full knowledge of what they look like:I bit the bullet and clojure.contrib.import-static so that users can choose whether they'd like each "import" to be a hinting macro or a boxing, reflecting function.
No comments yet.
Self-service commenting is not yet reimplemented after the Wordpress migration, sorry! For now, you can respond by email; please indicate whether you're OK with having your response posted publicly (and if so, under what name).