Skip to content

Instantly share code, notes, and snippets.

@pjstadig
Created March 20, 2017 13:06
Show Gist options
  • Save pjstadig/6fa4ebf465f6d438b4dcc2a5bfbb3db6 to your computer and use it in GitHub Desktop.
Save pjstadig/6fa4ebf465f6d438b4dcc2a5bfbb3db6 to your computer and use it in GitHub Desktop.
A little type hint hint
(ns typehint.core)
;; A little type hint hint...
(set! *warn-on-reflection* true)
;; There are two ways to type hint the return value of a function, but one is
;; (technically) wrong:
(defn ^String as-string0
[obj]
(str obj))
(defn as-string1
^String [obj]
(str obj))
(.length (as-string0 '(:a :b))) ; <- no reflection warning
(.length (as-string1 '(:a :b))) ; <- no reflection warning
;; But the first actually means something more than just the return value of the
;; function, it means the value of the var is a String.
(def ^String a-string
"Hello, World!")
(defn to-string0
[]
(.length a-string) ; <- no reflection warning
(.length as-string0) ; <- weird, but no reflection warning
)
(defn to-string1
[]
(.length as-string1) ; <- weird, and reflection warning
)
;; Why is this? Originally, Clojure did not use type hints on the argument
;; vector. To type hint the return value of a function you would put a type
;; hint on the var (the first method). In version 1.3 Clojure got primitive
;; arguments and return values for functions, and those are indicated by putting
;; the type hint on the args and on the arg vector (the second method).
;;
;; AFAIK, the only reason the first method even works for return values is for
;; backwards compatibility. If you put a type hint on the var you are saying
;; the value of that var will be that type. The correct way to type hint a
;; return value is to put the hint on the arg vector.
;;
;; This also means you can have different return hints for different arities
;; (though I would question why you'd want that!).
(defn ambiguous
(^String []
"foo")
(^Integer [_]
(int 5)))
(defn confusing
[]
(.length (ambiguous)) ; <- no reflection warning
(.longValue (ambiguous nil)) ; <- also no reflection warning
)
;; == SUMMARY
;; If you want to type hint the *content* of a var, put the type hint on the
;; var.
;; If you want to type hint the return value of a function, put the type hint on
;; the argument vector.
@cursive-ide
Copy link

I think @Bronsa pointed this out on Twitter, but in Clojure <= 1.7, when you type hint the arg vector the symbol is not evaluated. This means that if you type hint with the short name of a class you have imported where the function is defined, if that same class is not imported in the namespace where the function is invoked, things will break:

(ns first-ns
  (:import [java.util ArrayList]))

(defn my-fn ^ArrayList [])
(ns other-ns)

(first-ns/my-fn)  ; boom, ArrayList is not defined

@borkdude
Copy link

@cursive-ide I've tried to reproduce that problem with clojure 1.5, 1.6 and 1.7 but I couldn't. But given that it's no longer a problem with 1.8, maybe it's not so relevant anymore.

@vemv
Copy link

vemv commented Mar 31, 2022

@borkdude : it's definitely a real problem, often in various projects I had to qualify imports because 1.7 was part of the CI matrix and was failing

@borkdude
Copy link

@vemv I consider 1.7 too old now to bother adding support in clj-kondo.

@cursive-ide
Copy link

@borkdude There is at least one case which is still relevant in modern versions: https://clojure.atlassian.net/browse/CLJ-1180 (fixed in 1.11)

@borkdude
Copy link

borkdude commented Apr 1, 2022

@cursive-ide Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment