Stream: beginners

Topic: ✔ Int U16 is invalid argument for function expecting Int * ?


view this post on Zulip Ian McLerran (Apr 22 2024 at 00:08):

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 *

view this post on Zulip Luke Boswell (Apr 22 2024 at 00:09):

Can you share the function addDays please?

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:09):

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

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:09):

Date : { year: I64, month: U8, dayOfMonth: U8, dayOfYear: U16 }

view this post on Zulip Luke Boswell (Apr 22 2024 at 00:10):

Does it work if you change it to a instead of *?

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:11):

Nope, no difference.

view this post on Zulip Luke Boswell (Apr 22 2024 at 00:11):

I'm not sure I can help much... it seems strange to me

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 00:11):

Int _ should work, but so should Int *

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:12):

Ahah! Int _ does work!

view this post on Zulip Luke Boswell (Apr 22 2024 at 00:12):

Maybe it's something to do with Num.toU16... what if you change that to Num.intCast

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 00:12):

But yeah, 99% sure that Int * should work. Good bug to file.

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:13):

Changing to intCast does not fix, but yes, the Int _ does work.

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:14):

I can definitely file a bug report for that

view this post on Zulip Luke Boswell (Apr 22 2024 at 00:17):

Reproduction

interface Test
    exposes []
    imports []

expect add 1 == 1

add : Int * -> _
add = \in -> add (Num.toU16 (in - 1))

edit even smaller

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:19):

Awesome. Thanks for finding that minimum reproduction.

view this post on Zulip Ian McLerran (Apr 22 2024 at 00:20):

Surprised I haven't run into that bug sooner... I've been using Int *s left and right

view this post on Zulip Richard Feldman (Apr 22 2024 at 00:39):

this isn't a bug actually!

view this post on Zulip Richard Feldman (Apr 22 2024 at 00:39):

if you accept an Int * argument, that means you can pass any size int into the function and it will work

view this post on Zulip Richard Feldman (Apr 22 2024 at 00:39):

so let's say I pass 1i32 in, for example (because Int * says I'm allowed to do that)

view this post on Zulip Richard Feldman (Apr 22 2024 at 00:39):

now the body of the function is 1i32 + 1u16 which is a type mismatch

view this post on Zulip Richard Feldman (Apr 22 2024 at 00:42):

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 with U16.

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 * to U16, or changing the implementation to no longer use U16 wherever it currently uses it.

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 01:07):

I don't think this is the same @Richard Feldman

This is U16 + (Num.toU16 (Int *))

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 01:07):

So should always type check, right?

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 01:08):

Cause no matter the input int type, you convert it to U16

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 01:08):

As long as the function can specialize multiple times for different Int *, it should be totally fine.

view this post on Zulip Richard Feldman (Apr 22 2024 at 02:03):

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)

view this post on Zulip Richard Feldman (Apr 22 2024 at 02:04):

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

view this post on Zulip Richard Feldman (Apr 22 2024 at 02:05):

however, that's not quite what Int * means

view this post on Zulip Richard Feldman (Apr 22 2024 at 02:05):

it's very close to what it means, but not quite

view this post on Zulip Richard Feldman (Apr 22 2024 at 02:05):

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

view this post on Zulip Richard Feldman (Apr 22 2024 at 02:09):

but @Ayaz Hafiz would know better (and probably be able to explain better) than I would - I might be wrong about that!

view this post on Zulip Ayaz Hafiz (Apr 22 2024 at 02:33):

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.

view this post on Zulip Luke Boswell (Apr 22 2024 at 02:39):

This was my intuition for the issue and why I wrote the repro that way. Is it the same issue as the original report?

view this post on Zulip Luke Boswell (Apr 22 2024 at 02:40):

The original is also passing a U16 as the second argument to addDays

view this post on Zulip Ayaz Hafiz (Apr 22 2024 at 02:45):

yes, I think so

view this post on Zulip Richard Feldman (Apr 22 2024 at 03:38):

ahh I missed the recursion in the original because it's further down than where the conversion to U16 happens - makes sense now!

view this post on Zulip Ian McLerran (Apr 22 2024 at 13:56):

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.

view this post on Zulip Richard Feldman (Apr 22 2024 at 15:02):

this definitely seems like an opportunity for more helpful error messaging though!

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 15:05):

Why don't we have polymorphic recursion here? Feels like after the first call it will settle into a base case

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 15:05):

I feel like polymorphic recursion makes sense to support.

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 15:06):

But maybe it leads to pains due to tail recursion

view this post on Zulip Richard Feldman (Apr 22 2024 at 15:59):

I'm not sure how feasible it is to support without a totally different type system - that's another @Ayaz Hafiz question :big_smile:

view this post on Zulip Ayaz Hafiz (Apr 22 2024 at 16:13):

yeah polymorphic recursion is not a good idea

view this post on Zulip Ayaz Hafiz (Apr 22 2024 at 16:14):

I don't think the use cases are really there and it makes a ton of code generation really hard

view this post on Zulip Ayaz Hafiz (Apr 22 2024 at 16:15):

it also requires a certain expansion to the type system

view this post on Zulip Brendan Hansknecht (Apr 22 2024 at 17:22):

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?

view this post on Zulip Ayaz Hafiz (Apr 23 2024 at 02:06):

I think yes

view this post on Zulip Ian McLerran (Apr 25 2024 at 17:58):

Tested and confirmed, intCast will allow the original recursive form.

view this post on Zulip Notification Bot (Apr 25 2024 at 17:58):

Ian McLerran has marked this topic as resolved.

view this post on Zulip Luke Boswell (Apr 25 2024 at 20:48):

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