From the iterator discussion, I am questioning if certain features we are taking advantage of are meant to work in Roc or if they accidentally happened to work? Also, if they accidentally happened to work, do we actually want them to work?
Fundamentally, this is around higher kinded types and abilities. I feel like some of what works today is equivalent to using higher kinded types but with a worse api that fails to track the higher kinded relationships.
As the most simple example:
SequenceWalker seq state elem : seq, state, (state, elem -> state) -> state
This sequence walker type really should be a higher kinded realationship. It should be a sequence of elem type. Instead we have a fully opaque sequence. We then introduce an elem type that is not anchored to anything in the expression. To me, this strictly feels like failing to fully map out the relationship between seq
and elem
. This has the same features as a higher kinded type would give without any of the type information. Do we gain anything from banning higher kinded types here?
This second example is a continution with something that works as a iterable:
Walker state elem : state, (state, elem -> state) -> state
Iterable implements
createWalkFn : iter -> Walker state elem where iter implements Iterable
This Iterable api works. That said, it is clearly lacking information. We have no indication that the iterator is over the correct element type. Roc will still end up properly specializing and type checking this code, but it fundamentally feels wrong to me.
I guess my questions are really two fold:
Iterable
should be able to walk over any element type. This is obviously not correct. An iterable can only walk over the specific element type that it is generating. A List Int
and can support a Str
walking function, but this api suggest that is valid.Something just rings wrong to me here. There is a relationship between the sequence/iterator and the element type, but it is not represented in these apis despite definitely mattering.
Yeah, I think I see the error I would expect. Looking at SequenceWalker vs List.walk:
SequenceWalker seq state elem : seq, state, (state, elem -> state) -> state
List.walk : List elem, state, (state, elem -> state)-> state
I think there should be an error that say the function List.walk
is not general enough to satisfy SequenceWalker
. SequenceWalker
requires a function seq, state, (state, elem -> state) -> state
where there is no relationship between seq
and elem
. List.walk
requires a relationship between seq
(which is List elem
) and elem
(which is still elem
). So List.walk
is not general enough to be a SequenceWalker
.
Not saying this is a useful error to produce. It may be better that we allow this kind of relationship. But I do think it is a form of allowing higher kinded types while hiding them from the type system.
For reference, I just took two examples of monads from https://blog.oliverbalfour.com/haskell/2020/08/08/understanding-monads-haskell.html and implemented them in Roc.
To my understanding, this shouldn't be possible in Roc, but it works today.
Monad Ability with Option and List monad
this sounds familiar - I remember @Ayaz Hafiz mentioning some time ago that there was a missing type constraint somewhere in abilities
actually I think it was in the context of my having tried something similar with streams/iterators :laughing:
and ending up with something that seemed to work but shouldn't
Also, I feel like I may be pointing out two different issue but I am not fully sure. There is a chance that the SequenceWalker
and List.walk
part should work, but something more flexible in my Monad
example should fail to typecheck.
I mean, obviously in the monad example as written, it is using a unconstrained type variable as a Monad
. That is definitely incorrect. If ToMonad
is added in though and used in the example code, I think that all of the types technically work.
Actually no, even with that change the types technically wouldn't work. mb
only implements ToMonad
but ToMonad
would require it to implement Monad
and ToMonad
.
Yeah, so this monad example written either way definitely should not work. It is adding abilities to unconstrained type variables.
So yeah, monad version definitely should be broken in type checking.
For SequenceWalker
vs List.walk
. I don't think the types are technically wrong anywhere. They are just overly generalizing and removing a piece of information. So if you were to type check only based on SequenceWalker
, it would allow for types that are invalid for List.walk
.
Aside, if we stop SequenceWalker
from working, it will definitely limit possibilities for the encoding/decoding api.
I think the root of all of these bugs is that typechecking is done using the concrete types from resolving the ability. This gives way more information that what is specified in the interface of the ability.
Last updated: Jul 06 2025 at 12:14 UTC