Stream: roctoberfest

Topic: 2023 day 4


view this post on Zulip Elias Mulhall (Oct 04 2023 at 02:22):

I'm working on day 4 and can't figure out a type error
The full wip code is here https://github.com/mulias/roctoberfest/blob/main/advent_2022/day_04/main.roc
The relevant parts are

rangeParser : Parser RawStr (Range Nat)
rangeParser =
    const (\first -> \last -> Range.new first last)
    |> keep digits
    |> skip (codeunit '-')
    |> keep digits

# Copied from roc-parser for debugging
digits : Parser RawStr Nat
digits =
    oneOrMore digit
    |> map \ds -> List.walk ds 0 (\sum, d -> sum * 10 + d)

Range is defined in a different module

Range a : { first : Num a, last : Num a } where a implements Bool.Eq

new : Num a, Num a -> Range a
new = \a, b -> { first: a, last: b }

The error is

── TYPE MISMATCH ──────────────────────────────────────────────────── main.roc ─

Something is off with the body of the rangeParser definition:

50│   rangeParser : Parser RawStr (Range Nat)
51│   rangeParser =
52│>      const (\first -> \last -> Range.new first last)
53│>      |> keep digits
54│>      |> skip (codeunit '-')
55│>      |> keep digits

This Core.keep call produces:

    Parser (List (Int Unsigned8)) (Range (Integer Natural))

But the type annotation on rangeParser says it should be:

    Parser (List (Int Unsigned8)) (Range Nat)

view this post on Zulip Elias Mulhall (Oct 04 2023 at 02:28):

Am I using Num wrong? I'm confused that digits : Parser RawStr Nat seems correctly typed, but something is going wrong with keep?
keep is

keep : Parser input (a -> b), Parser input a -> Parser input b

view this post on Zulip Elias Mulhall (Oct 04 2023 at 02:31):

Now I'm just rubber ducking :)
I tried typing the curried function

rangeBuilder : Nat -> Nat -> Range Nat
rangeBuilder = \first -> \last -> Range.new first last

but that's giving me

── NOT END OF FILE ────────────────────────────────────────────────── main.roc ─

I expected to reach the end of the file, but got stuck here:

50│  rangeBuilder : Nat -> Nat -> Range Nat
                               ^

view this post on Zulip Brendan Hansknecht (Oct 04 2023 at 02:32):

Can you try:

Range a : { first : a, last : a } where a implements Bool.Eq

new : Num a, Num a -> Range (Num a)
new = \a, b -> { first: a, last: b }

view this post on Zulip Brendan Hansknecht (Oct 04 2023 at 02:34):

Also, just a note on the currying, you need parens in roc:
rangeBuilder : Nat -> (Nat -> Range Nat)

view this post on Zulip Elias Mulhall (Oct 04 2023 at 02:42):

Thanks for the hint, I'm getting slightly different errors now which I can bang on.
Is there going to be a separate ability for Ord or something similar? That implementation of Range doesn't seem right with just Eq

view this post on Zulip Brendan Hansknecht (Oct 04 2023 at 02:46):

I'm not sure. No one has talked about it. Sounds reasonable to have. Also, I guess your first version should probably work, though not sure what exactly is going on.

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:03):

I think richard has mentioned Sort ability which is similar to Rust's Ord

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:06):

@Richard Feldman mentions it in the Abilities proposal:

I prefer the name Sort because I think it should only be for sorting and not general ordering (e.g. I think the <, >, <=, and >= operators should continue to only accept numbers, not other sortable types like strings and booleans)

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:07):

But that's not implement yet right?

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:08):

I tried following the errors and changed all the types from Range Nat to Range (Integer Natural), and I got a compiler exception :upside_down: so I guess I'll report that. Probably should just specialize the Range struct instead of making it polymorphic

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:08):

Yeah that's right. It's not in the builtins and I don't think anyone has started on it. Not sure if there is a more concrete proposal anywhere.

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:10):

What about something like Range U32?

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:20):

