I am writing a "stream" type (Stream s a
) that has a value type a and an internal state type s. I have no problem connecting these things to one another most of the time, but I ran into trouble when I tried to use an if expression: if condition stream1 else stream2
fails if stream1
and stream2
have different internal state types. Is it possible to resolve or work around this (somehow convince roc that the internal types are inaccessible and thus safe to use? Or would such behaviour make essential type inference impossible?
Perhaps I have to make an "if" operation on streams that produces a stream whose internal type is a tagged union of the internal types of the original two?
Perhaps I have to make an "if" operation on streams that produces a stream whose internal type is a tagged union of the internal types of the original two?
I think it is this
If they have different state types, that means Roc can't store them in the same variable. They are different types.
Potentially something like what is done with error handling could work though
Where the stream internal state would be a tag by default. Then Roc would be able to unify the two different streams in the if.
So even if a stream only has on possible internal state type, it would still be Stream s [IntState I32]*
So then it can unify with Stream s [FloatState F32]*
to make Stream s [IntState I32, FloatState F32]
Streams also encapsulate a "next" function whose type depends on the state's type (turns a state into what to do next), so unification of the "next" functions probably can't happen automatically (though maybe tag the combination of next and state?). Is there a way to automatically generate tags? I guess the compiler will tell me if the ones I choose aren't unique enough (that is, unique within their scope; maybe not hard?)
I did make an "either" object to produce a stream that is one of two streams based on a flag, and it seems to work, but it's clunky to work with and the inner loop is stuck doing type dispatch.
Yeah, I don't think roc userland currently has (m)any solutions for this.
did make an "either" object to produce a stream that is one of two streams based on a flag, and it seems to work, but it's clunky to work with and the inner loop is stuck doing type dispatch.
So really you should be able to pick one and always dispatch to it, but currently you have a branch repeated every time in the loop. Interesting.
What actually is the state?
I wonder if it is just the case that you need the state to implement an ability. Then the ability will deal with all dispatch.
Brendan Hansknecht said:
Yeah, I don't think roc userland currently has (m)any solutions for this.
Upon reflection, I think I could hide the type inside a closure: instead of having a state and a function that advances to the next state, one could simply use a zero-argument function to advance (and return a new zero-argument function for use the next time one wants to advance). Effectively it makes sure roc knows what code to run on the internal state by wrapping code and state in a closure. The type of the closure is just a zero-argument function returnin a combination of a value and another zero-argument function of the same type, regardless of the internal state.
I'm not very confident that this will be efficient, though; so far the streams have been carefully constructed so that aggressive inlining and low-level optimisation should be able to collapse a number of stream operations into a single efficient one. I'm not sure whether putting closures in the mix will hamper these optimisations.
Brendan Hansknecht said:
I wonder if it is just the case that you need the state to implement an ability. Then the ability will deal with all dispatch.
I do think it is worth trying to implement an Iterable ability on things usable as streams. It would simply be a "next" function that returned the iterable advanced by one plus some other stuff. All (?) the type plumbing should clear away at the beginning of compilation, leaving me with the same hopefully efficient steps I have now.
If it helps, here are some examples, including "either": <https://github.com/aarchiba/roc-experiments/blob/main/Stream.roc>
As you can see each stream (append, repeat, etc.) needs its own type of state.
Anne Archibald said:
Brendan Hansknecht said:
I wonder if it is just the case that you need the state to implement an ability. Then the ability will deal with all dispatch.
I do think it is worth trying to implement an Iterable ability on things usable as streams. It would simply be a "next" function that returned the iterable advanced by one plus some other stuff. All (?) the type plumbing should clear away at the beginning of compilation, leaving me with the same hopefully efficient steps I have now.
Sadly this does not appear to work. Roc needs to know the type statically in order to determine which function to call (by looking up what function the type provided to implement the ability). So it looks like closures is it. Which makes sense; if you want to store data and the code appropriate to work on it together, that's what closures are kind of for. Let's just hope they can run efficiently.
Last updated: Jul 06 2025 at 12:14 UTC