Spinning off of #beginners > Code review?, there were a few messages (start, end) around how it would be nice for Num.absDiff to always return an unsigned number.
This has a few advantages:
Num.absDiff -1 Num.maxI32 would work.In the current Roc type system, Signed and Unsigned numbers are not usefully distinguishable in a related way. There is no way to know that U8 and I8 are related and transform from one to the other.
I think we should restructure the Num type to support signedness.
Current I8 looks like this:
Int range : Num (Integer range)
I8 : Int Signed8
I propose that we separate the signedness from the bitwidth. There are multiple ways to do this. The most directly probably being:
I8 : Int Signed Bits8
With that type, a user could write:
absDiff : Int sign bits, Int sign bits -> Int Unsigned bits
Of course we will need a Num.toSigned and Num.toUnsigned or similar to support this, but I think it would be a great help to the type system.
Thoughts?
I thought about this initially (it can be done with Int still having one type variable by using a record, e.g. Int { sign, bits }, so then other functions can still have Int a, Int a -> ...) but I ended up concluding it wasn't worth the complexity given how rarely it comes up
what other use cases would this have besides absDiff?
I ran into it while fuzzing, but yeah it is pretty rare.
I had a function where I wanted to be able to accept any unsigned integer as input.
Cause the function would give totally incorrect results for signed numbers.
do you remember what it was for?
Yeah, it was for generating a random integer in between two values from fuzz input. How the algorithm works, you essentially have to convert to unsigned, run the algorithm, then convert back to signed.
Actual rust algorithm for reference: https://github.com/rust-fuzz/arbitrary/blob/803f2df52fabe3a630fb054b8d7e7e2795487971/src/unstructured.rs#L303-L373
Note the to_unsigned and from_unsigned calls
Also, looking at the impl in more detail now, What the rust author did to support this was implement a trait on all the rust integer types. Technically the Roc number types are opaque. So theoretically an ability could be used if new abilities where allowed to be added to any opaque type.
yeah I strongly want to avoid that though :big_smile:
Oh, ik. Just noting how it was dealt with in rust and what a direct translation would be.
Too bad I can just comptime match on a type variable.
signed : Int a -> Bool
signed = \_ ->
comptime when a is
Signed8 | Signed16 | ... -> Bool.true
Unsigned8 | Unsigned16 | ... -> Bool.false
with compile-time evaluation of constants that would Just Work
Only if we add the ability to match on a type variable in general
oh I missed that part nm
I guess if you wanted to keep at most one type variable for simplicity, it could be:
Int a : Num (Integer a)
SInt a : Num (Integer (Signed a))
UInt a : Num (Integer (Unsigned a))
Would something along those lines work?
Of course we don't have to actually expose and SInt or UInt alias. It could be required to write
Int (Signed a)
yeah that could also work
but overall I think the bar for making such a fundamental type more complicated is higher than the number of motivating use cases right now :big_smile:
especially considering it's more work for the type checker
Well, at least we have an implementation that might work if we even gain enough motivating use cases.
Last updated: Jun 16 2026 at 16:19 UTC