Hi, I'm just toying around with Roc to explore functional programming. Coming from the OOP (Java) world having some (limited) experience with Rust, I found a common pattern such that a library declares an interface (or trait + trait object in case of Rust) and lets clients implement methods declared by it. Then there can be a collection of type of said Interface, a method for client to add to the collection, and finally the library does dynamic dispatch on each element of said collection. Searching through chat history, I saw this has come up, but the Roc-blessed approach (somewhere someone suggested a record of functions) is still not very clear to me.
For example, in Java I can have an interface of
public interface Renderable {
public String render();
}
and clients can implement classes of Person, Animal, Object
with their own implementations. The library can then do:
public class Scene {
ArrayList<Renderable> things = new ArrayList<>();
// constructors an then a method to add things
.....
// Scene rendering
public void render() {
for (Renderable thing: things) {
System.out.println(thing.render());
}
}
}
The closest to this in Roc I've found is to declare a tagged union of Renderable : [Person PersonData, Object ObjectData, Animal AnimalData]
, then a render : Renderable -> Str
that redirects the XData records to its correct rendering implementation. This of course would not let any client implement their own Renderable types at all as they would need to modify both the Renderable union and the render
redirecting function.
So what is the approach that Roc (and other functional programming languages in general) takes?
Hi @Gladisane, I think abilities can do what you are looking for.
Anton said:
Hi Gladisane, I think abilities can do what you are looking for.
To my understanding, Abilities is a compile-time concept only and is highly discouraged for custom use. Roc also disallows implementing new Abilities for existing types, so you can't have a game engine that exposes a Renderable
and asks users to implement it. Do I have any conceptual misunderstanding here?
That's all correct I believe. I've thought about translating inheritance to Roc before, I then wrote up an issue to make an example for it :)
Anyway, I have basically no game engine experience but it seems like you could work with a general function that accepts two arguments like Vertices
and Texture
. A Person
, Animal
and Object
would all just require Vertices
and a Texture
to render right? Let's leave shaders out of this for now :p that should not alter the fundamentals here.
Any user of your API (game engine platform) could provide Vertices
and a Texture
to that function for whatever custom thing they want in the game.
Contemplating on it more, I think use of custom abilities could be justified here to keep things conceptually simple. I do think it would not be recommended if you want maximum performance, but you could go with something like this:
Renderable implements
render : entity -> (Vertices, Texture) where entity implements Renderable
There would be no need for the user to alter/extend (Vertices, Texture)
because that is all the game engine needs to have in order to render something.
I don't think that would work because, I assume, abilities—unlike Java interfaces or Rust trait object—will get compiled down to a single type at compile-time. That means the collection List Renderable
can only hold one type only, appending another type (e.g adding Person
after Object
) will get compiler type mismatch error.
Yeah, an ability is not a solution to dynamic dispatch.
That said, if you can fit into an ability and it might make the most sense in some cases similar to this
I would say that the first suggestions is definitely switching to a form of compositions with tagged data.
So you would just use a when ... is
to do the dispatch.
Roc theoretically could support boxed dyn abilities like Rust supports boxed dyn traits, but that is not a feature today and is not currently planned.
Fundamentally, if you are missing multiple different types into the same data structure, roc requires tagging.
One final note, for functions called on a single element. You can make then have dynamic dispatch equivalent by passing function into the function.
Render : a, (a -> Vertex), (a -> Texture) -> Task {} [FailedToRender]
That enable essentially building up your dynamic dispatch table at runtime. You could create a record containing all of the functions that the data might need.
That said, if possible, I think it is generally preferred to use composition and have functions take a subset of the data.
welcome @Gladisane! :wave:
Gladisane said:
Hi, I'm just toying around with Roc to explore functional programming. Coming from the OOP (Java) world having some (limited) experience with Rust, I found a common pattern such that a library declares an interface (or trait + trait object in case of Rust) and lets clients implement methods declared by it.
[...]
So what is the approach that Roc (and other functional programming languages in general) takes?
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!)
Also, to be concrete on how to make a user extendable type that gets put in a list, I think you would have to do something like this:
Renderable a: [Person PersonData, Object ObjectData, Animal AnimalData, UserType a]
renderAll : List (Renderable a), (a -> Str) -> Str
Which fells pretty eh.
Edit: This can be a bit nicer with an ability, still discouraged, but better
Render implements
renderEntity : a -> Str
Renderable a: [Person PersonData, Object ObjectData, Animal AnimalData, UserType a]
where a implement Render
renderAll : List (Renderable a) -> Str
Then the users can implement render on there type and just stick it in UserType. They would aggregate all of their possible types into the single tag. The code would be able to store all Renderables into the single list.
That or force the user to define the render function.
So I don't know of a good solution to that specific problem apart from not modeling code that way.
Gladisane said:
Anton said:
Hi Gladisane, I think abilities can do what you are looking for.
To my understanding, Abilities is a compile-time concept only and is highly discouraged for custom use. Roc also disallows implementing new Abilities for existing types, so you can't have a game engine that exposes a
Renderable
and asks users to implement it. Do I have any conceptual misunderstanding here?
this is all totally correct, and in fact one of the main reasons I wanted to explicitly and strongly discourage reaching for abilities as a matter of course is that I believe it leads to overcomplicated code compared to just not doing that, and one of my fears with introducing abilities to the language is that it would lead to code that's overcomplicated because I know it's culturally encouraged to write code in this way in OOP communities :big_smile:
a game engine that exposes a
Renderable
and asks users to implement it
I don't have an exact example of how a game engine in Roc would look, but here's an example of a UI framework design for Roc - so it has the same concept of a rendering system which exposes ways for users of the system to specify how they want things to be rendered
without going too far down the rabbit hole, one of the key differences is that:
Richard Feldman said:
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!)
Well, I guess it's another case of reconditioning away from ingrained OOP patterns for me.
Brendan Hansknecht said:
That or force the user to define the render function.
That's probably the right way to go in the FP world, right, just embracing first-class functions.
BTW, what is the syntax for function that takes no argument/thunking in Roc. It seems a function type of (* -> Str)
would not work, but after some tinkering ({} -> Str)
seems to be the right one, but it still requires passing in {}
as an argument for function call.
Yeah {} -> Str
is the way we do a thunk in roc. You still pass in an empty record argument to call the function.
Thanks for all the responses, this is really helpful!
Gladisane has marked this topic as resolved.
Last updated: Jul 06 2025 at 12:14 UTC