Stream: beginners

Topic: Possible to do this in Roc?


view this post on Zulip Jared Cone (Mar 23 2022 at 19:32):

Hello, I was wondering if it would be possible to do something like this in Roc (C# code):

class ComponentRegistry
    {
        Dictionary<Type, Object> components = new Dictionary<Type, object>();

        public void AddComponent<T>(T component)
        {
            components[typeof(T)] = component;
        }

        public bool GetComponentOfType<T>(out T result)
        {
            if (components.ContainsKey(typeof(T)))
            {
                result = (T)components[typeof(T)];
                return true;
            }

            result = default(T);
            return false;
        }
    }

Basically, have a generic store of components that would let you retrieve a component by its type? In case context helps I'm brainstorming how ECS could be done in Roc

view this post on Zulip jan kili (Mar 23 2022 at 19:40):

I believe tags are the recommended mechanism for "runtime typing"

view this post on Zulip Jared Cone (Mar 23 2022 at 20:55):

But tags require the ComponentRegistry to know about every possible thing that can be put into it?

view this post on Zulip Folkert de Vries (Mar 23 2022 at 20:57):

we don't have this kind of reflection. You can see here how a type is turned into a value typeof(T). That's not something we can do

view this post on Zulip Folkert de Vries (Mar 23 2022 at 20:58):

types and values live in distinct universes in roc

view this post on Zulip Folkert de Vries (Mar 23 2022 at 20:58):

interestingly, to get this sort of feature you can be both more strongly and more weakly typed

view this post on Zulip Folkert de Vries (Mar 23 2022 at 20:59):

e.g. haskell can actually achieve the same result (if you enable a bunch of language extensions)

view this post on Zulip jan kili (Mar 23 2022 at 21:11):

If you don't want the component registry to know every "type" it can use as a dict key, then it sounds like you'll want to use strings as keys. Perhaps a component is a record with a .type string field.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 21:18):

That wouldn't work in roc. What would the actual component be? { type: Str, component: ??? }

So it would have to be an explicit tag I think.

If you used a type variable for the component, all components would need the same type.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 21:19):

As a side note, I think that the tagged version would be cheaper at runtime then the type reflection version.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 21:19):

But yeah, not flexible due to no reflection.

view this post on Zulip jan kili (Mar 23 2022 at 21:28):

I don't know ECS very well, so this is just an idea:

MyECS.Component : { type: Str }*
MyECS.ComponentRegistry : SomeDictLibrary.Dict
MyECS.addComponent : MyECS.ComponentRegistry, MyECS.Component -> MyECS.ComponentRegistry

registry : MyECS.ComponentRegistry
registry = MyECS.initializeComponentRegistry
registry = MyECS.addComponent registry { type: "friend", name: "Friendo" }

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 21:33):

Hmmm...I'm actually really interested, does that work in roc? At a minimum, it would break if two component have a field with the same name but different types, but otherwise it may work.

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:34):

MyECS.Component : { type: Str }* does not work. That * type variable needs to occur on the left-hand side

view this post on Zulip jan kili (Mar 23 2022 at 21:35):

Oh, whoops, lemme edit

view this post on Zulip jan kili (Mar 23 2022 at 21:39):

MyECS.Component a : { type: Str }a
MyECS.ComponentRegistry : SomeDictLibrary.Dict
MyECS.addComponent : MyECS.ComponentRegistry, MyECS.Component -> MyECS.ComponentRegistry

registry : MyECS.ComponentRegistry
registry = MyECS.initializeComponentRegistry
Friend : { name: Str }
friendo : MyECS.Component Friend
friendo = { type: "friend_idk_this_aspect_is_pain", name: "Friendo" }
registry = MyECS.addComponent registry

view this post on Zulip jan kili (Mar 23 2022 at 21:39):

None of those type aliases are necessary, they're just to show what types things would have (and to skip writing full definitions)

view this post on Zulip jan kili (Mar 23 2022 at 21:39):

It turns out my understanding of ECS is just OOP with methods extracted into systems (not shown here) :laughing:

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:41):

I think the .Dict also needs some type parameters right? and I think that makes this fall apart

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:42):

what you want is some sort of way to "forget" about that type variable

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:42):

and we can't do that. There are ways in haskell with a forall, or in rust with the Any trait

view this post on Zulip jan kili (Mar 23 2022 at 21:42):

I imagine this particular ECS implementation approach isn't the most Roc-y regardless

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:42):

or dyn Trait in general

view this post on Zulip jan kili (Mar 23 2022 at 21:44):

String encoding? :nauseated:

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:46):

void *

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:46):

solution to all our problems

view this post on Zulip jan kili (Mar 23 2022 at 21:47):

