Fork me on GitHub
#core-typed
<
2024-05-02
>
TJ Campanella03:05:58

Did t/Merge get removed from the library? I have tried using it from typed.clojure and from clojure.core.typed and in each case I am getting an error that it cannot resolve it. Specifically "Cannot resolve type: t/Merge"

ambrosebs06:05:32

(deftest Merge-test
  (is-tc-e nil (t/Merge))
  (is-tc-err {} (t/Merge))
  (is-tc-e {:a 1} (t/Merge '{:a t/Int}))
  (is-tc-err {:a :a} (t/Merge '{:a t/Int}))
  (is-tc-e {:a 1 :b true} (t/Merge '{:a t/Int} '{:b t/Bool}))
  (is-tc-err {:a 1 :b 1} (t/Merge '{:a t/Int} '{:b t/Bool}))
  (is-tc-e {:a true} (t/Merge '{:a t/Int} '{:a t/Bool}))
  (is-tc-err {:a 1} (t/Merge '{:a t/Int} '{:a t/Bool})))

TJ Campanella12:05:00

I'm trying to use it inside of a defalias would that be why?

TJ Campanella14:05:02

I'm also using macros like this for calls to pred as so. (defmacro mytype? [x] ((tyc/pred MyType) ~x))` I am doing it like that because I wanted to shorten those checks and if I do it as a def it doesn't seem to type check. Let me know if there is a better way to do that. Thanks

ambrosebs15:05:47

If you def it you need to annotate it (ann mypred (t/Pred MyType))

ambrosebs15:05:25

Could you show me how you're using t/Merge?

TJ Campanella16:05:45

Oh ok that sounds great i'll try that instead of macros

ambrosebs16:05:26

BTW I forgot that I had integrated runtime checking with Malli. It doesn't cover all types, but it's another alternative to pred that the type checker understands https://github.com/typedclojure/typedclojure/blob/main/typed/malli/src/typed/malli.clj

ambrosebs16:05:53

it also can parse values (`conform` in spec). I think in the future I'll be fleshing out this integration.

TJ Campanella16:05:53

Oh interesting I think I stumbled across that once. Whats the benefit of that vs just using pred?

TJ Campanella16:05:13

Also, do you know the best way to supress the unresolved symbol errors kondo complains about when using pred in a def

TJ Campanella16:05:46

Ik you can suppress it per form but I was wondering if you know of a way to do it globally for any calls to t/pred for instance

ambrosebs16:05:48

malli has more features like parse

(is (= [:int 1]
         (sut/parse (t/U ^{::sut/name :int} t/AnyInteger
                         ^{::sut/name :bool} t/Bool)
                    1)))
  (is (= [:bool true]
         (sut/parse (t/U ^{::sut/name :int} t/AnyInteger
                         ^{::sut/name :bool} t/Bool)
                    true))))

ambrosebs16:05:27

As for clj-kondo, try some things in this document. https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md#config-in-call perhaps: {:config-in-call {clojure.core.typed/pred {:ignore [:unresolved-symbol]}}}

TJ Campanella16:05:02

That worked perfectly thanks

ambrosebs16:05:05

I'll think about how to add proper support for type aliases with clj-kondo. malli just spits out a static file that clj-kondo can consume, I might do that.

👍 1
ambrosebs16:05:04

looks like I forgot to add native clj-kondo support for pred, could you open an issue to remind me?

TJ Campanella17:05:26

I'm trying to use t/Merge like so:

(t/defalias UserWithMore 
(t/Merge 
  User
  (t/HMap :mandatory                                                 {:extra-field1 t/Int                                           :extra-field2 t/Int}                                                                                    :complete? true)))

TJ Campanella17:05:15

Then I get "Syntax error macroexpanding tyc/pred" in the pred function I made for that type. This is using the def as well not a macro for the pred

ambrosebs18:05:48

Could you paste the full stacktrace?

TJ Campanella18:05:45

Start checking mynamespace
Syntax error macroexpanding tyc/pred at (file:myfile.clj:174:28).
Found errors
Subprocess failed (exit code: 1)

TJ Campanella18:05:58

Thats really all it says

ambrosebs18:05:30

Ok, I'm guessing pred doesn't support Merge. This is probably one of those things that is easy to add support for in typed.malli but a pain for pred.

TJ Campanella19:05:54

Hmm ok do you think I should try out the typed Malli approach? I would just use that validate function instead of pred right?

ambrosebs19:05:14

Not really, I think it has even less coverage at the moment. I'll experiment a bit and see if I can make it more useful.

TJ Campanella19:05:55

Ok thanks I mean ideally I’d just use pred but if you think it’s too much to add merge support into it I am open to using something else in its place just let me know what you think would be my best option. And for now I’ll just not use merge yet.

TJ Campanella17:05:14

Is there any other way I can let the type checker know that i'm returning a map of a certain type? Or is t/pred the standard way to do so?

ambrosebs19:05:47

@U034ELFSHPS there's lots of ways. t/pred is just the safest, since it's backed by runtime checks.

ambrosebs19:05:42

you can use ^{::t/unsafe-cast T} E to cast the return value without checking

ambrosebs19:05:02

if postconditions are enough, you can use {:post [(something? %]}

TJ Campanella19:05:13

Yeah I prefer t/pred vs those options but good to know about. Thanks.

TJ Campanella03:05:04

Also, is there any way to do a generic Result type like in Rust? I know you can simulate a concrete Result Type like (t/U t/Int MyError) but I was wondering if theres any way to make a generic Result type or if its best to use a different approach.

ambrosebs03:05:15

you would use a tagged union in this case.

(defalias Result (TFn [x] (U '{:op ':Ok :result x} '{:op ':Err}))

ambrosebs03:05:58

then (case (:op m) :Ok .. :Err) would be the "elimination rules".

TJ Campanella13:05:20

Ok cool I haven't explored that feature yet looks pretty useful though. How would I return an Ok variant or Err variant from a function? Essentially how do I construct one of those Result types

ambrosebs14:05:05

(defn my-fn []
  (try {:op :Ok :result 1}
       (catch Exception e {:op :Err :error e})))

TJ Campanella15:05:29

Interesting i'll try this out

TJ Campanella11:08:41

I'm trying this out now and I care about the error so I made the type like this. (t/defalias Result (t/TFn [x] (t/U '{:op ':Ok :result x} '{:op ':Err :error x}))) However, the type checker cannot resolve it for some reason when I construct it like {:op :Ok :result any-value} or {:op :Err :result any-error}. I get errors like:

Type mismatch:

Expected:       my-ns/Result

Actual:         (t/HMap :mandatory {:result nil, :op (t/Val :Ok)} :complete? true)


in:
{:op :Ok, :result (:message-id response)}
Type mismatch:

Expected:       my-ns/Result

Actual:         (t/HMap :mandatory {:op (t/Val :Err), :result String} :complete? true)


in:
{:op :Err, :result full-error}
Type mismatch:

Expected:       my-ns/Result

Actual:         (t/HMap :mandatory {:result t/Any, :op (t/Val :Ok)} :complete? true)


in:
{:op :Ok, :result result}
And my-ns/Result is just an alias over the result type like (t/defalias Result types/Result) just so its shorter. I normally make all my types using HMap and not the short form syntax so I think that is where some of the issues may be. Thanks for any help.

TJ Campanella11:08:43

I also have never used t/TFn before so that may be part of the problem as well

ambrosebs16:08:49

Result here is a "type function", in this case a function that takes a type x and returns a union type.

ambrosebs16:08:25

similar to Seqable. You need to provide it a type with (Result Foo).

ambrosebs16:08:31

You probably want :error String not :error x

TJ Campanella17:08:31

Ok cool i figured it was due to me not knowing fully how that works let me test some more