(sorry for the prior hiccup, I accidentally sent a draft of my message)
I've been using 1.3.0 alphas for real work for a little while now, and would
like to raise a couple of issues around type hinting so as to calibrate my
expectations and understanding and/or provoke some discussion about some
inconsistencies I'm seeing.
Some observations from a REPL interaction first:
=> *clojure-version*
{:major 1, :minor 3, :incremental 0, :qualifier "alpha6"}
=> (set! *warn-on-reflection* true)
true
=> (defn ^String foo [])
#'user/foo
=> (fn [] (String. (foo)))
#<user$eval398$fn__399 user$eval398$fn__399@215b011c>
=> (-> #'foo meta :tag)
java.lang.String
Right, so hinting a var's name works just fine, as in 1.2.x. Things get wonky
when hinting with primitives, though:
=> (defn ^long foo [])
#'user/foo
=> (fn [] (Long. (foo)))
#<CompilerException java.lang.IllegalArgumentException: Unable to resolve
classname: clojure.core$long@41f6321, compiling:(NO_SOURCE_PATH:1)>
=> (-> #'foo meta :tag)
#<core$long clojure.core$long@41f6321>
The compilation fails because the var's :tag metadata is a function,
clojure.core/long! Bizarre. I would assume that that's a bug, except that it
appears the intended way to hint _primitive_ returns (but no other returns?) is
on the arg vector, and not the var name:
=> (defn foo ^long [])
#'user/foo
=> (fn [] (Long. (foo)))
#<user$eval410$fn__411 user$eval410$fn__411@18287811>
=> (-> #'foo meta :tag)
nil
=> (meta @#'foo)
nil
So our compilation succeeds here, but the var's metadata is left :tag-less
(understandably, since the hint is on the arg vector and not the var name), and
the function has no metadata at all (this is surely a bug, but not my primary
focus here).
My understanding is that the objective in hinting the arg vector is to allow
for variable return hints on functions with multiple arities (which was not
possible in 1.2.x). This is an excellent aim, but it would seem that the
current implementation presents some usage difficulties:
=> (defn foo
(^String [])
(^long [a])
(^double [a b]))
#'user/foo
=> #(String. (foo))
#<user$eval2241$fn__2242 user$eval2241$fn__2242@25c2cbee>
Reflection warning, NO_SOURCE_PATH:1 - call to java.lang.String ctor can't be
resolved.
=> #(Long. (foo))
#<user$eval2245$fn__2246 user$eval2245$fn__2246@508a8b07>
Reflection warning, NO_SOURCE_PATH:1 - call to java.lang.Long ctor can't be
resolved.
=> #(Long. (foo 0))
#<user$eval2249$fn__2250 user$eval2249$fn__2250@c23c5ff>
=> #(Double. (foo 0))
#<user$eval2260$fn__2261 user$eval2260$fn__2261@35cfee57>
Reflection warning, NO_SOURCE_PATH:1 - call to java.lang.Double ctor can't be
resolved.
=> #(Double. (foo 0 0))
#<user$eval2264$fn__2265 user$eval2264$fn__2265@5d504a84>
So, we can have variable hinting of fn returns of different arities, but only
for primitive types. This hurts, and should either be "fixed" or produce a
compiler warning. Also, there is no metadata on the var indicating the types
of the different function arities:
=> (meta #'foo)
{:arglists ([] [a] [a b]), :ns #<Namespace user>, :name foo, :line 1, :file
"NO_SOURCE_PATH"}
Somewhat worse from the standpoint of semantic consistency, hinting the var
with ^String yields good — yet confusing — results:
=> (defn ^String foo
([])
(^long [a])
(^double [a b]))
#'user/foo
=> #(Double. (foo 0 0))
#<user$eval2289$fn__2290 user$eval2289$fn__2290@69996e15>
=> #(String. (foo))
#<user$eval2293$fn__2294 user$eval2293$fn__2294@220860ba>
And now the var metadata has a :tag, but only for the ^String hint:
=> (meta #'foo)
{:arglists ([] [a] [a b]), :ns #<Namespace user>, :name foo, :line 1, :file
"NO_SOURCE_PATH", :tag java.lang.String}
I understand that the generated function implements the necessary interfaces
when primitive hints are involved:
=> (supers (class foo))
#{clojure.lang.IFn$OL java.lang.Object java.lang.Runnable clojure.lang.Fn
clojure.lang.AFn clojure.lang.IObj clojure.lang.IMeta clojure.lang.AFunction
java.util.concurrent.Callable clojure.lang.IFn$OOD java.io.Serializable
java.util.Comparator clojure.lang.IFn}
Having to touch (:tag (meta var)) as well as dig away at the implemented
interfaces of generated function classes (which I have to assume are strictly
implementation details) seems unnecessarily inconsistent.
Finally, I'd like to question the current path of supporting hinting of
primitive returns — and primitive returns only — via function arg vectors.
Again, to me, it's a simple point of consistency, this time from a mostly
syntactic perspective; most of the examples below have already been seen above,
but I repeat them so that they're all co-located for easy comparison:
We hint object returns via the var name:
(defn ^String foo [])
But we hint primitive returns via arg vectors:
(defn foo ^long [])
Different arities of functions can be hinted, but _only_ with primitive types,
and on arg vectors:
(defn foo
(^long [])
(^double [a]))
While a hint added to a function's var "cascades" down to any unhinted arities
of a function:
(defn ^String foo
([] "bar")
(^double [a])
([a b] :surprise!)
I wasn't able to find a canonical discussion of the introduction of this
semantic of hinting argument vectors, so a pointer to one (or a new one here!)
would be great. It doesn't make a lot of sense to me — return hints are
related to the function, not the arguments. I thought that perhaps hinting the
arg vector was provided so as to support hinting returns of anonymous
functions, but that doesn't seem to be the case:
=> (def a (fn ^long []))
#'user/a
=> #(Long. (a))
#<user$eval2390$fn__2391 user$eval2390$fn__2391@3865db85>
Reflection warning, NO_SOURCE_PATH:1 - call to java.lang.Long ctor can't be
resolved.
>From a naive perspective, understanding that perhaps implementation details
>and historical considerations are driving the current state of things, I would
>expect this to work, and be the most semantically-consistent usage:
(defn foo
^String ([] "bar")
^double ([a] 5.6)
([a b] :not-hinted-at-all))
…with appropriate metadata on #'foo and the function itself indicating the
corresponding arity<=>return types.
Of course, this isn't how 1.3.0 alphas work now, but unless things are really
locked down, I hope the above is a reasonable starting point for discussing how
to smooth out the inconsistencies that currently exist.
Cheers,
- Chas
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en