Problem with inferred type

I have the following snippet with full type annotation.

type context = string

type result<'a> =
    | Ok({parsed: 'a, newContext: context})
    | Err(string)

let map = (r: result<'a>, fn: 'a => 'b): result<'b> =>
    switch r {
    | Ok({parsed, newContext}) => Ok({parsed: fn(parsed), newContext: newContext})
    | e => e
    }

let flatMap = (r: result<'a>, fn: ('a, context) => result<'b>): result<'b> =>
    switch r {
    | Ok({parsed, newContext}) => fn(parsed, newContext)
    | e => e
    }

I expect map to have type (result<'a>, 'a => 'b) => result<'b> and flatMap to have type (result<'a>, ('a, context) => result<'b>) => result<'b>. However, the compiler insists that map has type (result<'b>, 'b => 'b) => result<'b> and flatMap has type (result<'b>, ('b, context) => result<'b>) => result<'b>.

Could anyone please show me what I’m doing wrong here?

The error cases in both switch statements are wrong. They return the original value - result<‘a> - not the type you’re expecting. You need to re-wrap the string in a new Error constructor to create a new value of a different type.

For example, the Belt.Result.map implementation:

3 Likes

That was the issue. Thank you!

The fact that the compiler does not warn/throw error about this is counterintuitive to me, though.

Polymorphic type annotations in the implementations actually are not universally quantified. This means that, even though you said that these functions have two type parameters 'a and 'b, if you get the implementation wrong the compiler will happily unify the type parameters to 'b like you saw in your case.

The best way to avoid this error is to actually not annotate the implementation, and put the type annotations in an interface file, where type parameters will be universally quantified.

By the way, you don’t really need this custom result type, you can alias the existing one, e.g.

type ok<'a> = {parsed: 'a, newContext: context}
type myResult<'a> = result<ok<'a>, string>

EDIT: you can see this happening in the error message that happens when you use an interface: https://rescript-lang.org/try?code=LYewJgrgNgpgBAFRgZwC4C44G8BQc6oCeADvAMYgB2qMAHqnALxxoBOAlpQOY54EnxWKaKgA8AcgCGAPiZ8APnADyAawAUWYpNbIYYTFIA0cSjADuAYSo16mCtTqoAvgEoFcAKKtWatpy5u+HywDMCSxJhqQsgiEjLGUkyy4gBGLklw0bGp0sEwDABmUJKoALLhkVlQYlLSxmpGcPY2qOmMslU1KdJtHcLVEt04TkzYfESkTdaOo37cvPgTgv01MnL4iqoaWjp6BpLGppbTtlMO9K7uXj5zAQtwIXBhxKNRmJ1xdXAFlPsZqS53itBrJ2nx8MgzOxUGQABaZMb4DbKdSabS6MCHcxWc7OXoo7bovbfShqHYYlxY464zBHHEtS5IuCKeDtOAwcFwJz3R5FErlF7MN6ZYG1Yw-SKNZqOfEfHKAkUxAY5JKcyHQuEI3BMzao8l6Kn0xyuDI-MlEzEmbEnVqclkZDlM7lOIA

2 Likes

This was going to be my next point :slight_smile:

I see inline type annotations a lot in people coming from TypeScript, in ReScript they’re almost always unnecessary on function arguments and they’re both unnecessary and misleading on return types. This is why I recommend avoiding them.

2 Likes