── TYPE MISMATCH ──────────────────────────────────────────────────── main.roc ─

Something is off with the body of the rangeParser definition:

52│   rangeParser : Parser RawStr (Range U32)
53│   rangeParser =
54│>      const (\first -> \last -> Range.new first last)
55│>      |> keep digits
56│>      |> skip (codeunit '-')
57│>      |> keep digits

This Core.keep call produces:

    Parser (List (Int Unsigned8)) (Range (Integer Natural))

But the type annotation on rangeParser says it should be:

    Parser (List (Int Unsigned8)) (Range U32)

I tried

rangeParser : Parser RawStr (Range U32)
rangeParser =
    const (\first -> \last -> Range.new (Num.intCast first) (Num.intCast last))
    |> keep digits
    |> skip (codeunit '-')
    |> keep digits

and got

── TYPE MISMATCH ──────────────────────────────────────────────────── main.roc ─

Something is off with the body of the rangeParser definition:

52│   rangeParser : Parser RawStr (Range U32)
53│   rangeParser =
54│>      const (\first -> \last -> Range.new (Num.intCast first) (Num.intCast last))
55│>      |> keep digits
56│>      |> skip (codeunit '-')
57│>      |> keep digits

This Core.keep call produces:

    Parser (List (Int Unsigned8)) (Range (Integer b)) where b implements Eq

But the type annotation on rangeParser says it should be:

    Parser (List (Int Unsigned8)) (Range U32)

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:24):

Have you got roc check Range.roc happy?

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:26):

I got it working fine with Range : { first : Nat, last : Nat }, so at least there's that. I'll go back and see

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:30):

@Luke Boswell roc check Range.roc came back clean, so it seems like Range is internally consistent.

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:30):

I don't know if this helps, but modified to an Opaque type and gave it the Eq ability

interface Range exposes [Range, new, min, max, intersection, isContained] imports []

Range := { first : U32, last : U32 } implements [Eq { isEq: rangeIsEq }]

rangeIsEq : Range, Range -> Bool
rangeIsEq = \@Range ra, @Range rb -> ra == rb

new : U32, U32 -> Range
new = \first, last -> @Range { first, last }

min : Range -> U32
min = \@Range { first, last } -> if first <= last then first else last

max : Range -> U32
max = \@Range { first, last } -> if first <= last then last else first

intersection : Range, Range -> [Disjoint, Intersecting (Range)]
intersection = \rangeA, rangeB ->
    if min rangeA > max rangeB || min rangeB > max rangeA then
        Disjoint
    else
        first = Num.max (min rangeA) (min rangeB)
        last = Num.min (max rangeA) (max rangeB)
        Intersecting (new first last)

expect intersection (new 0 5) (new 1 3) == Intersecting (new 1 3)

isContained : Range, Range -> Bool
isContained = \inner, outer ->
    intersection inner outer == Intersecting inner

expect new 1 3 == @Range {first: 1, last: 3}

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:32):

Also works ok with Nat which is probably a nicer integration with the parser

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:32):

ohhh nice. I would expect that to work, since Range is no longer polymorphic. But I briefly tried to make an opaque type and couldn't get the syntax right so that helps

view this post on Zulip Luke Boswell (Oct 04 2023 at 03:40):

I've played around with it a bit and I am not sure if there is any way to constrain that type variable to be an Integer or Number. @Ayaz Hafiz could probably advise on that??

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:44):

numbers are def not the only thing you could have a range of, so I think that's fine. What I would really want would be an ability for comparison.

view this post on Zulip Ayaz Hafiz (Oct 04 2023 at 03:45):

Yeah unfortunately there is no way to constrain a type variable to be numeric. I think this should all work correctly if the appropriate abilities were available.

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:50):

That makes sense! The errors were confusing but I've learned something.

view this post on Zulip Elias Mulhall (Oct 04 2023 at 03:51):

Day 4 https://github.com/mulias/roctoberfest/tree/main/advent_2022/day_04


Last updated: Jul 06 2025 at 12:14 UTC