So, I came up with a solution that seems to work. As reference (and for others), I did reference work on Entity-Component Systems mentioned in this group before.
https://gist.github.com/TheSeamau5/8d7fac9f1937f0bab317 https://gist.github.com/TheSeamau5/ec9695232ae919185f4d And I found Chris Granger's post quite helpful: www.chris-granger.com/2012/12/11/anatomy-of-a-knockout/ I decided to model a generic entity as: // entity.elm type alias ID = Int type alias Model = { id : ID , components : List Component } With the components as a type: // entity.elm type Component = Spatial Component.Spatial.Model | Corporeal Component.Corporeal.Model | Label Component.Label.Model | Viewable ComponentView | Gravitate Component.Gravitate.Model type alias ComponentView = { func : Model -> Form } And the Spatial component model would contain fields that had to do with spatial orientation of the entity, modeled as: // components/spatial.elm type alias Model = { mass : Float , forces : List Vec.Vec , pos : Vec.Vec -- in units , vel : Vec.Vec -- in units / centiseconds , acc : Vec.Vec -- in units / centiseconds ** 2 , heading : Float } So in declaring an instance entity, such as a cat, I modeled it as: // entity/cat.elm init : Entity.ID -> Entity.Model init id = { id = id , components = [ Entity.spatial 50 (100, 0) , Entity.corporeal (45, 45) Color.orange , Entity.viewable view , Entity.gravitate Component.Gravitate.ToEarth ] } where view() was another method with the signature view : Model -> Form What is Entity.spatial? Well, it's just: //entity.elm spatial : Float -> Vec -> Component spatial mass pos = Spatial <| Component.Spatial.init mass po So entity.elm has some functions that delegate to the specific component being created, and just tacks on the type in front. How do we get at a particular component type in a list of components? getSpatial : Model -> Maybe Component.Spatial.Model getSpatial model = List.filterMap (\component -> case component of Spatial spaceModel -> Just spaceModel _ -> Nothing ) model.components |> List.head Unfortunately, this needed to be repeated for all other components. I don't know of a way to pass in a type and use it in the branches of a case expression. But sometimes, you don't just want to get the component, you want to replace it. Learning the lesson from functors, I added a filterMap for entity, treating it as a container. filterMapSpatial : (Component.Spatial.Model -> Component.Spatial.Model) -> Model -> Model filterMapSpatial func entity = { entity | components = List.map (\component -> case component of Spatial space -> Spatial (func space) _ -> component ) entity.components } So when I update and step through the model with a dt tick, I do: //main.elm step : Float -> Model -> Model step dt model = model |> boundFloor |> gravity dt |> newtonian dt |> clearForces where each one of those functions changes the model somehow. In newtonian, it maps over all entities and calls newtonian on each entity. // main.elm newtonian : Float -> Model -> Model newtonian dt model = map (Entity.newtonian dt) model it calculates the new position based on the velocity. It calculates velocity based on the total acceleration. And it calculate the total acceleration based on all the forces acting on it. So even though all the entities have different components, I'm able to call newtonian on each entity, and filterMapSpatial will act on a spatial component, if it can find one, and update it. // entity.elm newtonian : Float -> Model -> Model newtonian dt entity = filterMapSpatial (\space -> let acc = Component.Spatial.totalAcc space space2 = Component.Spatial.vel (space.vel |+ acc .* (dt / 10)) space in Component.Spatial.pos (space2.pos |+ ((space.vel |+ space2.vel) .* (0.5 * (dt / 10)))) space2 ) entity And in that way, I can call newtonian on every entity, regardless of what components it has, because filterMapSpatial looks for a spatial component in an entity's component list, and only acts to replace it if it can find one. And that's how I got around overloading and polymorphism. The full code (work in progress, as of this posting) is here <https://github.com/iamwilhelm/bayes-cat/tree/796542f8789726a797a268bf521c2a8c5e56b64a>. The downside is that every time you add a component, you have to add accessor and filterMap functions for each component. Hope that helps someone. Wil On Tuesday, May 10, 2016 at 11:56:44 PM UTC-7, Wil C wrote: > > Thanks so far for the help that elm community has given lately. Really > appreciated. :) > > I've been in the middle of writing a simple game in Elm. However, I'm > running into a roadblock that makes me feel unproductive in elm. Here's the > problem: > > In a game, I got it in my head that the way to do this is to have a list > of entities (game objects), where you'd map over the entities to call > update on them to have them change. Because they're all in the list, they > should all have the same interface, like update(). However, an entity may > have different abilities due to them playing a different role: player > character is controlled by keyboard, whereas AI characters is controlled by > computer. So in a list when updating them, they should have the same > interface--the same type, whereas in other computational contexts, they > should be treated differently. Polymorphism is the tool that comes to mind, > given my background. > > I know polymorphism has been done to death on this forum, and there are no > typeclasses in Elm. But I didn't see a solution that I understood. It seems > like case statements or functors might be a way to go. Modeling it after > the composability of the Html startapp, I figured having a container called > Entity, that can contain different types of entities with different > components was a way to go. > > module Component where > > type alias Spatial = { > pos: Vec.Vec > , vel: Vec.Vec > , acc: Vec.Vec > , heading: Float > } > > module Entity.Egg where > > type alias Model = > { space: Component.Spatial > , corp: Component.Corporeal -- a record for dimension and color > , label: Component.Label -- a record for labeling the entity for display > } > > view : Model -> Form > view model = > group [ > filled model.corp.color <| circle ((fst model.corp.dim) / 2) > ] > > Each entity "subclass" has a view method that we call that shows the right > view when we pass in the model. > module Entity where > > type Entity a = > Egg a > | Cat a > > I figured I could use the Entity container as a functor, so I can map > functions over it, like how Maybe and Result do it. > > map : (a -> b) -> Entity a -> Entity b > map func model = > case model of > Role.Egg a -> > Role.Egg <| func a > Role.Cat a -> > Role.Cat <| func a > > So that way, I can ask an entity to apply a function to the entity model. > That's all well and good, but if I want to call the view() method across > all entities, I'm not sure how to do that. If I have a list of entities, I > could map across the List of entities, but how do I know which view() to > call for each? Would I have to have a view() method at the entity level? > > mapView : Entity a -> Form > mapView model = > case model of > Role.Egg a -> > Role.Egg <| Entity.Egg.view a > Role.Cat a -> > Role.Cat <| Entity.Cat.view a > > That seems like a lot of repetition, especially if I have other methods, > and other entities...wouldn't i get a combinatorial explosion? Is there a > better way? > > -- You received this message because you are subscribed to the Google Groups "Elm Discuss" group. To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.