I don't understand and I'm terrified

view this post on Zulip jan kili (Mar 23 2022 at 21:48):

So what's the real solution here? It's a legitimate use case, so we should have a clean pattern ready.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 21:50):

A tag and explicitly naming the possible types is definitely the cleanest in current roc. The library would just expose it as a type variable and the user would fill in the tag

view this post on Zulip jan kili (Mar 23 2022 at 21:50):

Ooh

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:52):

ComponentRegistry a : List a

# not sure if the original deduplicates based on the type?
addComponent = List.push

findComponent : ComponentRegistry a, (a -> Bool) -> [ Found a, NotFound ]

view this post on Zulip jan kili (Mar 23 2022 at 21:54):

(wow, Roc's type variables and type inference are a powerful combo for terse yet readable code, IMO)

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 21:54):

So does this or something like it work in general?

dict : Dict Str { }a
dict = Dict.Empty
    |> Dict.insert "Friend" { hp: 12, name: "Foo" }
    |> Dict.insert "Enemy" { hp: 10, atk: 7 }

view this post on Zulip jan kili (Mar 23 2022 at 21:55):

dict a : ...?

view this post on Zulip Folkert de Vries (Mar 23 2022 at 21:56):

no collections are homogeneous

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 21:58):

Ok. That is what I thought.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 22:01):

@Jared Cone with the original code, wouldn't that limit to exactly one component of each type? Is that normal for ECS. This just doesn't quite match my understanding of it.

view this post on Zulip jan kili (Mar 23 2022 at 22:29):

Yeah I think this use case needs variable component types/bodies

view this post on Zulip jan kili (Mar 23 2022 at 22:30):

I think the a in Folkert's example would be a tag union with "component types" as tags and "component data" as tag payloads

view this post on Zulip Jared Cone (Mar 23 2022 at 22:36):

with the original code, wouldn't that limit to exactly one component of each type? Is that normal for ECS. This just doesn't quite match my understanding of it.

Yes, I was just wanting to keep the example simple. The core issue is the same regardless if it's exactly one component or more than one component of each type.

view this post on Zulip Jared Cone (Mar 23 2022 at 22:46):

So a basic design in Roc might be something like:

EntityId : U32

Game : {
    deltaSeconds : F32,
    velocityComponents : Dict EntityId Vector3,
    gravityComponents : Dict EntityId Vector3,
    ...
}

GravitySystem : Game -> Game
GravitySystem = \game ->
    velocityComponents = ... apply gravity to each entity that has velocity and gravity components ...
    { game & velocityComponents }

Issues:

view this post on Zulip jan kili (Mar 23 2022 at 22:49):

I thought one of the goals of ECS pattern is to eliminate bloated "God"/"Game" objects, so maybe mimicking the implementation from another language is breaking the spirit of the pattern?

view this post on Zulip Jared Cone (Mar 23 2022 at 22:52):

The idea is a System can say "Give me the entities that have X and Y components so I can do some work with them". As far as I can tell in Roc currently the only way to do that is with a "God" object.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 22:57):

So I think this would be a functional example that would theoretically perform similar to a regular ECS:

Type : [
        Animal,
        Plant,
        Player,
        Chest,
    ]

Entity := { id: U64, type: Type }

ComponentProperties : {
        hasPosition: Bool,
        hasVelocity: Bool,
        hasHealth: Bool,
        hasInventory: Bool,
    }

supportedComponents : Type -> ComponentProperties
supportedComponents = \type ->
    when type is
        Animal ->
            { hasPosition: True, hasVelocity: True, hasHealth: True, hasInventory: False }
        Plant ->
            { hasPosition: True, hasVelocity: False, hasHealth: True, hasInventory: False }
        Chest ->
            { hasPosition: True, hasVelocity: False, hasHealth: False, hasInventory: True }
        Player ->
            { hasPosition: True, hasVelocity: True, hasHealth: True, hasInventory: True }

Item : [ SomeTag ]

Components : {
        # These could also be lists with empty data for entities without this data.
        position: Dict U64 { x: F64, y: F64, z: F64 },
        velocity: Dict U64 { dx: F64, dy: F64, dz: F64 },
        health: Dict U64 U64
        inventory: Dict U64 (List Item)
    }

moveSystem : List Entity, Components -> [ T (List Entity) Components ]
moveSystem = \entities, components ->
    List.walk entities (T entities components) (\T currentEntities currentComponents, entity ->
        supported = supportedComponents entity.type
        if supported.hasPosition && supported.hasVelocity then
            {x, y, z} = Dict.get currentComponents.position entity.id
            {dx, dy, dz} = Dict.get currentComponents.velocity entity.id
            nextPosition = { x: x + dx, y: y + dy, z: z + dz }
            nextComponents = {currentComponents & position: Dict.insert currentComponents.position entity.id nextPosition}
            T currentEntities nextComponents
        else
            T currentEntities currentComponents
    )

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 23:00):

