Stream: beginners

Topic: Variance in type annotations for functions


view this post on Zulip Monica (Jul 30 2024 at 09:04):

Is there a way to control the subtyping behaviour of functions in roc?

I have the following function which is meant to be an or operator on a decoder. Try decoderA if that fails, then try decoderB.

or = \decoderA, decoderB ->
    \data ->
        when decoderA data is
            Ok a -> Ok a
            _ -> decoderB data

I want the return type of this function to be c, where:

c: [
    a,
    b
]

so the type annotation of or to be:
or: JsonDecoder a, JsonDecoder b -> JsonDecoder c

When I have this type annotation I get an error telling me all my branches of my when must be the same type. Is this possible in roc or am I missing something? Cheers :)

Other type defs:

JsonDecoder t : Json -> Result t DecodingErrors
Json : [
String Str,
Number U64
...etc
]

view this post on Zulip Kiryl Dziamura (Jul 30 2024 at 09:40):

I think because return types of decoderA and decoderB are different, the way to go is to use custom tags to distinguish them.
So you want the c type to be

c: [
    A a,
    B b
]

and decoderA returns [A a] and decoderB returns [B b].

view this post on Zulip Anton (Jul 30 2024 at 09:41):

@Kiryl Dziamura beat me to it but I'll leave my example here anyway :)

So, this works:

when color is
    Green -> Go
    Red -> Stop

But this does not:

when color is
    Green -> Go
    Red -> 123

But that one can be modified to:

when color is
    Green -> Go
    Red -> Stop 123

view this post on Zulip Norbert Hajagos (Jul 30 2024 at 09:42):

There isn't subtyping in Roc, tho it can look like it has. if decoderB data doesn't return the same type as decoderA data, it will be a type mismatch. You generally solve that by wrapping the returned values with a different tag for each different return value, so that the your function returns the union of those tags (tag union). In your example:

c: [
    a,
    b
]

should be:

c: [
    TagFromDecoderA a,
    TagFromDecoderB b
]

in the pattern match, you would apply the tag:

      when decoderA data is
            Ok a ->  TagFromDecoderA (Ok a)
            _ -> TagFromDecoderB (decoderB data)

Without the actual tag, you couldn't tell if your function returned type a or b. Other languages solve that by having runtime type information, but in Roc, there isn't such thing.

I haven't used decoders, but that is the general solution.
If you look at the Json tag union you pasted, it has tags "String" and "Number" (which are not some special Roc types)

view this post on Zulip Norbert Hajagos (Jul 30 2024 at 09:42):

Wow, you guys were fast :racecar:

view this post on Zulip Monica (Jul 30 2024 at 12:57):

thanks all for your answers! I think I was making it far more complicated than it needed to be...

What I was really wanting to do and managed to get working was:

or : JsonDecoder a, JsonDecoder a -> JsonDecoder a
or = \decoderA, decoderB ->
    \data ->
        when decoderA data is
            Ok _ -> decoderA data
            _ -> decoderB data

the way I call this function is by passing in values like this:

decoderA : JsonDecoder A
decoderB : JsonDecoder B

C : [
  A,
  B
]

or decoderA decoderB

view this post on Zulip Kiryl Dziamura (Jul 30 2024 at 13:01):

        when decoderA data is
            Ok _ -> decoderA data
            _ -> decoderB data

it seems you call decodeA data twice. it would probably be optimized, but I'm not sure

You can also use Result.onErr:

or : JsonDecoder a, JsonDecoder a -> JsonDecoder a
or = \decoderA, decoderB ->
    \data ->
        decoderA data |> Result.onErr \_ -> decoderB data

view this post on Zulip Monica (Jul 30 2024 at 16:56):

nice yeh your optimization works perfectly! cheers


Last updated: Jul 05 2025 at 12:14 UTC