I want to get the clojure.core.typed
type check passing, but I'm getting a type error that I don't understand.
My questions are:
Here is my code (which I realize is incorrect):
(ns clj.util.map
(:require [clojure.core.typed :as t]))
(t/ann map-vals
(All [k v1 v2]
(Fn [ (Fn [(t/Option v1) -> (t/Option v2)])
(t/Option (t/Map k v1)) ->
(t/Option (t/Map k v2)) ])))
(defn map-vals
;; FIXME: Incorrect code
"Apply a function to each of the values in a map, returning the updated map."
[f hm]
(t/doseq> [k :- Any (keys hm)]
(assoc hm k (f (get hm k)))))
Here is the output of lein typed check clj.util.map
:
Initializing core.typed ...
"Elapsed time: 6697.604 msecs"
core.typed initialized.
Start collecting clj.util.map
Finished collecting clj.util.map
Collected 1 namespaces in 6851.111 msecs
Start checking clj.util.map
Checked clj.util.map in 968.041 msecs
Checked 1 namespaces (approx. 21 lines) in 7823.552 msecs
Type Error (clj.util.map:14:23) Polymorphic function clojure.core/keys could not be applied to arguments:
Polymorphic Variables:
k
Domains:
(t/Map k Any)
Arguments:
(t/Option (t/Map k v1))
Ranges:
(t/Seq k) :object {:path [Keys], :id 0}
in: (clojure.core/keys hm)
in: (clojure.core/seq (clojure.core/keys hm))
Type Checker: Found 1 error
Found errors
Subprocess failed
The first part of the error tells you this concerns a call to clojure.core/keys
. You can lookup the type with (cf keys)
.
(All [k]
[(Map k Any) -> (Seq k) :object {:id 0 :path [Keys]}])
The error is basically summarising this polymorphic type juxtaposed with the actual types provided to the function.
The Polymorphic Variables
list all the variables in the All
binder and their type bounds. k
is the only variable, which has essentially no bounds, so k
is displayed.
The Domains
list all the parameter types (to the left of the ->
) in order. If multiple arities were specified with Fn
, each parameter list would be displayed sequentially.
The Arguments
show you what types are actually passed to the function. The code at 14:23 is presumably this call (keys hm)
, so the type of the first argument is displayed: (t/Option (t/Map k v1))
The Ranges
list all the return types (to the right of the ->
) in order.
Sometimes there is an Expected
type, which must match up with a Range
in the same way all the Arguments
must match up with a Domain
.
We can diagnose this error by comparing the Domains
with the Arguments
. The list of Arguments
must fit under one list of Domains
, and the match is attempted top-down. If no Domains
fit with the Arguments
, we get a type error like this; it's often too complicated to pinpoint exactly where the constraint algorithm failed, so the user is presented with a lot of information.
In this case, we try and fit the argument (t/Option (t/Map k v1))
under the domain (t/Map k Any)
, which fails. This is because the argument (t/Option (t/Map k v1))
is the same as (U nil (t/Map k v1))
, and nil
does not fit under the domain (t/Map k Any)
.
There are a couple of ways to fix this. You basically need to ensure keys
isn't passed nil
. Here's one option:
(keys (or hm {}))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With