core-typed

emccue 2022-03-28T00:06:51.981439Z

(ns tyt
  (:require 
    [typed.clojure :as t]
    [malli.core :as m]))

(m/=> foo [:=> [:cat :int] :int])

(defn foo [t] 
  "abc")

(comment
  (t/check-ns-clj))

emccue 2022-03-28T00:07:24.111689Z

Going through the malli example, first time in a long time trying to use typed clojure. Can't seem to get it to break. Am I doing something wrong?

emccue 2022-03-28T00:07:59.204349Z

(or rather, it doesn't fail unless i restart the repl and vice versa when changing the return from "ABC" to 3)

2022-03-28T00:45:53.852069Z

Hmm, so check-ns-clj fails with (defn foo [t] "abc"), but only after restarting the REPL?

emccue 2022-03-28T00:48:53.838959Z

emccue 2022-03-28T00:49:28.022889Z

yep

2022-03-28T00:50:55.207179Z

Oh, save the file?

emccue 2022-03-28T00:52:15.173369Z

mashing cmd + S does seem to make it work. Intellij saves when you switch tabs or give it that hint

emccue 2022-03-28T00:52:24.923989Z

but okay, that makes sense

emccue 2022-03-28T00:53:06.250219Z

so it type checks the file on disk, not the state in the repl

2022-03-28T00:53:31.578699Z

exactly. Did you eval the buffer instead of saving to disk?

emccue 2022-03-28T00:55:06.255259Z

yes i did. usually with IntelliJ I am not actively thinking about when the files are saved because they "just are"

👍 1
emccue 2022-03-28T00:57:50.008069Z

but is it using the live repl state to do the type checking?

2022-03-28T00:58:38.562549Z

Type collection is live. eg., t/ann and m/=> is gathered from the REPL.

emccue 2022-03-28T00:59:44.517569Z

So is this some form of caching?

emccue 2022-03-28T01:00:35.280609Z

(how does it know the type when i haven't loaded it in?)

2022-03-28T01:01:23.688789Z

by loading the namespace in the REPL, you've evaluated m/=>. so t/check-ns-clj just retrieves it from malli.

2022-03-28T01:01:43.371359Z

Oh, now I get it.

2022-03-28T01:02:34.636729Z

Type checking works much like evaluation. except it's read -> macroexpand/type check -> eval for each form, left to right.

2022-03-28T01:02:55.939899Z

probably if m/=> occurred after defn it would fail

emccue 2022-03-28T01:03:23.939329Z

but in this case I haven't loaded the m/=> line

2022-03-28T01:04:45.993979Z

Here's what happens: 1. you create the tyt namespace. 2. you call t/check-ns-clj 3. now, the type checker basically calls (require 'tyt :reload), except just before evaluating each form, the form is type checked.

2022-03-28T01:05:44.673149Z

so (check-form '(m/=> ..)) is called before (check-form '(defn foo ..)), so the malli schema is registered just in time.

emccue 2022-03-28T01:07:26.228599Z

ooooh okay

2022-03-28T01:07:51.166789Z

does it still work if you switch the forms?

emccue 2022-03-28T01:08:27.780719Z

it does not fail with the error if i switch the forms

emccue 2022-03-28T01:08:59.598119Z

sideways question: Does this mean that typedclojure couldn't be embedded in cljkondo?

2022-03-28T01:09:04.340029Z

ah, so what's happening there is that if there's no expected type available, it will check as [Any -> Any] . and it succeeds.

emccue 2022-03-28T01:09:28.006349Z

(or be used in a similar way)

2022-03-28T01:10:01.140439Z

clj-kondo is very different, and I don't know if it's possible to combine the two. Typed Clojure relies on being able to really macroexpand your code, which is exactly what clj-kondo cannot.

2022-03-28T01:10:26.920219Z

it's more like eastwood.

2022-03-28T01:12:12.350849Z

Interestingly, clj-kondo and Typed Clojure are converging in other ways, since we're both making extensible rules for macros that we don't recognize. So maybe, Typed Clojure will know so many macros eventually that we can think of a graalvm offline version. But, I still don't really understand why you'd want that over importing a jar.

2022-03-28T01:13:21.335399Z

The flexibility you get is perfectly demonstrated by the malli integration you're playing with. You'd need extra steps to export the malli to a clj-kondo config to do this.

emccue 2022-03-28T01:13:50.053739Z

there is already that for malli

emccue 2022-03-28T01:14:02.759139Z

and it goes to the "clj-kondo type declaration" format

2022-03-28T01:14:27.718379Z

yes, that's what made me realize the pros/cons of each approach actually.

emccue 2022-03-28T01:15:09.135669Z

most of the motivation is (i think) around the editor experience, since requiring a running repl for feedback has been a barrier

emccue 2022-03-28T01:15:25.323449Z

I think the folks that make better use of it could make a more convincing pitch though

emccue 2022-03-28T01:16:00.189869Z

so "hooks" and the pseudo macro-expansion they provide wouldn't be enough for typed clojure?

2022-03-28T01:16:09.342209Z

IMO clj-kondo is perfect the way it is, I'm just commenting on what you'd lose by making Typed Clojure move to graalvm.

2022-03-28T01:16:54.406799Z

Theoretically it's enough.

2022-03-28T01:17:20.089709Z

But there are so few rules atm that we have to wait before seriously trying.

👍 1
2022-03-28T01:19:17.554089Z

this is the no.1 question I get in the last few years 🙂 but Typed Clojure is 11 years old at this point, it's a totally different ball game on graalvm.

2022-03-28T01:19:46.820189Z

The best I could hope to contribute realistically is maybe help borkdude port some of Typed Clojure's type system features to clj-kondo.

2022-03-28T01:20:00.120079Z

at least in the short term.

2022-03-28T01:21:50.302859Z

It's about the same complexity as porting eastwood to graalvm. I haven't seen that suggested anywhere, which makes me think there are some misconceptions about Typed Clojure floating around 🙂

emccue 2022-03-28T00:11:17.742509Z

(also I am getting the feeling that i'm maybe a few months early since :map isn't supported)

2022-03-28T00:44:44.352519Z

Yes, perhaps, but you trying it out gives me an incentive to finish it so thank you. 🙂

2022-03-28T01:25:08.196859Z

I bet I can knock out a good chunk of support by just copying the malli.clj-kondo namespace.

2022-03-28T01:27:46.792389Z

I would really like to know what the real challenges are in practice (you know, asides from :map not being implemented xD).

2022-03-28T01:29:29.234359Z

I have a feeling we'll need a way to embed malli syntax into Typed Clojure syntax. Say you have a big map defined in malli, and you need to override a type annotation, you'd want to be able to refer to the big malli map without rewriting the whole thing in typed clojure

2022-03-28T01:30:15.491589Z

(def MyMassiveMalliType [:cat ... ...])
(t/ann foo [(Malli MyMassiveMalliType) -> ...])

2022-03-28T02:30:25.268889Z

@emccue I added a few dozen more malli -> type conversions, typed.malli 1.0.24 is currently releasing please check it out when the build finishes.

emccue 2022-03-28T02:39:09.072559Z

Basic :map works

emccue 2022-03-28T02:39:47.276559Z

is there a way to see what type was inferred based on a schema?

2022-03-28T02:43:02.387159Z

yeah, but it uses the old namespace. Try (clojure.core.typed/cf foo)

2022-03-28T02:43:54.611179Z

should be implicitly loaded by typed.clojure

emccue 2022-03-28T02:48:06.415449Z

is check-ns-clj what i should run to type check an entire project?

emccue 2022-03-28T02:48:21.601339Z

and does type checking extend into local/root dependencies?

emccue 2022-03-28T02:48:47.317289Z

(I'm gonna take the plunge and try to type check the monorepo)

2022-03-28T02:50:46.630989Z

check-ns-clj sources files in the same way as require. It just needs to be on the classpath in the normal place.

emccue 2022-03-28T02:58:08.108549Z

okay that went pretty quick - it skips checking namespaces that don't depend on clojure.core.typed

emccue 2022-03-28T02:58:57.709579Z

so i guess we would add a dummy require to turn it on ns by ns

(->> (all-ns)
     (filter #(string/starts-with? (str %) "lumanu"))
     (map (comp symbol str))
     (t/check-ns-clj))

2022-03-28T02:59:17.805059Z

Yeah that part is in flux. Before, that was important because check-ns checked all dependencies transitively. This is no longer the case, but the requirement is still there.

2022-03-28T02:59:54.733689Z

I was about to lift that requirement actually.

2022-03-28T03:01:57.107459Z

Yeah, the workflow is still not obvious to me after all these years. I just do

(deftest type-checking
  (is (check-ns-clj '[ns1 ns2 n3])))

2022-03-28T03:03:33.171699Z

I'm really leaning into a single file being the unit of type checking these days. Not a project, or a set of ns dependencies. Mainly for performance reasons.

2022-03-28T03:04:44.202039Z

One of the reasons users stopped using core.typed was that it forced you to check dependencies before doing anything else. Which was an agonizingly long time. This is no longer the case.

emccue 2022-03-28T03:09:47.632549Z

(ns ^:typecheck something.name)
Feels like the approach i'd go with
(deftest type-check
  (is (->> (all-ns)
           (filter #(string/starts-with? (str %) "lumanu"))
           (filter #(:typecheck (meta %)))
           (t/check-ns-clj)))
Slap that on CI

2022-03-28T03:11:19.765659Z

ha nice

2022-03-28T03:12:52.408749Z

alright I gotta go. thanks @emccue, lmk how your experiments go!