Of course it is custom and would be much harder to make generic in roc. Would have a bunch of open ended type variables that need to be filled in.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 23:03):

As a side note, this might be made more reasonable with abilities. Could have an ability to get an entities position. That ability would be on all entities and would just look up the entity in the position dictionary. If the entity does not support position, it would return None.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 23:04):

But that would also be one ability per component, so it would be quite odd.

view this post on Zulip Brian Carroll (Mar 23 2022 at 23:26):

would be much harder to make generic in roc

True but in Elm I've always found this to be a good thing. If you're making a game (as opposed to selling a game engine like Unity) then isn't it better it to be explicit rather than generic?

view this post on Zulip Brian Carroll (Mar 23 2022 at 23:26):

I say this with zero experience in game dev, it's just what I've found in other types of application.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 23:29):

I mean if you are new to game making and want to use someone's basic game platform with some ECS library, you would prefer not to have to learn and implementation everything explicitly cause it is all new to you.

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 23:31):

The big issue is that I believe some ECS library basically wouldn't be functional without reflection or something more powerful in those lines. It would basically be an empty shell of type variables that take explicit knowledge to implement. Probably would be best just to make your own custom implementation or ECS rather than use the library.

view this post on Zulip Martin Stewart (Mar 23 2022 at 23:33):

There are two ECS packages in Elm (which has a very similar type system to Roc) if you're interested in seeing what the API and implementation of those look like. I don't know that much about ECS so I don't know if they are good implementations of it though.

https://package.elm-lang.org/packages/justgook/elm-game-logic/latest/ (source code: https://github.com/justgook/elm-game-logic/tree/3.0.0)
https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/ (source code: https://github.com/harmboschloo/elm-ecs/tree/2.0.0)

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 23:53):

So elm-game-logic just ignores the entity typing problem all together by using an array of Maybe. That might be an acceptable option, but it has a few problems:

view this post on Zulip Brendan Hansknecht (Mar 23 2022 at 23:54):

elm-ecs is more complicated for me to wrap my head around. Will try to give an analysis once I get what it is actually doing under the hood.

view this post on Zulip Brendan Hansknecht (Mar 24 2022 at 00:12):

Ok, elm-ecs looks better. It uses a lot of type variables and indirection (that could theoretically be compiled away) to make this work. Essentially each component has to know how to get/set itself in the world via the use of lambdas. Assuming those lambdas get compiled away it should generate similar code to what I have above. Though it does still have some clear drawbacks from the direct implementation:

view this post on Zulip Brendan Hansknecht (Mar 24 2022 at 00:13):

So I would label this as both having fundamental overhead and restrictions compared to the raw implementation, but still cool and probably useful for many hobby games.

view this post on Zulip jan kili (Mar 24 2022 at 00:48):

Would this be a job for a Roc codegen/scaffolding tool? Or maybe just copy/paste/customize haha

view this post on Zulip jan kili (Mar 24 2022 at 00:56):

