I appreciate all the great answers I've gotten on here, and I have yet another question. This goes back to something I was thinking about a while ago, how I would port a modeling architecture (originally in Clojure, more recently also in Swift) to Roc. Here, we have a heterogeneous list of elements. Every element is of some type (not referring specifically to Roc types), but there could also be subtypes. So a type might be Highlight
, which can be thought of as a record that is guaranteed to have certain fields. And a subtype might be ColorHighlight
, which will have all the Highlight
fields, along with some others as well.
I was thinking of representing subtypes in Roc based simply on the fields in a record, so you just add the more specific fields to create an instance of the subtype. But I don't think this works when you've got the full heterogeneous list and you're trying to find instances of a type or subtype. As I now understand it, you can't write a function that checks whether a record has certain fields because as soon as you pattern match on those fields, the compiler determines that all inputs to the function must have those fields. In other words, a function's input can't be a disjunction of multiple record types. If you want to handle disjunctions, you need to use tags.
So instead, this is what I've got for handling subtypes (this is an instance of the ColorHighlight type):
{ world: None, content: Highlight { segment: 17, content: ColorHighlight { color: Red } } }
Here, all elements have the world
field, all Highlights have the segment
field, and all ColorHighlights have the color
field. This feels a bit cumbersome, but the representation clearly indicates what is in the type and what is in the subtype, and it supports pattern-matching.
Does this make sense? I'm curious if there's a better approach that I'm missing. I'm trying to take advantage of Roc's flexible type system, where I shouldn't need to declare in one place what are all the possible types and subtypes of elements--instead, I should be able to add ones any time, simply by writing code that puts them in the heterogeneous list or code that looks for them in the heterogeneous list.
This old discussion may be helpful.
One important quote from that:
I can't speak for other languages, but I would say that the approach that both Roc and Elm take is recommending against organizing code this way :big_smile:
I spent a lot of time writing code like this, and also writing code in a "just don't bother doing that" style and my conclusion is that doing it this way is the wrong default way to organize code in general (not just in FP, but in OOP too - which I realize is controversial depending on who you ask!)
Can you share a bit more about what process/algorithm the code is executing? That may help me suggest an alternative structure instead of the heterogeneous list.
@Anton Sure, here's a brief description. (Sorry, it ended up not being very brief. I hope this isn't too much.)
This is for an AI (not ML) framework. You have two heterogeneous collections. A) A collection of components operating in parallel. B) A collection of elements, small data structures containing information produced by components on the prior cycle of operation. Components are basically just functions that take in the full collection of elements produced on the prior cycle and produce new elements, that will be available on the next cycle.
Elements might include:
a) An image.
b) A segmentation of an image, contain a list of objects identified in the image.
c) A highlight, describing some particular segment that could be worth further investigation.
Components might include:
a) An image segmenter, that takes in the image and produces a segmentation.
b) A highlighter, that takes in the segmentation and highlights a particular segment.
Elements can include subtypes. For example, a highlight will always describe some segment of interest. But a color highlight will describe that segment's color, whereas a motion highlight will describe that segment's motion. By "subtype" I mean in a conceptual sense--we aren't using actual OOP. The original implementation was in Clojure with hashmaps, so really no typing at all. But I'd like to use static typing to make the framework more consistent and predictable. In Swift, that meant using structs. In Roc, my thought was to use records, but I need tags to capture the fact that all the different elements (types and subtypes) are combined in a single collection.
I'll say a bit more about components. Components get access to the entire collection of elements produced by all components on the prior cycle. But they need to pick out particular elements (could be either types or subtypes, so you might want all highlights, for example, or only the color highlights) that are relevant to their operation. Basically, for each possible type or subtype, I want to have a function that takes an element and either coerces it to be an instance of that type (if the element matches that type) or returns nothing (if the element doesn't match that type). So that means a Result type in Roc. Once I have that function for each type/subtype, I can use a higher-order function like List.keepOks to grab all the elements that match the desired type or subtype.
That was a lot. I hope it made sense. I appreciate your patience just for reading this far.
Generally for subtyping moving over to functional land it depends on if the subtypes are all known and defined at once
If so, I’d just make a sum type
So in Swift, I didn't use a sum type (enum) because it would require them to be all defined at once, and I want people to have the flexibility of being able to define new types in new files. My thought with Roc is I shouldn't have to do that because you don't have to define all the possible tags explicitly--new tags should be added to the tag union simply by virtue of your writing code that adds elements with those tags to the collection.
So the sum type is emergent from the code. At least that's my understanding. But my question here is specifically about how to handle subtypes.
Oh sorry, you're talking about a sum type specifically for the subtypes. In that case, no, I wouldn't want them to be all defined at once. And also I want the subtypes to share some fields, such that if you simply grab a Highlight without worry about whether it's a ColorHighlight or a MotionHighlight, you're guaranteed to have the segment
field. That was the reasoning behind the awkward data structure in my first post above.
Yes open tag unions work
But if you are defining a module of functionality that works for them all that trickier
And I’m getting sloppy typing so I’m probably too tired to be making sense right now
I’ll think on this tonight and check in in the morning
Yeah, that's the issue. Since there's no way to guarantee that all tags in a union share some record field, afaik the only thing you can do is nest the subtype tag inside the record for the type tag.
{ element: Highlight { segment: <whatever>, subtype: ColorHighlight { color: Red } } }
Sure thing, thanks.
the only thing you can do is nest the subtype tag inside the record for the type tag.
Yeah, I can't think of something better either
You could flatten it and make multiple things that contain the same sub data. Then just extract the sub data you want. But yeah, not really a design that is meant for fp langauges
Last updated: Jul 05 2025 at 12:14 UTC