Rich interactive notebooks with elm-markup
Tags: elm, markup, interactive
Published: 2019-10-03 15:37:01

Whether it's Observable, Google Colab or Jupyter, I love interactive notebooks. If none of these names are familiar, such notebooks are documents where regular text snippets, headings etc. mingle with working code snippets. They are great communication tools and allow a kind of fluid play with code that is hard to get inside regular IDE's.

I have been interested in making my own interactive notebooks for a long time not because there was anything missing in existing solutions, but because a lot of the times I find them to be too flexible: if a notebook allows its user to change everything, they can also be break its logic pretty easily and with little help towards recovery.

And besides, how much effort would it take to make an interactive notebook on my own terms anyway...

Well, without good crutches, probably a lot, so I didn't attempt it right until I stumbled upon a package in the Elm programming language called `elm-markup`. Turns out, making a basic interactive notebook in it is much simpler than I had imagined: just under 200 lines and without even touching the package's more involved modules.

This experiment is what brings me to writing this series of blog posts, covering a delightful journey looking at interactive notebooks using `elm-markup`. We will start with a simple counter app, working our way up to some interactive drawings.

## What is `elm-markup`?

It is officially described as the *Elm-friendly markup language*, one that brings Elm's promise of type safety and friendly error messages into the world of markup languages. Roughly speaking, I would sum it up as a kind of markup language that exposes its own internal structure (AST) so we can render it as we please: plain text, HTML, `elm-ui`, or any other Elm value really. Spoiler: in this post, we will be turning markup into functions.

