hmm indeed the error is not obvious at first sight.
Polymorphic type annotations don’t work the way you think they do in Hindley-Milner type systems, you can for example do this:
let foo : 'a = 1
It’s obvious here that foo
is of type int
and not of type 'a
, but at the same time int
is indeed a possible type for 'a so it’s not wrong per se, so the type system doesn’t complain. If you want the type system to complain about such issues, you’d have to add an interface to your file / module:
module Http: {
type expect<'t> =
| ExpectJson(Js.Json.t => result<'t, string>)
| ExpectText(string => result<string, string>)
let get : expect<'t> => promise<result<'t, string>>
} = {
type expect<'t> =
| ExpectJson(Js.Json.t => result<'t, string>)
| ExpectText(string => result<string, string>)
// imagine it takes some url as well...
let get = async (expect: expect<'t>): result<'t, string> => {
switch expect {
| ExpectJson(dec) => dec(Js.Json.Null)
| ExpectText(val) => val("")
}
}
}
Here it would point to the implementation of Http and rightfully complain that:
Signature mismatch:
...
Values do not match:
let get: expect<string> => promise<result<string, string>>
is not included in
let get: expect<'t> => promise<result<'t, string>>
playground.res:13:3-53: Expected declaration
playground.res:20:7-9: Actual declaration
Indeed if you pay attention at your code, val("")
is of type result<string, string>
so during type checking (what is called “unification”), the type checker will infer that get
return type is also result<string, string>
because all branches of a switch need to return the same type.
So if get expects a function of type Js.Json => result<string, string>
, it can’t accept a function that returns result<Post.t, string>
.
Does it make sense?
Now how would you solve your situation?
You’d have two solutions, either you make your variants return result of string in both cases, but this doesn’t seem to be a solution here or you can supercharge your variant and make it a GADT (generalized algebraic data type), which allows you to make your function return different types depending on the case of the variant it receives. It’s pretty powerful but it can also be cumbersome to use because you have to help the type checker in this case:
module Post = {
type t = {userId: int}
let decode = (_t: Js.Json.t): result<t, string> => {
Ok({userId: 1}) // some decoding is happening here...
}
}
module Http = {
type rec expect<_> =
| ExpectJson(Js.Json.t => result<'t, string>): expect<'t>
| ExpectText(string => result<string, string>): expect<string>
// imagine it takes some url as well...
let get:
type t. expect<t> => promise<result<t, string>> =
async expect => {
switch expect {
| ExpectJson(dec) => dec(Js.Json.Null)
| ExpectText(val) => val("")
}
}
}
module Str = {
type t = string
let decode = s => Ok(s)
}
let post: promise<result<Post.t, string>> = Http.get(
Http.ExpectJson(Post.decode),
)
let string: promise<result<string, string>> = Http.get(
Http.ExpectText(Str.decode),
)
Playground link