(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 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