# Elm Object Oriented Style

**URL:** https://discourse.elm-lang.org/t/elm-object-oriented-style/8848
**Category:** Show and Tell
**Created:** [December 29, 2022, 4:45pm UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848 "2022-12-29T16:45:53Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)
#### Post date: [December 29, 2022, 4:45pm UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848/1 "2022-12-29T16:45:53Z")

</div>

Based on the code presented by @jhbrown at a recent Elm meetup, [with accompanying slides](https://docs.google.com/presentation/d/1lTgRyGbswb2ozynYckLUZja_FY5RGlBnfJGduOUsYGc/edit#slide=id.p), I decided to build a small app in an object oriented style using this technique. What intrigued me about this talk was the “hiding implementation types” idea, which immediately made me think “existential types in Elm?”. If I can hide the implementation type of something, then I can separate interface and implementation, and I can have multiple implementations of the same interface even if the implementations have different types. I did not think this was possible in Elm, but Jeremy showed how to sneak it past the type checker.

My code is here. This is a re-implementation of a little example I wrote to test my [2d camera package.](https://package.elm-lang.org/packages/the-sett/elm-2d-camera/latest/Camera2d). That explains why I have a black strip at the left (to leave space for a menu), and a transparent one on the right (for an overlay) - I was experimenting with setting up a 2d camera where the frame is offset from 0,0 and where the focus region of the camera does not necessarily fill the entire SVG region with the drawing in it.

> **[GitHub - rupertlssmith/elm-oo-style: An example showing how to do OO style...](https://github.com/rupertlssmith/elm-oo-style)**
>
> An example showing how to do OO style programming in Elm. - GitHub - rupertlssmith/elm-oo-style: An example showing how to do OO style programming in Elm.

The original non-OO version can be found in this repo under [OldMain.elm](https://github.com/rupertlssmith/elm-oo-style/blob/master/src/elm/OldMain.elm)

The interfaces for the OO version are defined in [Scene/Spec.elm](https://github.com/rupertlssmith/elm-oo-style/blob/master/src/elm/Scene/Spec.elm).

To get started with this I imagined doing something along the lines of an ECS or entity-component-system, so the drawings themselves I called Entity. In the end this wasn’t quite the right idea, but I stuck with the name Entity. A set of Entities are composed into a Scene, with this interface:

```auto
type alias SceneIF =
    { subscriptions : Sub Msg
    , update : Msg -> ( Scene, Cmd Msg )
    , view : () -> Html Msg
    , add : EntityId -> Entity Msg -> Scene
    }

```

And the Entities implement this interface (note `move` and `animate` not actually used in this example):

```auto
type alias EntityIF msg =
    { gestureHandler : Maybe (GestureIF msg)
    , move : VScene -> Entity msg
    , animate : Posix -> Maybe (Entity msg)
    , position : PScene
    , bbox : BLocal
    , view : ViewContextIF msg -> List (Svg msg)
    }

```

The gesture handlers are optional on an entity, and have this interface:

```auto
type alias GestureIF msg =
    { tap : Maybe (Pointer.PointArgs Screen -> UpdateContext msg -> UpdateContext msg)
    , doubleTap : Maybe (Pointer.PointArgs Screen -> UpdateContext msg -> UpdateContext msg)
    , drag : Maybe (Pointer.DragArgs Screen -> UpdateContext msg -> UpdateContext msg)
    , dragEnd : Maybe (Pointer.DragArgs Screen -> UpdateContext msg -> UpdateContext msg)
    }

```

The idea is that mouse or touch gestures are sent to the Scene through its `subscription` or event handlers in its `view`. Those events will contain the id of the entity that they take place on, and so the scene can trigger the gesture handler on an entity, provided it exists in the scene and has the relevant gesture handler.

Some interesting things about this:

- The Entity has no idea what `Msg` type it is dealing with, it is parameterized over `msg`. This means it cannot create arbitrary side effects, it will be given an interface with functions in it to use to generate from a limited pallete of side effects (none in this example, although I did pass down `noop` just to show it can be done). An Entity is like a Program that I have specialized to fit a particular purpose - and unlike most Elm Programs, it is not a TEA.

- The Scene and Entity know nothing about each others implementations. In particular new entities can be added to the codebase without touching any other code. So each entity gives a bounding box - I do not need to update a case statement somewhere that dervies bounding boxes for all Entities, just adding this code to the new one is sufficient.

Having the implementations hidden, does make it a little awkward, since sometimes the Entities do need to know things about the Scene. For `view` purposes, they need to know about the camera and frame and so on. For `update` purposes, they need some kind of interface they can apply those updates to, in order to change the state of the Scene, for example to move the camera, add more entities to the scene, etc.

For the view we have a view interface:

```auto
type alias ViewContextIF msg =
    { noop : msg -- Example side-effect the view is allowed to create.
    , zoom : Float
    , frame : BScreen
    , mousePos : PScreen
    , camera : Camera2d Unitless Pixels Geometry.Scene
    }

```

The update interface is a bit more complicated:

```auto
type alias UpdateContextIF msg =
    { frame : BScreen
    , camera : Camera2d Unitless Pixels Geometry.Scene
    , setCamera : Camera2d Unitless Pixels Geometry.Scene -> UpdateContext msg
    , animateZoomToTarget : Point3d Unitless (ZoomSpace Pixels Geometry.Scene) -> UpdateContext msg
    , updateEntity : EntityId -> Entity msg -> UpdateContext msg
    , toScene : Scene
    }

```

The update interface has a `toScene` method on it. This is because this interface is for making changes to the Scene, and so must ulimately be able to produce a new `Scene`, even though several updates can be applied to it to produce new `UpdateContext`s.

I haven’t described everything but this is probably enough of a guide for now, in case anyone wants to take a closer look at the code.

==========

Here is a summary what I think about this way of working with Elm, good and bad:

- I like the extensibility, new implementations can be added without changing any old code. Comparing that with using a custom type, where a new constructor gets added for each new thing, it feels nicely encapsulated.

- I like the idea of building my own mini-Program types in Elm that are more restrictive than the general TEA pattern. More restrictive means more specific to some particular application, and this can be a nice way of designing and enforcing a design on an area of code.

- Elm does not have dynamic code loading, so this cannot be used to dynamically load plug-ins at runtime. The lack of runtime extensibility somewhat negates the benefits of the technique.

- It was a bit complicated to figure out, but perhaps that was just because its the first time I have done this. The `UpdateContext` was the hardest part to figure out. I could actually have avoided `UpdateContext` altogether, if I had not hidden the `Scene` implementation type, and I only have one `Scene` implementation.

- There is one `Msg` type over the whole scene and entities, and the entities do not have their own internal `Msg` types. If they did, they would need to expose it so that the parent `update` could use a constructor on the parent `Msg` type to group the child one under, as per nested TEA. This would stop the entities from all having the same type. On the other hand, I quite like the idea of passing a set of functions to the entity `update` or `view` offering a limited pallete of side-effects, it seems appropriate to using this technique for creating highly restricted Program types.

- Separation of interface and implementation has a lot of appeal for me. Opaque types and modules can be used to achieve this too, and are usually going to be a more appropriate way of doing so in Elm.

In short, its too fiddly to use for most Elm work, but I think it can have its uses in limited situations where writing software in an open-ended and extensible way is highly important to you.

---

<div class="post-metadata">

### Author: ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)
#### Post date: [January 5, 2023, 11:59am UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848/2 "2023-01-05T11:59:59Z")

</div>

So I made some improvements to this, which I think show how narrowing interfaces can lead to cleaner code.

I removed the entire `GestureIF` interface from Entities, and added a new `select` method on the entity. Instead of entities themselves handling gestures to implement `move`, I now use the `move` method to do so. Instead of handling a `doubleTap` gesture event on the entity, I now user the `select` method.

```auto
type alias EntityIF msg =
    { move : VScene -> UpdateContext msg -> UpdateContext msg
    , select : UpdateContext msg -> UpdateContext msg
    , animate : Posix -> Maybe (Entity msg)
    , position : PScene
    , bbox : BLocal
    , view : ViewContextIF msg -> List (Svg msg)
    }

```

There was code in each entity to handle pointer drag, and this code needs to implement a small state machine (called `GestureCondition` in the implementation). This state machine flips into dragging mode when a drag is occurring and back to normal mode when the drag ends.

By removing the logic to drive the state machine from the entities and putting it in the `Scene` implementation instead, I am able to re-use this logic for all entities, current and future. Once this logic is correctly implemented, it removes the risk that some new entity added to the system can get it wrong. This greatly simplifies the logic each entity needs to implement to handle moves, since each move is now just a 2d vector.

For the Target shape:

```auto
onMove trans model raise (UpdateContext uc) =
    { model | pos = Point2d.translateBy trans model.pos }
        |> raise
        |> uc.updateEntity model.id

```

For the Root which acts as camera controller:

```auto
onMove trans _ _ (UpdateContext uc) =
    uc.moveCamera (Vector2d.reverse trans)

```

(On the `UpdateContextIF`, `setCamera` was replaced by `moveCamera` which is also narrower - the camera can no longer be arbitrarily set, it can only be moved or other specific and limited camera operations that are desirable to expose.)

I like how this now splits low-level gesture handling, from more high-level outcomes. The low-level handling bit happens entirely in the `Scene` implementation, and each entity provides implementations of more high-level methods that describe better the outcomes that will be applied to them. In this case I implemented `doubleTap` as triggering `select` on an entity, and that also leaves open the possibility of triggering the same `select` outcome in different ways - such as dragging a lasso control around a group of them, or single clicking, or …

Of course this is the well known OO refactoring of ‘pulling up’ a method from a sub-class into a parent one. A specific implementation gets recognised as a re-usable pattern, and pulled up into a place where it can be re-used and applied uniformly over a class of things.

---

<div class="post-metadata">

### Author: ![axelbdt](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/axelbdt/32/4404_2.png) [@axelbdt](https://discourse.elm-lang.org/u/axelbdt)
#### Post date: [January 9, 2023, 10:34am UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848/3 "2023-01-09T10:34:09Z")

</div>

Thanks for the write up!

In my understanding, FP facilitates adding an operation to a fixed set of data types (ADT) while OOP facilitates adding a data type to a fixed set of operations (Interface, type classes).

It’s clear how your work enables the second kind of extensibility as you pointed out, and it’s a very interesting pattern.

In your last point, you mention opaque types. In my experience with them, they do provide encapsulation, but not the extensibility of your solution. Is it yours as well? Or do you have examples of opaque types “done right” with some extensibility that I could look at for comparison/education?

---

<div class="post-metadata">

### Author: ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)
#### Post date: [January 9, 2023, 11:01am UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848/4 "2023-01-09T11:01:11Z")

</div>

> [@axelbdt](#):
>
> In your last point, you mention opaque types. In my experience with them, they do provide encapsulation, but not the extensibility of your solution. Is it yours as well? Or do you have examples of opaque types “done right” with some extensibility that I could look at for comparison/education?

I think that is correct, custom types do not give extensibility, at least not without modifying the type to add a new constructor.

```auto
type Shape = Square SquareProps | Triangle TriangleProps

```

Now you want to add cirlces?

```auto
type Shape = Square SquareProps | Triangle TriangleProps | Circle CircleProps

```

But you probably also have a load of functions over Shapes, so you need to add circle to them too:

```auto
shapeBBox : Shape -> BoundingBox2d Unitless Scene
shapeBBox shape = 
    case shape of 
        Circle props -> ... -- New code here
        Square props -> .. -- Existing code
        Triangle props -> .. -- Existing code

```

And so on, maybe you also have `Encoder/Decoder, view, translation, select` and many other things in some imaginary drawing program. If `Shape` is also opaque, the implementation details are hidden behind this drawing API.

So in that sense a custom type can be extended to add new behaviour, but it is necessary to modify existing code. The OO pattern can let you add new behaviour without touching existing code at all.

A good example of opaque types hiding implementation details might be Elms `Dict`. You don’t need to know how it works to use it, and its implementation could even change without breaking code using it. Inside I think its a red-black tree, but that should never leak through its API.

---

<div class="post-metadata">

### Author: ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)
#### Post date: [January 9, 2023, 2:58pm UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848/5 "2023-01-09T14:58:13Z")

</div>

> [@axelbdt](#):
>
> In my understanding, FP facilitates adding an operation to a fixed set of data types (ADT) while OOP facilitates adding a data type to a fixed set of operations (Interface, type classes).

I don’t classify FP and OO as opposites though. FP is about letting you program with pure mathematical functions, making functions first class in the language and allowing higher order functions; it usually employs immutability to achieve purity. OO is about combining data and code together into objects, and using them together as units that have a degree of independance of other code; it is about achieving modularity.

My code combines these two ideas as they can be orthogonal. But as there are no built in OO constructs in Elm, it is necessarily a bit of a round about way of doing it.

---

<div class="post-metadata">

### Author: ![axelbdt](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/axelbdt/32/4404_2.png) [@axelbdt](https://discourse.elm-lang.org/u/axelbdt)
#### Post date: [January 9, 2023, 4:40pm UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848/6 "2023-01-09T16:40:46Z")

</div>

I agree that FP and OOP can’t really be described as opposites, especially with the two (IMO valid) definitions you provided.

What I would be described as “opposites” (or in fact, complementary) are the two extension mechanisms:

- Defining a single function for all the variants of an ADT with pattern matching (i.e. adding an operation to a fixed set of data types)
- Implementing an interface or a type class for a datatype (i.e. adding a fixed set of operation to a data type)

Different projects will emphasize those differently. For example, a compiler will work great with the first, while a game engine will rely more on the latter (some would say UI programming too!). It depends on how the developper thinks too!

(I got the idea from Dan Grossman in [this video](https://youtu.be/nsbJHo9MkCw))

This would explain while some people hope for type classes in Elm: there is first class language support for the first kind of extensibility only.  
This is why your thread piqued my interest. This might become a very useful pattern!

---

<div class="post-metadata">

### Author: ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)
#### Post date: [January 19, 2023, 4:41pm UTC](https://discourse.elm-lang.org/t/elm-object-oriented-style/8848/7 "2023-01-19T16:41:11Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
