Is there a way to assert to the type checker that every map in a vector is of a certain HMap type? Like by calling a function to validate that or something? Kind of like the inference using (if (int? somevar) (do something with somevar as an int) (throw ...)). I am wondering if its possible to do something like (if (valid-myhmap-type? map ) (do something with myhmap type) (throw ...))
See https://api.typedclojure.org/latest/typed.clj.runtime/clojure.core.typed.html#var-cast
Also https://api.typedclojure.org/latest/typed.clj.runtime/clojure.core.typed.html#var-pred
Oh great how do I reference a type I defined via defalias in cast?
Just like t/ann-form or t/inst.
Weird i'm getting no such var t/cast. These are the only functions/macros my repl can see.
it's in clojure.core.typed
Oh my bad let me see
It seems you cannot cast a map with a vector value. Like {:x t/int :xs (t/Vec t/Int)}. Trying to cast any map into that type gives an error Don't know how to apply type: (clojure.lang.IPersistentVector x)
But you can use pred on it which is interesting
Thanks I'll take a look.
Use pred. cast is a based on an old idea that I've moved away from. It might wrap the value and check it lazily, which comes with its own set of issues.
pred will blow up at compile time if it cannot check it without wrapping it.
Even pred is not a great solution compared to schema/spec/malli validators. But it will do the job.
Would one of those validators allow the function to typecheck? And whats your reasoning for pred not being as great a solution?
Yes, if you do (if ((pred '{my hmap}) foo) good (throw)) that should infer correctly.
What pred has over other validators is that it refuses to create a predicate for a value it cannot check 100% deterministically. But on the downside, pred is a macro so it can generate a ton of code. It also ties my hands as an implementor because I can't lazily load the contract implementation, so everyone has to load the implementation of pred even if they don't use it.
Mostly minor details for users.
I was trying out the commented https://github.com/typedclojure/typedclojure/blob/main/typed/lib.clojure/src/typed/ann/clojure.cljc#L2084 but kept on getting error: Annotation:
cc/empty
(t/All [x empty-fn]
[(IPersistentCollection t/Any :empty-fn empty-fn) :-> (empty-fn)]
[nil :-> nil]
[(t/U nil (IPersistentCollection t/Any :empty-fn empty-fn)) :-> (t/U nil (empty-fn))])
Error:
Start checking typed.core
Type Error (file:/src/typed/core.clj:20:27)
Internal Error (file:/src/typed/core.clj:20:27) Bad All syntax: (t/All [x empty-fn] [(IPersistentCollection x :empty-fn empty-fn) :-> (empty-fn)] [nil :-> nil] [(t/U nil (IPersistentCollection x :empty-fn empty-fn)) :-> (t/U nil (empty-fn))])
Execution error (ExceptionInfo) at clojure.core.typed.errors/print-errors! (errors.cljc:299).
Type Checker: Found 1 error
Oh that's just a sketch. It should look similar to cc/conj. This sketch was trying to make it extensible.
More like:
(t/IFn [(t/Vec t/Any) :-> (t/Vec t/Nothing)]
[(t/Map t/Any t/Any) :-> (t/Map t/Nothing t/Nothing)]
[(t/Set t/Any) :-> (t/Set t/Nothing)]
[(t/ISeq t/Any) :-> (t/ISeq t/Nothing)]
...)I'll push an annotation to main once I test it.
I found this https://github.com/typedclojure/typedclojure/blob/main/typed/lib.clojure/src/typed/ann/clojure.cljc#L2074very interesting
IPersistentCollection [[x :variance :covariant]
:conj-fn [conj-fn :kind (t/TFn [[x :variance :covariant]] (IPersistentCollection x))]
:empty-fn [empty-fn :kind (t/TFn [] (IPersistentCollection t/Nothing :count (ExactCount 0)))]]
I found this very generic and a great way to encode interface behaviour e.g. conj-fn and empty-fn in typedef.
How can one use such a Type annotation? would it be via defalias or TFn?Yes I agree except it's all just wishful thinking, I haven't actually implemented anything powerful enough to manage this kind of annotation. This comment is actually fleshed out a bit more https://github.com/typedclojure/typedclojure/blob/73fee72abafa47c6c0a285bd674f8952e0604bb3/typed/lib.clojure/src/typed/ann/clojure/jvm.clj#L40-L53.
There's even more sketches and ideas here if you're interested https://github.com/typedclojure/typedclojure/blob/main/typed/clj.checker/test/clojure/core/typed/test/conj_experiment.clj
Thanks Ambrose. This is very interesting. I often go through typed.clojure code to fish out missed type aliases or types that went under the radar.
and the sketches for possible directions are very illuminating 🙂
Yeah! The idea of a once-and-for-all type for conj is very attractive. I think Scala can easily do this sort of thing. I've concentrated on other areas.
I haven't really considered how powerful it would be tbh, most of clojure's data types are build on conj. Might it change how we annotate cc/into?
If the experiment succeeds, then the annotation for cc/into would probably change.
I agree that Scala 3 is really capable in type driven development.
maybe:
cc/into
#_
(t/All [x y Conj]
[(t/Coll x :conjable y :Conj Conj) (t/Transducer y x) (t/Seqable y) :-> (Conj x)])Something a bit fishy about it. Maybe someone has already tried to encode the clojure hierarchy in scala. (Oh, should return (Conj x) I think.)
Hmm no still misses that in (into {} (map (fn [[k v]] ...) [{:a 1} [:a 1]]) the transducer will be passed [:a 1] .
Oh wait that's not what happens
typed.dev=> (into {} (map (fn [e] (prn e) e)) [{:a 1} [:a 2]])
{:a 1}
[:a 2]
{:a 2}
It's the return value of the transducer that's interesting not the input. It can return either an entry or a map. Yeah that's missing in the annotation.
well it doesn't work on lists for some reason, but here's the non-extensible annotation for empty https://github.com/typedclojure/typedclojure/commit/b296eaef32825549b7daebd680fbb22115fe11b6