Stream: beginners

Topic: Alternative to Int *


view this post on Zulip Jonathan (Jun 25 2026 at 15:06):

I'm working on porting roc-random and came across list which is of type

list : Generator a, Int * -> Generator (List a)

The Int * is used to generate "n" random numbers. For comparison, Roc now uses the following signature for List.repeat:

repeat : a, U64 -> List(a)

This seems quite specific and, e.g., won't accept U32 without .to_u64 first. Equally, I'm not sure

repeat : a, n -> List(a) where [n.to_u64 : n -> u64]

is the clearest signature. After all, I don't think U64 currently implements to_u64.

view this post on Zulip Jonathan (Jun 25 2026 at 15:10):

I'm still not sure how numeral parsing works, but I'm guessing there's also an argument to be made to restrict it to something like the below because that is what is needed, but it doesn't communicate as clearly to me that "This should be an unsigned integer" and also leaks the implementation. If the function is changed so that the destination list is reserved ahead of time, n must now implement also n.to_u64, so that List.with_capacity can be used, which then excludes using U64 itself.

list : Generate(a), n -> Generate(List(a))
  where [
    n.to : n, n -> Iter(n),
    # something that allows us to parse '0' to type n so that we can do 0.to(n)
]

view this post on Zulip Jonathan (Jun 25 2026 at 15:12):

I haven't got to testing it yet, and I could probably leave it without a signature and it will Just Work, but I don't know what the best signature would be for e.g., someone reading documentation. Probably U64? Unless U64 and other numerals come to implement redundant [UI][n].to_[ui][n] methods.

view this post on Zulip Jonathan (Jun 25 2026 at 15:18):

(Side note - is there a way to inspect the type of something like _ type holes? I think before I've just put a blatantly wrong annotation and looked for the compiler error)

view this post on Zulip Anton (Jun 26 2026 at 12:08):

Jonathan said:

(Side note - is there a way to inspect the type of something like _ type holes? I think before I've just put a blatantly wrong annotation and looked for the compiler error)

You can use the LSP but it still has some bugs.

view this post on Zulip Jonathan (Jun 26 2026 at 13:47):

Unless U64 and other numerals come to implement redundant [UI][n].to_[ui][n] methods.

I'll set it as U64 then :+1: Would it make sense to have these? Just so that whenever you need an integer you can get anything that can be turned into it including itself?

view this post on Zulip Anton (Jun 26 2026 at 14:24):

It seems reasonable. What do you think @Richard Feldman? To summarize, the proposal is to add e.g. to_u64 to U64, so that you can make a function with where [n.to_u64 : n -> U64].

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:25):

yep, sounds good! :thumbs_up:

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:25):

same for the other number types

view this post on Zulip Anton (Jun 26 2026 at 14:25):

Good, I will make an issue

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:25):

actually, sorry - we should not add it to I128 or U128 or I64

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:26):

because those are lossy conversions

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:26):

those should have to_u64_try

view this post on Zulip Jonathan (Jun 26 2026 at 14:26):

But each up to their own n right?

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:26):

yeah, taking into account signed vs unsigned

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:27):

e.g. I64.to_u64 and U64.to_i64 shouldn't exist because they are lossy

view this post on Zulip Richard Feldman (Jun 26 2026 at 14:27):

the "just trust me I know what I'm doing" versions would be I64.to_u64_wrap and U64.to_i64_wrap

view this post on Zulip Anton (Jun 26 2026 at 14:29):

For clarity, we already have the wrap functions.

view this post on Zulip Anton (Jun 26 2026 at 14:37):

#9830

view this post on Zulip Anton (Jun 26 2026 at 14:39):

Do we want to do to_<self>_try as well?

view this post on Zulip Richard Feldman (Jun 26 2026 at 16:32):

sure, just have it return Try(MySpecificType, _never_fails)

view this post on Zulip Jonathan (Jun 29 2026 at 22:25):

As these are all no-ops, should these be done in Roc or piped through low level and codegen?

view this post on Zulip Richard Feldman (Jun 30 2026 at 00:29):

I think they can just be pure Roc since all they do is return self or Ok(self) :smile:

view this post on Zulip Jonathan (Jun 30 2026 at 17:04):

One other thing - I just realised to_str was not defined for Str. Shall I tack it onto the PR while I'm at it?

view this post on Zulip Richard Feldman (Jun 30 2026 at 17:06):

sure!

view this post on Zulip Jonathan (Jul 01 2026 at 12:27):

This is a continuation of a similar problem, so adding it on here. I'm midway through converting roc-random and it's a little tricky doing it faithfully in some cases that depended on generic Int behaviour.

between_unsigned : Int a, Int a -> Generator (Int a)
between_unsigned = |x, y|
    (minimum, maximum) = sort(x, y)
    range = maximum - minimum |> Num.add_checked(1)

    |s|
        # TODO: Analyze this. The mod-ing might be biased towards a smaller offset!
        value =
            when range is
                Ok(r) -> minimum + (Num.int_cast(permute(s))) % r
                Err(_) -> permute(s) |> Num.int_cast
        state = update(s)

        { value, state }

The lines I'm thinking about have Num.int_cast. permute returns a U32, and so we need a way of converting U32 -> generic "int". We have U32.to_u8_wrap, U32.to_u16_wrap etc., but in this case we don't know the return type and have no way to parameterise over it, nor have the dual to_ conversions <Num>.from_u32_wrap, <Num>.from_u16_wrap etc.

Ideas so far:

The fundamental shape of it is that we have a U32 core that needs to output generic integer types. Is there something obvious or more out of the box I'm missing?

view this post on Zulip Jonathan (Jul 01 2026 at 12:30):

I mean, there is one solution in passing in a from_u32 : U32 -> a function, but was wondering if there was a dispatch-y way to do it that mirrors the generic behaviour that Int * permitted before.

between_unsigned = |x, y, from_u32| {
    (minimum, maximum) = sort(x, y)
    range = (maximum - minimum).add_try(1)

    |s| {
        # TODO: Analyze this. The mod-ing might be biased towards a smaller offset!
        value = match range {
            Ok(r) -> minimum + from_u32(permute(s)).mod_by(r)
            Err(_) -> from_u32(permute(s))
        }
        state = update(s)

        { value, state }
    }
}

view this post on Zulip Anton (Jul 01 2026 at 12:56):

Looks like there may have been a communication issue, roc-random was migrated yesterday https://github.com/kili-ilo/roc-random/pull/51. Or perhaps it was not completely migrated?

view this post on Zulip Jonathan (Jul 01 2026 at 12:58):

Communication on my part :sweat_smile:

view this post on Zulip Jonathan (Jul 01 2026 at 13:01):

Ah well, still it's interesting to me that the solution there was to use between_u8, between_16 etc. and duplicate the function logic.

view this post on Zulip Luke Boswell (Jul 01 2026 at 21:18):

Sorry I didnt realise :smiling_face_with_tear:. I just launched into that while on a roll with path/http/ansi/parser. I havent spent a lot of time thinking about the API, it was very much my goal to get something working as a first pass and then polish it later.


Last updated: Jul 23 2026 at 13:15 UTC