Elm experiences

I have had a look at Elm again, and I've mostly liked what I have seen so far.

Before the praise, lets pick up on some issues in Elm.

Type classes

Elm may eventually get typeclasses due to peer pressure. I wouldn't be satisfied.

Elm has this kind of a hack for operator overloading that is a reflection from how Javascript is treating arithmetic:

(+) : number -> number -> number

This signature is from Elm's Basics package module. Below's an explanation for the type:

The number type variable means this operation can be specialized to Int -> Int -> Int or to Float -> Float -> Float.

Typeclasses would do this official. You might get the following note into the documentation:

(+) : (Add number) => number -> number -> number

Add Int
Add Float

You would then implement it through dictionary passing and be able to implement your own variations. We'd get something like:

type Add number = AddImpl { add : number -> number -> number }

(+) : (Add number) -> number -> number -> number

int_add : Add Int
float_add : Add Float

tuple_add : Add a -> Add (a,a)
tuple_add t_add = AddImpl {
    add (x,y) (z,w) = (t_add.add x z, t_add.add y w) }

Typeclasses get complicated when we have multiple notions of "add" that might make sense for the same type.

((+) int_add 3 5) == 8
((+) bitmask_add 6 2) == 6

You can solve the expression problem by wrapping the whole language you use inside a typeclass. You get Tagless-final style. It suggests there's better way to solve the same problem.

"Live programming"

The neatest thing about Elm would be the live programming capabilities. It's something that has been touted and that I waited for. However they don't seem to have solved this quite yet. Right now Elm supports precisely the forms of live programming you'd get with any other system.

  1. Reload the program in whole
  2. Reload only part of the program and have it crash.

I was waiting for a proper solution to live reloading that would have been bundled to the standard library tools in Elm. Elm clearly specifies the model in the program so it is potentially possible later.

Elm could keep the Model -type numbered and require that the user provides a mapping to upgrade the model from the old version to the new version. When it's time to reload the code it could load the new program and map the state into the new model.

Weirdness in parsing

Elm's parser tries to give you good response messages and partially it's doing so. Though there's something weird going in the layout computation that makes it feel like the parsing is broken somehow.

The parser stops when it hits a portion it cannot parse. I really don't like this. I prefer that it would attempt to error recovery.

The first error the parser detects isn't always the error that I'm interested about.

NPM-toolchain

While writing this post I found out Elm has a vim-mode, although at the moment it isn't fancy enough that it'd expand case blocks for you.

This is nice, but it's been implemented as a bunch of npm packages. Also nothing of it seems to work when I tried it.

Overall this is my experience of NPM. The NPM install is constantly slightly broken and you got to straighten it out every once a while.

Now to the praises.

Simple interfaces

Elm gives an incredibly clean programming model. I especially like using the html module.

When you got a view, it's of this form:

view : Model -> Gui Message

The Gui Message is a parametric object that describes what is shown to the user. You attach events to the structure that are triggered when user manipulates the view.

Then you have a routine that tells how the Message modifies the model:

update : Message -> Model -> (Model, Cmd Message)

Hm. Btw. This is doing assumptions about HTML that do not hold. The worst assumption being that the state of the DOM is only determined by the model. The only reason it works out at all because for it to work at all, the new view 'diffs' over the old view.

If you're unlucky with Elm, it may reset a scroll in your flex element. Unless you fix "optimize" it.

I really like how clean the model is though. The "API" described for the HTML is simple to understand. You can do surprisingly lot with what has been declared in Elm libraries.

Focus

I wrote a small Elm program where you can select three points and it draws a circle that intersects them. Most of the effort went to remembering enough geometry so that I can find the center of the circle. It also took a while to decide what to display to the user. Considering that I didn't spend lot of time to figuring out how to construct/draw/modify drawings of circles and points in SVG, I'm quite satisfied to the experience.

I cheated a little bit by using Elm's "Playground", but turns out this would have probably been simple enough to do in SVG, and still can translate to those systems.

This would have been faster if the typeclass -issue had been taken care of. However it's still quite neat.

So how do you write a program in Elm?

After describing your module and libraries you use, declare the entry point to the program and describe the model. In this case I had:

type alias Point = (Number, Number)
type Selection
    = Blank
    | First Point
    | Second Point Point
    | Done Point Point Point

You can start with a bare model and then keep adding stuff that you need to track.

The program state keeps growing but should stay manageable. It somewhat corresponds to what the user of the software experiences.

Next you declare a view. The Playground app took a list of things to display.

view computer state =
  [ case state of
      Blank -> group []
      First (x0,y0) -> group [
        move x0 y0 (circle black 2) ]
      Second (x0,y0) (x1,y1) -> group [
        move x0 y0 (circle black 2),
        move x1 y1 (circle black 2),
        finished_circle (x0,y0) (x1,y1)
            (computer.mouse.x, computer.mouse.y)
            (rgb 230 230 230)
            computer
        ]
      Done (x0,y0) (x1,y1) (x2,y2) -> group [
        move x0 y0 (circle black 2),
        move x1 y1 (circle black 2),
        move x2 y2 (circle black 2),
        finished_circle (x0,y0) (x1,y1) (x2,y2)
            (rgb 200 250 200)
            computer
          ]
  , group [
        circle lightPurple 3,
        seltext state
    ]
      |> move computer.mouse.x computer.mouse.y
      |> fade (if computer.mouse.down then 0.2 else 1)
  ]

When it was obvious that I have to present the constructed circle and present a "preview" for it, I moved the presentation into separate function:

finished_circle a b c color computer =
    case (find_center a b c) of
        Nothing ->
            words red "Unstable circle"
             |> move computer.mouse.x computer.mouse.y
             |> move 75 -12
        Just (x3,y3) ->
            move x3 y3 (circle color
                (distance a (x3,y3)))

Note that circle defined through three points doesn't always have a stable center. It's a condition you can detect and write down.

As the state of the tool is progressing, it shows instructions of what to do. I moved this logic apart as well, because it is presented aside the cursor.

seltext state = case state of
  Blank -> words black "Select 1st point"
      |> moveX 60
      |> moveY 2
  First _ -> words black "Select 2nd point"
      |> moveX 60
      |> moveY 2
  Second _ _ -> words black "Select 3rd point"
      |> moveX 60
      |> moveY 2
  Done _ _ _ -> words black "Circle!"
      |> moveX 60
      |> moveY 2

Finally there's a thing that updates the model when user clicks the mouse:

update : Computer -> Selection -> Selection
update computer state =
  if computer.mouse.click then
      case state of
        Blank ->
            First (computer.mouse.x, computer.mouse.y)
        First a ->
            Second a (computer.mouse.x, computer.mouse.y)
        Second a b ->
            Done a b (computer.mouse.x, computer.mouse.y)
        Done a b c ->
            Blank
  else
      state

Finding the center of circle is bunch of vector arithmetic. You find lines crossing between the points and find their intersection to find the center of the circle. If the lines are crossing and they are stable lines, then the formed circle is stable.

You can try the program here, it's a part of this blogpost.

Similar posts