(with regard to the idea I'm gathering that "Roc ECS developers of all skill levels might be better served by a manual implementation from scratch, since a Roc ECS library can't provide meaningful zero-cost abstractions")

view this post on Zulip Brendan Hansknecht (Mar 24 2022 at 03:06):

I think copy and modify would work great for something like with. Also would work well to be able to make a declarative list of types and their components and then generate it.

view this post on Zulip Brendan Hansknecht (Mar 24 2022 at 03:58):

So I was watching a talk on ECS to better understand how it is done with good data oriented design. I think that there may actually be a way to modify something like elm-ecs to generate almost the exact same thing as a proper DoD entity component system (assuming all lambdas inline). The actual core difference is just expanding an entity include some metadata about the components and tags that is has.

This ECS still doesn't exactly have entity types, but it has entity tags that can be used in the same way. Though it has no proper verification to stop a user from accidentally make an entity that is missing a component that it should have. To get that sort of full verification, you would either need a tag per entity type, or make the entity type something separate.

view this post on Zulip Brendan Hansknecht (Mar 24 2022 at 03:59):

I should add a warning to the talk: it is a lot of C++ and a metric ton of templates.

view this post on Zulip Jared Cone (Mar 24 2022 at 04:38):

To clarify, there shouldn't be static types of entities. An entity is a collection of components and components can be added to or removed from entities throughout their lifetime

view this post on Zulip Brendan Hansknecht (Mar 24 2022 at 04:39):

Yeah, I didn't realize that at first. see it now with these talks

view this post on Zulip jan kili (Mar 24 2022 at 05:04):

Yeah, I forgot that too in my examples and treated entities as components

view this post on Zulip Zeljko Nesic (Mar 25 2022 at 01:27):

There is fundamental clash between ECS and functional programming.

The fact that any component of an entity can modify other entities and it's components in the game world, eg. on colision with other object, open the door.

Which is bonkers from the perspective of FP.

view this post on Zulip Zeljko Nesic (Mar 25 2022 at 01:29):

I believe that we still don't know how to model the world so rich and dynamic with FP and type systems that we've got.

Just blunt copying the pattern doesn't work!

view this post on Zulip Zeljko Nesic (Mar 25 2022 at 01:31):

Which is weird, because we are talking about "composability" of FP constructs, and yet, we can't have a blank cotton ball and stick random stuff to it and be sure that our program will run.

view this post on Zulip Brendan Hansknecht (Mar 25 2022 at 01:31):

With Roc's inplace mutation, I think that can be worked around. Of course, a small mistake can lead to tons of copying.

view this post on Zulip Brendan Hansknecht (Mar 25 2022 at 01:32):

So definitely a case where you need to tread carefully or you could ruin performance

view this post on Zulip Zeljko Nesic (Mar 25 2022 at 01:34):

Maybe this is good place where abilities might help? Or platforms?

view this post on Zulip Jared Cone (Mar 25 2022 at 04:20):

The fact that any component of an entity can modify other entities and it's components

Unfortunately ECS has two different meanings. The type of ECS I'm referring to is where the Components are just data, no behavior. Systems are behavior, no data. So in that case Components don't modify anything, but any System can modify any entity or component.

view this post on Zulip Jared Cone (Mar 25 2022 at 04:26):

I think FP should be a good fit for ECS because Systems can be implemented as pure functions. The thing that I'm a little roadblocked with is how to do it in Roc since there (currently) isn't a data type that can store "anything" that can later be cast to the correct underlying type. Though... could open tags somehow support this? I haven't worked much with them.

view this post on Zulip Brendan Hansknecht (Mar 25 2022 at 04:27):

An open tag can't be stored. Has be a concrete type.

view this post on Zulip Brendan Hansknecht (Mar 25 2022 at 04:28):

I think making an ECS library will be hard in Roc (would require lots of type variables and lambdas like the linked elm libraries)

view this post on Zulip Brendan Hansknecht (Mar 25 2022 at 04:29):

I think making a concrete ECS where each component gets it's own array or dictionary should be totally doable. Just has to be done manually.

view this post on Zulip Kevin Gillette (Mar 27 2022 at 13:43):

I haven't thought this through at all, and don't know about ECS, but can't you have a general ECS module expressed in terms of a completely open tag system (possibly with tags the ECS module specifically understands), and accept a callback function from the app that takes as input the known app-specific tags? At that point, the compiler would have its concrete definition. This would be a bit like Elm update functions, I presume.

view this post on Zulip Brendan Hansknecht (Mar 27 2022 at 17:56):

I think you would run into storage issues. For example, I want the position component in one array and the color component in a separate array.

If the user of the library is passing an open tag of type [ Pos F32 F32, Color U8 U8 U8 ]*. The underlying library would know nothing about the type and would not able able to do anything with it. The underlying library could store them all to one master list, but it would have no way to distinguish between them.

view this post on Zulip Brendan Hansknecht (Mar 27 2022 at 17:57):

To store Pos in the position array and Color in the color array would require the library to know about the individual tags.

view this post on Zulip Kas Buunk (Jul 03 2022 at 07:18):

Hi! Is there a platform and example of a simple http server in Roc?

view this post on Zulip Anton (Jul 03 2022 at 07:44):

Some work was done on this in PR #2975 but it may require some updating.

view this post on Zulip Kas Buunk (Jul 03 2022 at 08:09):

Thanks. Would be relevant for a large audience if a solid ecosystem exists for building web services on http, like rest, gRPC, graphql, etc.

view this post on Zulip Kas Buunk (Jul 03 2022 at 08:10):

Same goes for event-driven microservices, an ecosystem for subscribing and publishing to a Kafka or Nats/JetStream event bus would make it easy for service meshes to experiment with a microservice in Roc.

view this post on Zulip Anton (Jul 03 2022 at 08:14):

Absolutely, we definitely plan to work on it in the future.

view this post on Zulip Kas Buunk (Jul 03 2022 at 08:29):

Would make for a fantastic well-scoped domain.

view this post on Zulip Kas Buunk (Jul 03 2022 at 08:31):

Probably in Rust. With arena memory management with requests and incoming events with their separate arenas.


Last updated: Jul 06 2025 at 12:14 UTC