Hi. I'm curious if Roc equivalent of this OCaml code works in Roc. I wanted to try it myself, but I'm unable to run anything. ("missing header" or something like that)
type ('ok, 'err) result =
| Ok of 'ok
| Err of 'err
let handle (x : (int, [`A | `B | `C | `D]) result) : (int, [`B | `D]) result =
match x with
| Err(`A) -> Ok(1)
| Err(`C) -> Ok(2)
| other -> other (* doesn't work, `other` has the same type as `x` *)
As far as I understand tags in Roc are the same in polymorphic variants in OCaml. I'm wondering if this kind of thing ports directly to Roc, and whether it has the same limitation as OCaml.
For the "missing header" I suspect that you need an app header like in helloWorld. When writing a Roc program I usually start from helloWorld.roc.
I'll look into improving that error because it is almost impossible for a beginner to create a working program with only the info provided in that error message.
https://github.com/roc-lang/examples/issues/182
Yeah, we hit the same problem as in OCaml:
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
import pf.Stdout
import pf.Task
main =
Stdout.line! "Hello, World!"
ErrType : [A, B, C, D]
handle : [Ok U64, Err ErrType] -> [Ok U64, Err [B, D]]
handle = \x ->
when x is
Err A -> Ok 1
Err C -> Ok 2
other -> other
Error message:
── TYPE MISMATCH in examples/helloWorld.roc ────────────────────────────────────
Something is off with the body of the handle definition:
11│ handle : [Ok U64, Err ErrType] -> [Ok U64, Err [B, D]]
12│ handle = \x ->
13│> when x is
14│> Err A -> Ok 1
15│> Err C -> Ok 2
16│> other -> other
This when expression produces:
[Err [
A,
B,
C,
D,
], …]
But the type annotation on handle says it should be:
[Err [
B,
D,
], …]
────────────────────────────────────────────────────────────────────────────────
It's possible that we improve this in the future but I don't know how complicated the required compiler changes would be.
Wait, I don't understand... Why is this code incorrect?
Yeah well it's definitely something that could work but the compiler is currently not "smart" enough to figure that out.
Okay, thanks
Thanks for the answer.
osa1 has marked this topic as resolved.
It's possible that we improve this in the future
@Anton Do you mean you know how to improve the type checker or inference algorithm to make this work, or are you guessing that it may be possible?
I'm curious how you would go about improving this. I don't know too much about Roc's type system, but I'm assuming some kind of constraint-based HM formulation, with unification for solving the constraints, and row types. I'm curious how you would make this work in such a system if you already know.
I'm guessing that it is possible. @Ayaz Hafiz will probably know this.
Roc does not have gradual types
This should not work (maybe it will change one day, bit I don't think there are any plans currently)
The type of other
is the same type as x
That code is equivalent to doing:
ErrType : [A, B, C, D]
handle : [Ok U64, Err ErrType] -> [Ok U64, Err [B, D]]
handle = \x ->
when x is
Err A -> Ok 1
Err C -> Ok 2
_ -> x
x
is a [Ok I64, Err ErrType]
so returning x
must also return a [Ok I64, Err ErrType]
. It is still the same type, it just won't contain an Err A
or Err C
at runtime.
To fix it you would have to manually speed out the different variants to avoid returning x
. That might be a sugar worth adding to when ... is
explicitly.
ErrType : [A, B, C, D]
handle : [Ok U64, Err ErrType] -> [Ok U64, Err [B, D]]
handle = \x ->
when x is
Err A -> Ok 1
Err C -> Ok 2
Err B -> Err B
Err D -> Err D
Ok v -> Ok v
I don't know what this kind of typing would be called but I'm pretty sure it has nothing to do with gradual typing, which is about mixing dynamic and static typing (see wiki page).
Ayaz has a plan to fix this
I forget where it’s tracked
it doesn’t require gradual typing - it’s about the pattern match in the branch affecting the type of the branch body based on the other branches, plus needing to silently convert from one in-memory representation to another in that branch
but yeah we definitely want this to work as described!
Sorry, I think it may be refinement types or something.....too many names
“Type refinement” I think might have been the name Ayaz used
or “narrowing” maybe
And yeah, we could make this automatic/implicit (which sounds like what is planned). Personally, I would rather it be explicit causing implicit means unexpected data movement. But this is probably a case where convenience outweighs any performance concerns.
Thanks. If anyone knows where this is tracked I would be happy to follow :grinning:
Last updated: Jul 06 2025 at 12:14 UTC