Can any tell me why can't I pass an Int Unsigned16
into a function which requires Int *
?
This 2nd argument to addDays has an unexpected type:
109│ addDays { year: date.year + 1, month: 1, dayOfMonth: 1, dayOfYear: 0 } (newDayOfYear - daysInYear)
^^^^^^^^^^^^^^^^^^^^^^^^^
This sub call produces:
Int Unsigned16
But addDays needs its 2nd argument to be:
Int *
Can you share the function addDays
please?
addDays : Date, Int * -> Date
addDays = \date, days ->
daysInYear = if isLeapYear date.year then 366 else 365
newDayOfYear = date.dayOfYear + Num.toU16 days
if newDayOfYear > daysInYear then
addDays { year: date.year + 1, month: 1, dayOfMonth: 1, dayOfYear: 0 } (newDayOfYear - daysInYear)
else
fromYd date.year newDayOfYear
Date : { year: I64, month: U8, dayOfMonth: U8, dayOfYear: U16 }
Does it work if you change it to a
instead of *
?
Nope, no difference.
I'm not sure I can help much... it seems strange to me
Int _
should work, but so should Int *
Ahah! Int _
does work!
Maybe it's something to do with Num.toU16
... what if you change that to Num.intCast
But yeah, 99% sure that Int *
should work. Good bug to file.
Changing to intCast
does not fix, but yes, the Int _
does work.
I can definitely file a bug report for that
Reproduction
interface Test
exposes []
imports []
expect add 1 == 1
add : Int * -> _
add = \in -> add (Num.toU16 (in - 1))
edit even smaller
Awesome. Thanks for finding that minimum reproduction.
Surprised I haven't run into that bug sooner... I've been using Int *
s left and right
this isn't a bug actually!
if you accept an Int *
argument, that means you can pass any size int into the function and it will work
so let's say I pass 1i32
in, for example (because Int *
says I'm allowed to do that)
now the body of the function is 1i32 + 1u16
which is a type mismatch
so I think a more helpful error message would say something like:
This type annotation says the function can accept any
Int
, but the implementation only works withU16
.If you used any integer other than
U16
here, the implementation would have a type mismatch, but
the type annotation incorrectly says other integer types would work.Hint: Try changing
Int *
toU16
, or changing the implementation to no longer useU16
wherever it currently uses it.
I don't think this is the same @Richard Feldman
This is U16 + (Num.toU16 (Int *))
So should always type check, right?
Cause no matter the input int type, you convert it to U16
As long as the function can specialize multiple times for different Int *
, it should be totally fine.
sorry, the original one looks like it should work (so there's a bug there) but I wouldn't expect the minimal repro to work (although it's been edited since my previous response)
I totally understand why this looks like it should work:
add : Int * -> _
add = \in -> add (Num.toU16 (in - 1))
because in the mental model of "Int *
means it accepts any integer" it should totally work
however, that's not quite what Int *
means
it's very close to what it means, but not quite
and I think the fact that it's recursively calling itself with a narrower type than Int *
(namely U16
) would be an error in a perfect implementation of Roc's type system
but @Ayaz Hafiz would know better (and probably be able to explain better) than I would - I might be wrong about that!
yeah, the reason
add : Int * -> _
add = \in -> add (Num.toU16 (in - 1))
doesn't work is that it demand polymorphic recursion - regardless of what you pass in for the first add
, the recursive call to add
must be U16
. And Roc does not support polymorphic recursion. That is, a recursive call must be as generic as the initial call, so in this case the recursive call to add
must be made with the same type as in
.
This was my intuition for the issue and why I wrote the repro that way. Is it the same issue as the original report?
The original is also passing a U16
as the second argument to addDays
yes, I think so
ahh I missed the recursion in the original because it's further down than where the conversion to U16
happens - makes sense now!
Thanks for taking a look at this, all. Good to know its just PEBCAK! :sweat_smile:
I want my API to be flexible on argument types, so I rewrote with a helper method to get out of the polymorphic recursion issue.
this definitely seems like an opportunity for more helpful error messaging though!
Why don't we have polymorphic recursion here? Feels like after the first call it will settle into a base case
I feel like polymorphic recursion makes sense to support.
But maybe it leads to pains due to tail recursion
I'm not sure how feasible it is to support without a totally different type system - that's another @Ayaz Hafiz question :big_smile:
yeah polymorphic recursion is not a good idea
I don't think the use cases are really there and it makes a ton of code generation really hard
it also requires a certain expansion to the type system
Side question: could the initial function be made to work by adding a Num.intCast
to go from U16
back to the original Int *
type?
I think yes
Tested and confirmed, intCast will allow the original recursive form.
Ian McLerran has marked this topic as resolved.
I wonder if we can capture this somehow? Maybe an advanced section in Tutorial on recursion?
Last updated: Jul 06 2025 at 12:14 UTC