> **tldr** the full example with comments is available in [this Ellie](https://ellie-app.com/6PN39PX42YRa1). The rest of the post will go over it in detail.

## A basic `elm-markup` document

Let's define a simple `Title` block. This block would tell `elm-markup` that whenever it sees this piece of text:

```
|> Title
    Interactive counteradder
```

It should render the indented text inside an `h1` tag. The code for that would look like this:

```elm
titleBlock : Mark.Block (Html msg)
titleBlock =
    Mark.block "Title"
        (\str ->
            h1 [] [ text str ]
        )
        Mark.string
```

Using this block, we can create a document like so:

```elm
Mark.compile
  (Mark.document identity titleBlock)
  """
|> Title
    Interactive counteradder
"""
```

> Rendering this into a running Elm app involves a few more steps that will become clear later in the post. If you're curious, the [package docs](https://package.elm-lang.org/packages/mdgriffith/elm-markup/latest/Mark#compile) are your friends.

## Adding a counter

The second block we will define is a named counter that renders some UI and emits the updated value as a message. The markup syntax looks like this:

```
|> Counter
    name = var1
```

And without further ado, here is the code for it:

```elm
counterBlock : Mark.Block (Html ( String, Float ))
counterBlock =
    Mark.record "Counter"
        (\name ->
            let
                -- This is a placeholder value
                -- We'll wire this up to proper state values in the next step
                val = 0
            in
            div
                []
                [ text (name ++ " = ")
                , button
                    [ onClick ( name, val - 1 )
                    ]
                    [ text "-"
                    ]
                , text (String.fromFloat val)
                , button
                    [ onClick ( name, val + 1 )
                    ]
                    [ text "+"
                    ]
                ]
        )
        |> Mark.field "name" Mark.string
        |> Mark.toBlock
```

## Adding interactivity

So far, this `elm-markup` document looks pretty static. In order to make it interactive and link counters to actual values by name, we will parse these them into functions that will allow us to inject values stored in the application model. This mind-bend is probably best illustrated by this shift in type signatures for the rendered block:

```elm
-- before
counterBlock : Mark.Block (Html ( String, Float ))
counterBlock = _

-- after
type alias Values = Dict.Dict String Float

counterBlock : Mark.Block (Values -> Html ( String, Float ))
counterBlock = _
```

There is nothing inherent in `elm-markup` that would stop us from taking this shift: if we're in charge of what value a piece of markup renders to, it might as well be a function. We can use this function to inject a dictionary of `Values` directly from our model and wire up the `(String, Float)` events that changes it in response to interacting with the counter buttons.

Our finished counter block looks like this:

```elm
-- We will use this helper throughout the rest of the example
getValue : String -> Values -> Float
getValue name values =
    values
        |> Dict.get name
        -- Values that are not kept track of yet are assumed to be the default
        |> Maybe.withDefault 0


counterBlock : Mark.Block (Values -> Html ( String, Float ))
counterBlock =
    Mark.record "Counter"
        (\name values ->
            let
                value = getValue name values
            in
            div
                []
                [ text (name ++ " = ")
                , button
                    [ onClick ( name, value - 1 )
                    ]
                    [ text "-"
                    ]
                , text (String.fromFloat value)
                , button
                    [ onClick ( name, value + 1 )
                    ]
                    [ text "+"
                    ]
                ]
        )
        |> Mark.field "name" Mark.string
        |> Mark.toBlock

```

## Doing something with our values

Now that our wiring is complete, we can simply define a `Sum` block that works with this markup:

```
|> Counter
    name = var1

|> Counter
    name = var2

|> Sum
    arg1 = var1
    arg2 = var2
```

What this block will do is simply take the values from the two named counters, add them together, and communicate the result as `var1 + var2 == 3`.

The sum block implementation looks like this:

```elm
sumBlock : Mark.Block (Values -> Html ( String, Float ))
sumBlock =
    Mark.record "Sum"
        (\arg1 arg2 values ->
            let
                res =
                    getValue arg1 values + getValue arg2 values
            in
            div
                []
                [ text (arg1 ++ " + " ++ arg2 ++ " == ")
                , text (String.fromFloat res)
                ]
        )
        |> Mark.field "arg1" Mark.string
        |> Mark.field "arg2" Mark.string
        |> Mark.toBlock
```

## Wiring it all up

Now that we have our blocks, we can write a method that compiles a document like this one:

```elm
markup : String
markup =
    """
|> Title
    Interactive counteradder

|> Counter
    name = var1

|> Counter
    name = var2
    
|> Sum
    arg1 = var1
    arg2 = var2
"""
```

The compiler for it would look like this:

```elm
compileMarkup : String -> Result String (Values -> Html ( String, Float ))
compileMarkup markdownBody =
    Mark.compile
        (Mark.document
            identity
            (Mark.manyOf
                [ titleBlock
                , counterBlock
                , sumBlock
                ]
            )
        )
        markdownBody
        |> (\res ->
                case res of
                    Mark.Success blocks ->
                        Ok
                            (\data ->
                                div []
                                    (List.map
                                        -- Inject data into each block
                                        -- This makes them into regular `elm-html` nodes
                                        (\block -> block data)
                                        blocks
                                    )
                            )

                    _ ->
                        Err "Compile error"
           )
```

And finally, the main `model`, `update` and `view`:

```elm
type alias Model =
    { values : Values
    }


type Msg
    = SetValue ( String, Float )


update : Msg -> Model -> Model
update msg model =
    case msg of
        SetValue ( key, val ) ->
            { model
                | values =
                    Dict.insert key
                        val
                        model.values
            }


view : Model -> Html Msg
view model =
    case compileMarkup markup of
        -- The success case yields a function that takes the current `Values` dictionary
        Ok viewByData ->
            viewByData model.values
                -- Map to the program message
                |> map SetValue

        Err err ->
            text err
```

For a full implementation, head to [the full example Ellie](https://ellie-app.com/6PN39PX42YRa1) or [the same code as a gist](https://gist.github.com/peterszerzo/a93ad31977b4bc467a17eccc8382110e).

## Where will we go next?

I know, I know, adding numbers is not that exciting. Eventually, we'll add sliders to make `elm-webgl` drawings like [this one](http://peterszerzo.com/sketches/envo), interactive.

Until then ☺️