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.
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)
]
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.
(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)
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.
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?
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].
yep, sounds good! :thumbs_up:
same for the other number types
Good, I will make an issue
actually, sorry - we should not add it to I128 or U128 or I64
because those are lossy conversions
those should have to_u64_try
But each up to their own n right?
yeah, taking into account signed vs unsigned
e.g. I64.to_u64 and U64.to_i64 shouldn't exist because they are lossy
the "just trust me I know what I'm doing" versions would be I64.to_u64_wrap and U64.to_i64_wrap
For clarity, we already have the wrap functions.
Do we want to do to_<self>_try as well?
sure, just have it return Try(MySpecificType, _never_fails)
As these are all no-ops, should these be done in Roc or piped through low level and codegen?
I think they can just be pure Roc since all they do is return self or Ok(self) :smile:
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?
sure!
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:
from dual implementations for each number type in builtin - not great, as it would be a (controlled) explosion in the number of builtin methods. Would be nice to have a from on any foreign type if your type implements the to?permute returns U32 because the state is represented as U32, so a generic permute would require a generic state, and I believe the algorithms use hardcoded constants that relate to the state (e.g., use the top 5 bits for 32-bit state). I think this would also mean you need a different generator for each output type.to_str/from_str - a bit of a hack.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?
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 }
}
}
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?
Communication on my part :sweat_smile:
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.
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