Demystifying Jeremy's Interfaces

Thank you, Rupert, for your buffer example. You are a little bit further in your code than I am here, but I’ll catch up in this part.

Jeremy’s Original Code

So, after the (maybe not so interesting) introduction, let’s proceed straight to the example from Jeremy’s talk. I recommend reading his slides first, because I don’t repeat his introduction here.

First, he defines two mutually recursive types:

``````type Counter
= Counter CounterRecord

type alias CounterRecord =
{ up : Int -> Counter
, down : Int -> Counter
, value : Int
}
``````

This defines an “interface” named `Counter` together with the functions `up`, `down`, and `value`, all three combined in the `CounterRecord`.

There certainly are similarities between Jeremy’s `Counter` and my introductory `Thing` example:

Jeremy Pit
Exposed type `Counter` `Thing`
Exposed functions `up, down, value` `thingSize, thingDouble`
Underlying types `Int, List ()` `String, Int, Char`

But there are differences. The most important one: in Jeremy’s example, you don’t see the underlying types at all. In fact, I mentioned the underlying types `Int` and `List ()` just because Jeremy used them in his examples, as we’ll see soon, but there could be many more. The definition of the “interface” is totally independent of the actual “implementations”!

Great, but now the question is: how can I create such a `Counter`?

This is where the magic begins. Here’s Jeremy’s code to create two kinds of `Counter`s:

``````intCounter : Int -> Counter
intCounter =
impl CounterRecord
|> wrap (\raise rep n -> raise (rep + n))
|> wrap (\raise rep n -> raise (rep - n))
|> map Counter
|> init (\raise rep -> raise rep)

listCounter : Int -> Counter
listCounter =
impl CounterRecord
|> wrap (\raise rep n -> raise (List.repeat n () ++ rep))
|> wrap (\raise rep n -> raise (List.drop n rep))
|> map Counter
|> init (\raise n -> raise (List.repeat n ()))
``````

Both functions have the same signature: `Int -> Counter`. They return a `Counter` with the given `Int` as the starting value. An `intCounter` allegedly uses the type `Int` internally to represent the counter state, a `listCounter` uses the type `List ()`, with the length of the list representing the counter state. We’ll look at the details of these functions in the next parts…

Here are the helper functions that make it all possible:

``````impl : t -> (raise -> rep -> t)
impl constructor =
\_ _ -> constructor

wrap : (raise -> rep -> t) -> (raise -> rep -> (t -> q)) -> (raise -> rep -> q)
wrap method pipeline raise rep =
method raise rep |> pipeline raise rep

add : (rep -> t) -> (raise -> rep -> (t -> q)) -> (raise -> rep -> q)
add method pipeline raise rep =
method rep |> pipeline raise rep

map : (a -> b) -> (raise -> rep -> a) -> (raise -> rep -> b)
map op pipeline raise rep =
pipeline raise rep |> op

init : ((rep -> sealed) -> flags -> output) -> ((rep -> sealed) -> rep -> sealed) -> flags -> output
init initRep pipeline flags =
let
raise : rep -> sealed
raise rep =
pipeline raise rep
in
initRep raise flags
``````

As I’ve already written, at this point in the talk my reaction was simply:

Before trying to understand how this code works, I wanted to check whether it works at all.

Sidemark: the German translation of “to understand” is “begreifen”. This word contains another German verb: “greifen”, which means “to grab” or “to take hold of”. I like this connection, because in order to understand something it often helps if I can grab it, touch it and play with it.

We have functions to create `Counter`s, but the functions in the `CounterRecord` look a little bit unfamiliar. For example, for a function like `up`, which increases a `Counter`, I’d expect a signature like

``````up : Int -> Counter -> Counter
``````

but in the `CounterRecord` we have the function

``````up : Int -> Counter
``````

At this place, it helps to have some OO knowledge. In an object-oriented programming language, for example in Kotlin, we could define a `Counter` interface like this:

``````interface Counter {
fun up(n: Int): Counter
fun down(n: Int): Counter
val value: Int
}
``````

Note that here we have exactly the same function signatures as in Jeremy’s `CounterRecord`!

We continue with the OO example: Whenever I have an object which “implements” the `Counter` interface, then I can call the defined methods on this object. For example, in Kotlin, I could write a function to increment a `Counter` like this:

``````fun increment(counter: Counter): Counter =
counter.up(1)
``````

The function `increment` gets an object named `counter` which implements the `Counter` interface, and returns a new `Counter` object. Internally, it calls the `up` method on the `counter` object using the `counter.up(1)` syntax.

In Elm, the `increment` function could be defined (surprisingly similar) like this:

``````increment : Counter -> Counter
increment (Counter counterRecord) =
counterRecord.up 1
``````

Takeaway: Whenever I would call a method on an object implementing an interface in the OO world, in Elm, with Jeremy’s interfaces, I destructure the value to get the record with the functions and then call the appropriate one.

This allows me to implement some more Elm-like `Counter` functions:

``````up : Int -> Counter -> Counter
up n (Counter counterRecord) =
counterRecord.up n

down : Int -> Counter -> Counter
down n (Counter counterRecord) =
counterRecord.down n

value : Counter -> Int
value (Counter counterRecord) =
counterRecord.value
``````

And now I can start to play with Jeremy’s code.

Let’s create a list of different `Counter` types with different starting values:

``````myListOfCounters : List Counter
myListOfCounters =
[ intCounter 1, listCounter 2, intCounter 11, listCounter 12 ]
``````

Now we can check whether they were created with the correct initial values:

``````List.map value myListOfCounters
--> [ 1, 2, 11, 12 ]
``````

Yeah, it seems to work!

Let’s increment them and then get the new values:

``````myListOfCounters
|> List.map (up 3)
|> List.map value
--> [ 4, 5, 14, 15 ]
``````

And now decrement:

``````myListOfCounters
|> List.map (down 3)
|> List.map value
--> [ -2, 0, 8, 9 ]
``````

Hmm. At first glance, the second value looks suspicious. “2 - 3” should be “-1” and not “0”. At second glance, the second and fourth `Counter`s have been created with the function `listCounter`, so they internally use a value of type `List ()`, and the length of the list is used to represent the current state of the counter. Since the length of a list can’t be negative, the implementation of `listCounter` isn’t able to represent negative values. The lowest value we can get is “0”.

Great. The code works as expected!

In case you want to play with the code, too, Jeremy posted an Ellie with the code from his slides, and here’s an Ellie with my test code so far.

In the next part, we’ll start to get a better understanding of Jeremy’s code as we begin to modify it a little bit.

5 Likes

Good that you are taking an interest too and exploring the idea, I look forward to seeing how you modified the original set of functions.

I would like to point out a limitation of this pattern, as compared with typical OO languages. In Java say, you can do this:

``````public class MyClass
{
private int field;
public equals(Object other) {
if (o instanceof MyClass) { return (field == other.field); }
return false;
}
}
``````

(Hope that is right, haven’t touched Java for a while…) Even though the `field` is private, within the `MyClass` implementation it is possible to look inside another instance of `MyClass` and access the field.

It is interesting to try to do this with this Elm pattern, assuming the `int field` is not exposed in the interface in any way. As the hidden representation is kept inside the continuation, it is only possible to get to it from inside. It is not possible to get to the private field inside another instance and the Elm typechecker will prevent you from ever doing that.

Attempt in Elm looks like:

``````type MyClass = MyClass MyClassIF

type alias MyClassIF =
{ equals : MyClass -> Bool}

myClass =
impl MyClassIF
|> add (\rep other -> other.field == rep.field )
|> map MyClass
|> init (\raise rep -> raise rep)
``````

and the compiler gives the error:

``````This function cannot handle the argument sent through the (|>) pipe:

40|       impl MyClassIF
41|         |> add (\rep other -> other.field == rep.field )
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The argument is:

raise -> { b | field : a } -> (MyClass -> Bool) -> MyClassIF

But (|>) is piping it to a function that expects:

raise -> { b | field : a } -> ({ c | field : a } -> Bool) -> q
``````

Useful to understand the limits of this technique - the access rules are a bit more black and white than many OO languages typically allow. It would also be interesting to see how these things are dealt with in the OO calculi such as Ob<:, which were popular around the late 90s as accademic tools for exploring the semantics of OO. A Theory of Objects | SpringerLink

2 Likes

First: Merry Christmas to everyone!

Second: Rupert, thank you once more for your interesting comparison between Jeremy’s interfaces and Java classes. I tested your example in IntelliJ, and indeed it works (after some minor corrections). You’re right that there’s no way to get at the internals of an “interface” in Elm. I’ll come back to this in a later post.

W r t a - Rename, Type alias, Applicative functor

Let’s start to look at the code in more detail.

`Counter` is just one example for the usage of Jeremy’s interfaces. In his talk, Jeremy also used the technique with pages of an SPA application, and Rupert used it with his buffer implementation. So, first, I’ll try to hide most of the `Counter`-specific things.

We can simply rename the types and the `Counter` functions:

What Old New
Main type of the interface `Counter` `T`
Type constructor `Counter` `C`
Record with the operations `CounterRecord` `O`
Function to create an `Int` counter `intCounter` `intT`
Function to create a `List ()` counter `listCounter` `listT`

This gives us

``````type T
= C O

type alias O =
{ up : Int -> T
, down : Int -> T
, value : Int
}

intT : Int -> T
intT =
impl O
|> wrap (\raise rep n -> raise (rep + n))
|> wrap (\raise rep n -> raise (rep - n))
|> map C
|> init (\raise rep -> raise rep)

listT : Int -> T
listT =
impl O
|> wrap (\raise rep n -> raise (List.repeat n () ++ rep))
|> wrap (\raise rep n -> raise (List.drop n rep))
|> map C
|> init (\raise n -> raise (List.repeat n ()))
``````

Let’s examine the steps of the pipelines in `intT` and `listT`. In order to do so, I’ll change every line but the first into a comment and let my editor (VS Code) tell me the resulting type:

``````intT : (Int -> T) -> Int -> (Int -> T) -> (Int -> T) -> Int -> O
intT =
impl O
-- |> wrap (\raise rep n -> raise (rep + n))
-- |> wrap (\raise rep n -> raise (rep - n))
-- |> map C
-- |> init (\raise rep -> raise rep)
``````

Next, I uncomment the second line, note the resulting type, and so on. In the end, this gives us

``````intT : Int -> T
intT =
impl O
-- (Int -> T) -> Int -> (Int -> T) -> (Int -> T) -> Int -> O
|> wrap (\raise rep n -> raise (rep + n))
-- (Int -> T) -> Int -> (Int -> T) -> Int -> O
|> wrap (\raise rep n -> raise (rep - n))
-- (Int -> T) -> Int -> Int -> O
-- (Int -> T) -> Int -> O
|> map C
-- (Int -> T) -> Int -> T
|> init (\raise rep -> raise rep)
-- Int -> T

listT : Int -> T
listT =
impl O
-- (List () -> T) -> List () -> (Int -> T) -> (Int -> T) -> Int -> O
|> wrap (\raise rep n -> raise (List.repeat n () ++ rep))
-- (List () -> T) -> List () -> (Int -> T) -> Int -> O
|> wrap (\raise rep n -> raise (List.drop n rep))
-- (List () -> T) -> List () -> Int -> O
-- (List () -> T) -> List () -> O
|> map C
-- (List () -> T) -> List () -> T
|> init (\raise n -> raise (List.repeat n ()))
-- Int -> T
``````

In theses types, there’s a repeating pattern, which Jeremy named “the pipeline shape” in his talk:

``````(Int     -> T) -> Int     -> ...
(List () -> T) -> List () -> ...
``````

Let’s define a type alias for it. `Int` and `List ()` are the hidden types used to represent the internal state of the counter. Jeremy names them `rep`. I’ll stick with one-letter names and use `r`. For the type `T` I’ll use the type variable `t`, and for the remaining “…” an innocent looking `a`. For the type alias itself I’d choose the name `⁉️`, but once again I use a one-letter name. This gives me the following type alias:

``````type alias W r t a =
(r -> t) -> r -> a
``````

Note 1: Using type aliases for functions can make function signatures harder to understand, especially if the functions return those type aliases. In this case, it helps me to better understand the roles of Jeremy’s magic internal functions.

Note 2: In Jeremy’s code, the function `(r -> t)` is named `raise`, the second `r` is named `rep`. So whenever we see `(raise -> rep -> xyz)`, we can replace it with `W r t xyz`.

Using the new type alias and renaming the type variables, the function signatures can be changed like this:

``````impl : t -> (raise -> rep -> t)
-->
impl : a -> W r t a
``````
``````wrap : (raise -> rep -> t) -> (raise -> rep -> (t -> q)) -> (raise -> rep -> q)
-->
wrap : W r t a -> W r t (a -> b) -> W r t b
``````
``````add : (rep -> t) -> (raise -> rep -> (t -> q)) -> (raise -> rep -> q)
-->
add : (r -> a) -> W r t (a -> b) -> W r t b
``````
``````map : (a -> b) -> (raise -> rep -> a) -> (raise -> rep -> b)
-->
map : (a -> b) -> W r t a -> W r t b
``````
``````init : ((rep -> sealed) -> flags -> output) -> ((rep -> sealed) -> rep -> sealed) -> flags -> output
-->
init : ((r -> t) -> i -> t) -> W r t t -> i -> t
``````

Remark: the new signatures are not as general as the old ones, but they match the actual usage, so the new code still compiles.

Ok, the new signature of the `wrap` function looks familiar! It’s the well-known `andMap` function! You can find `andMap` functions in many packages, even for basic Elm types:

``````Maybe.Extra.andMap       : Maybe a    -> Maybe (a -> b)    -> Maybe b
Result.Extra.andMap      : Result e a -> Result e (a -> b) -> Result e b
Json.Decode.Extra.andMap : Decoder a  -> Decoder (a -> b)  -> Decoder b

wrap                     : W r t a    -> W r t (a -> b)    -> W r t b
``````

`W r t a` is what in other functional languages is called an “Applicative Functor”.

To be an Applicative Functor, a type has to have two other functions, besides an `andMap` function. One is a `map` function, and we indeed have it. Jeremy named it already in the Applicative Functor jargon:

``````map : (a -> b) -> W r t a -> W r t b
``````

The second missing function for an Applicative Functor is called `pure`, and it is right there, under another name:

``````impl : a -> W r t a
``````

In Elm, there are many different names for such a function:

``````Maybe.Just :          a -> Maybe a
Result.Ok  :          a -> Result e a
Json.Decode.succeed : a -> Decoder a
``````

So why not rename some of the internal functions? What about

``````impl --> succeed
wrap --> andMap
``````

I chose to rename `add`, too, because its signature looks very similar to that of `andMap`, and I therefore wanted to have a name that resembles `andMap`.

While renaming the functions, I’ll also rename the function parameters. Here’s the result:

``````succeed : a -> W r t a
succeed a _ _ =
a

andMap : W r t a -> W r t (a -> b) -> W r t b
andMap rtra rtrab rt r =
rtrab rt r (rtra rt r)

andAdd : (r -> a) -> W r t (a -> b) -> W r t b
andAdd ra rtrab rt r =
rtrab rt r (ra r)

map : (a -> b) -> W r t a -> W r t b
map ab rtra rt r =
ab (rtra rt r)

init : ((r -> t) -> i -> t) -> W r t t -> i -> t
init rtit rtrt i =
let
rt : r -> t
rt r =
rtrt rt r
in
rtit rt i
``````

Why the … did I choose completely meaningless and unreadable parameter names?

Well, for the same reason that I renamed the original types. If I only have to look at the types and not at names like “method”, “pipeline”, and “raise”, it helps me to understand the effect of these functions.

The names I chose are not completely meaningless, of course. Let’s look at `andMap`:

``````andMap : W r t a -> W r t (a -> b) -> W r t b
andMap rtra rtrab rt r =
rtrab rt r (rtra rt r)
``````

The first parameter is of type `W r t a`, which is an abbreviation of `(r -> t) -> r -> a`. If you join these letters, you get the name I chose for the first parameter: “rtra”.

The second parameter is of type `W r t (a -> b)` or `(r -> t) -> r -> (a -> b)`, giving me “rtrab”.

The function result is `W r t b` or `(r -> t) -> r -> b`. If a function like `andMap` returns another function like here, we can remove the parentheses around the function result:

``````andMap :
((r -> t) -> r -> a)
-> ((r -> t) -> r -> (a -> b))
-> ((r -> t) -> r -> b)
``````

is the same as

``````andMap :
((r -> t) -> r -> a)
-> ((r -> t) -> r -> (a -> b))
-> (r -> t)
-> r
-> b
``````

So, we could as well say that `andMap` takes two more parameters: one of type `(r -> t)`, named “rt”, and the last one of type `r` named “r”. The function finally returns a value of type `b`.

This notation helps me to visually check whether the types are correct. Applying a function `a -> b` (which would be named “ab”) to a value of type `a` (named “a”) yields a value of type `b`. In my notation it means that the type of “ab a” is “b”. To get the resulting type (“b”), I only need to remove the name of the argument (“a”) from the beginning of the function name (“ab”). Here’s how this works in the case of `andMap`:

``````andMap : W r t a -> W r t (a -> b) -> W r t b
andMap rtra rtrab rt r =
rtrab rt r (rtra rt r)
--------    -------
rab   r     ra   r
-------     ------
ab         a
------------
b
``````

It’s no surprise that this code type checks “visually”, too. This way of naming the parameters might help me later when I start to rearrange the code.

Using the new function and parameter names, I think I finally understand all the internal functions, with one exception: `init`. I don’t understand how it works, but at least I can visually verify that the types are correct. Here’s the function again:

``````init : ((r -> t) -> i -> t) -> W r t t -> i -> t
init rtit rtrt i =
let
rt : r -> t
rt r =
rtrt rt r
in
rtit rt i
``````

The second parameter of type `W r t t` or `(r -> t) -> r -> t` seems to be especially important. The `let` clause somehow transforms this to a function of type `r -> t`, which is exactly what is needed as the parameter “raise” in the `intT` and `listT` functions. It is a function that takes an `r` (also called “rep”, a value of the internal type like `Int` or `List ()`) and transforms it to the type `t`, in our case to the main type `T`.

It’s still mysterious to me…

How do the user-supplied functions look like after the renaming? I’ll leave the type comments in the code, now using the type alias:

``````intT : Int -> T
intT =
succeed O
-- W Int T ((Int -> T) -> (Int -> T) -> Int -> O)
|> andMap (\raise rep n -> raise (rep + n))
-- W Int T ((Int -> T) -> Int -> O)
|> andMap (\raise rep n -> raise (rep - n))
-- W Int T (Int -> O)
-- W Int T O
|> map C
-- W Int T T
|> init (\raise rep -> raise rep)
-- Int -> T

listT : Int -> T
listT =
succeed O
-- W (List ()) T ((Int -> T) -> (Int -> T) -> Int -> O)
|> andMap (\raise rep n -> raise (List.repeat n () ++ rep))
-- W (List ()) T ((Int -> T) -> Int -> O)
|> andMap (\raise rep n -> raise (List.drop n rep))
-- W (List ()) T (Int -> O)
-- W (List ()) T O
|> map C
-- W (List ()) T T
|> init (\raise n -> raise (List.repeat n ()))
-- Int -> T
``````

If you know `Parser` or `Decoder` pipelines, at least the first steps should be understandable now:

• We start with `succeed O`, where `O` is the record constructor function, with expects values for the `up`, `down` and `value` fields.

• We then use `andMap` and `andAdd` to supply the values for the `up`, `down`, and `value` fields. What we get after these steps is a `W r T O` value, with `r` being the internal type, either `Int` or `List ()`.

• With `map C` this is changed to a value of type `W r T T`, which is exactly the special type mentioned above, which lets `init` do its magic to internally generate a function of type `r -> T`.

• In both cases, `init` finally gives us a function `Int -> T` (this `Int` is the starting value for the counter).

Let’s take a final look at the parameters of `andMap`, `andAdd` and `init`:

In the counter example, `andMap` always gets a function with 3 parameters: “raise”, the type-raising function of type `r -> T`, “rep”, the internal representation of the current counter state of type `r`, and “n”, the `Int` parameter of the `up` and `down` functions.

In these functions, we calculate the result (again of type `r`). For example, in the `down` function, the values for the new state are `rep - n` and `List.drop n rep`, respectively. In the end, the “raise” function is applied to the result, so that we get back a value of type `T`.

`andAdd` is similar to `andMap`. In this example, it takes a function of type `r -> Int`, which returns the current counter state. Here it isn’t necessary to change the type of the result, so the functions don’t need a “raise” parameter.

To make it even more explicit and more similar to the `andMap` calls, I would change the `andAdd` lines:

``````        |> andAdd identity
-->
``````
``````        |> andAdd List.length
-->
|> andAdd (\rep -> List.length rep)
``````

Finally, `init` gets a function with 2 parameters: again “raise”, the type-raising function of type `r -> T`, and a second parameter, named “rep” in the `intT` function, and “n” in the `listT` function. In both cases it is the starting value for the counter of type `Int`, therefore I would name the parameter “n” in both cases.

In these functions, we create an internal representation of the starting value (of type `r`), and then apply the “raise” function to the result, so that we get back a value of type `T`, just like in the `andMap` cases.

With the minor changes mentioned above, the functions now look like this:

``````intT : Int -> T
intT =
succeed O
|> andMap (\raise rep n -> raise (rep + n))
|> andMap (\raise rep n -> raise (rep - n))
|> map C
|> init (\raise n -> raise n)

listT : Int -> T
listT =
succeed O
|> andMap (\raise rep n -> raise (List.repeat n () ++ rep))
|> andMap (\raise rep n -> raise (List.drop n rep))
|> andAdd (\rep -> List.length rep)
|> map C
|> init (\raise n -> raise (List.repeat n ()))
``````

At least for me, it is now much easier to understand what we are doing here.

In the next part, I’ll start to change the structure of the code.

3 Likes

Also worth noting that we don’t really need `add`, its just a special case of `wrap`. Neat in the sense that it reduces the whole thing down to just the essential `map`, `andMap`, `succeed` and `pure`.

``````-- Shapes with bounding boxes.
type Shape = Shape ShapeIF

type alias ShapeIF msg =
{ bbox : BoundingBox2d Unitless Local
}

shape bboxFn =
impl ShapeIF
|> add (\rep -> bboxFn rep)
|> map Shape
|> init (\raise rep -> raise rep)

-- Using wrap
shape bboxFn =
impl ShapeIF
|> wrap (\raise rep -> bboxFn rep) -- Didnt need the raise
|> map Shape
|> init (\raise rep -> raise rep)
``````
3 Likes

Change #1: Remove the Record Constructor Function

In this part, we’ll start to look at the code from different angles: it could be from the point of view of the author of the interface technique, or from a user’s point of view.

While lying in my bed this morning, pondering about what to write in this part, I thought: why don’t you introduce some fictitious characters for those different viewpoints? It felt interesting, so let’s try it. So far, I have been simply analyzing the code. From now on, I’ll be more like telling a story. (I split the table of contents at the beginning of this series into the appropriate sections.) So let’s start our story. Of course, any similarities with existing persons are purely coincidental

The first character is Jeremy. Jeremy is a real type system wizard. He manages the magic internal functions we’ve analyzed in the last part. Jeremy packaged the functions and the type alias `W` in a neat module called `Interface`:

``````module Interface exposing (W, andAdd, andMap, init, map, succeed)
``````

The next character, let’s name him Rupert, is a user of Jeremy’s `Interface` module. He’s very versed in applying the interface technique to various use cases, be it buffers, geometric shapes, or recently counters. Here’s his `Counter` code again:

``````import Interface as IF

type Counter
= Counter CounterRecord

type alias CounterRecord =
{ up : Int -> Counter
, down : Int -> Counter
, value : Int
}

intCounter : Int -> Counter
intCounter =
IF.succeed CounterRecord
|> IF.andMap (\raise rep n -> raise (rep + n))
|> IF.andMap (\raise rep n -> raise (rep - n))
|> IF.map Counter
|> IF.init (\raise n -> raise n)

listCounter : Int -> Counter
listCounter =
IF.succeed CounterRecord
|> IF.andMap (\raise rep n -> raise (List.repeat n () ++ rep))
|> IF.andMap (\raise rep n -> raise (List.drop n rep))
|> IF.andAdd (\rep -> List.length rep)
|> IF.map Counter
|> IF.init (\raise n -> raise (List.repeat n ()))
``````

(Note that we re-introduced the original type and function names, because as a user, Rupert’s main interest is the domain-specific logic.)

Overall, Rupert is fine with this code, but there’s something that bothers him. Lue, a friend of Rupert, keeps telling him:

Rupert, you never should use these record type alias constructor functions, never ever!

Rupert understands his friend’s concern. He knows that, because of the problematic usage of the `CounterRecord` function in the `IF.succeed` lines, the `intCounter` and `listCounter` implementations are dependent on the field order in `CounterRecord`. Rupert is responsible enough to not change the field order without good reason, but he is not the only one working on the code. For example, there’s his new colleague, Pit. Rupert knows that Pit loves to touch and to modify every single piece of code, whether he understands it or not, just to get a better feeling for it.

There’s no doubt that one day Pit will find the `CounterRecord` definition. If he changes the field order to something like

``````type alias CounterRecord =
{ value : Int
, up : Int -> Counter
, down : Int -> Counter
}
``````

it would be no problem, because the Elm compiler would show him where the other code had to be changed, too. But if he just swaps the `up` and `down` fields as in

``````type alias CounterRecord =
{ down : Int -> Counter
, up : Int -> Counter
, value : Int
}
``````

the code would still compile, and maybe Pit would check in his change, despite the fact that their app wouldn’t work as before.

So Rupert looks at the implementation of the `listCounter` function again, trying to understand how the problematic `CounterRecord` constructor function is used, and whether he can do anything against it. He looks at the types that every step in the pipeline returns, just as we did in the last part of the series, and he notices that the type after the `IF.andAdd` call looks interesting:

``````listCounter : Int -> Counter
listCounter =
IF.succeed CounterRecord
|> IF.andMap (\raise rep n -> raise (List.repeat n () ++ rep))
|> IF.andMap (\raise rep n -> raise (List.drop n rep))
|> IF.andAdd (\rep -> List.length rep)
-- IF.W (List ()) Counter CounterRecord
|> IF.map Counter
|> IF.init (\raise n -> raise (List.repeat n ()))
``````

The type is

``````IF.W (List ()) Counter CounterRecord
``````

which is just a shorthand for

``````(List () -> Counter) -> List () -> CounterRecord
``````

Here, `CounterRecord` is the record type, not the problematic record constructor function his friend Lue is talking about. Rupert says to himself:

Couldn’t I directly create such a value myself?

The type says, that it needs to be a function taking two parameters:

• a function (typically called “raise” in the `listCounter` code) which can turn the internal representation of the counter state, in this case `List ()`, into the main `Counter` type
• the current counter state (normally called “rep”), in the form of the internally used `List ()`

The function should return a `CounterRecord`. So Rupert starts with:

``````firstFourSteps : (List () -> Counter) -> List () -> CounterRecord
firstFourSteps raise rep =
...
``````

He needs to return a record with the fields `up`, `down`, and `value`, and suddenly the function body just flows from his fingers:

``````firstFourSteps : (List () -> Counter) -> List () -> CounterRecord
firstFourSteps raise rep =
{ up = \n -> raise (List.repeat n () ++ rep)
, down = \n -> raise (List.drop n rep)
, value = List.length rep
}
``````

This function type-checks! Rupert doesn’t want to keep the code in the form of a top-level function, so he just takes the function body and uses it to replace the first four steps of the pipeline:

``````listCounter : Int -> Counter
listCounter =
(\raise rep ->
{ up = \n -> raise (List.repeat n () ++ rep)
, down = \n -> raise (List.drop n rep)
, value = List.length rep
}
)
|> IF.map Counter
|> IF.init (\raise n -> raise (List.repeat n ()))
``````

He runs a few tests, and the code works! Rupert calls Jeremy, the author of the `Interface` module, and tells him what he changed in his code and why he did it.

Jeremy, being the type system wizard he is, quickly recognizes the potential of Rupert’s new way to use the `Interface` module. He not only could completely remove the `succeed`, `andMap`, and `andAdd` functions, but even go one step further: the next step in the user’s pipeline would always be

``````        |> IF.map C
``````

with `C` being the constructor function of the type

``````type T
= C O
``````

(As a type system wizard, Jeremy prefers to use the abstract names…)

Jeremy is a friendly wizard who always wants the best for the users of his module, so he’ll save his users from having to write the `IF.map` step every time. He removes the `map` function from the `exposes` list of the `Interface` module, too, and exposes a new function instead. For this function he chooses a name which is still well-known to his users. In a previous version of the module, the `success` function has been named `impl` to signal the start of the implementation of an interface.

This is the signature of the new function:

``````impl : (o -> t) -> W r t o -> W r t t
``````

Jeremy really is a genius! This is the same signature as that of the previous `map` function, with slightly renamed type variables to better reflect the desired usage. By simply renaming the `map` function, he enables his users to replace the first lines of an interface pipeline with

``````    IF.impl C
(\rt r ->
...
)
``````

He publishes a new version of the interface module and tells Rupert how to use it.

Here’s Rupert’s new code for the `intCounter` and `listCounter` functions:

``````intCounter : Int -> Counter
intCounter =
IF.impl Counter
(\raise rep ->
{ up = \n -> raise (rep + n)
, down = \n -> raise (rep - n)
, value = rep
}
)
|> IF.init (\raise n -> raise n)

listCounter : Int -> Counter
listCounter =
IF.impl Counter
(\raise rep ->
{ up = \n -> raise (List.repeat n () ++ rep)
, down = \n -> raise (List.drop n rep)
, value = List.length rep
}
)
|> IF.init (\raise n -> raise (List.repeat n ()))
``````

I don’t know about you, but I very much like how this code reads. Here’s an Ellie with the actual code.

In the evening, Rupert meets Lue in a bar and proudly tells him that he not only managed to completely remove record alias constructors from their codebase, but also successfully added Lue’s `elm-review` rule which forbids record type alias constructors to the set of their project’s `elm-review` rules.

Later at home, he falls asleep relieved, knowing that even his colleague Pit can’t break the code simply by changing the order of the `CounterRecord` fields.

Meanwhile, Jeremy, the type system wizard, has a new idea for improving his `Interface` module even further…

4 Likes

Change #2: Redesigned Initialization

Happy new year! I’ll continue the story…

Rupert and Jeremy are having lunch, and obviously they also talk about interfaces. Rupert tells Jeremy:

You know that I have functions like

``````intCounter : Int -> Counter
``````

and

``````listCounter : Int -> Counter
``````

which take a starting value and create a `Counter`, appropriately backed by either an `Int` or a `List ()`. But I noticed that the users of these functions don’t really need the starting value, because they always create counters starting from zero, yet. So I was thinking whether I could remove this parameter. In the current function implementations, the parameter isn’t declared explicitly, but if it would be, I would have to pass it as the last parameter to the `init` function of the `Interface` module:

``````listCounter : Int -> Counter
listCounter start =
...
|> (\pipeline ->
IF.init (\raise n -> raise (List.repeat n ()))
pipeline
start
)
``````

(I had to introduce the current value of the pipeline as an intermediate parameter.)

So I thought: what would I do, if I wanted to pass two or more parameters to the “initialization function” (the first parameter of `IF.init`), or no parameter at all?

You know what, Rupert? Right after we changed the `Interface` module last time, I’ve been looking at the remaining code. The `impl` function looks OK to me, but I had the feeling that the `init` function could still be improved. Before I tell you what I’ve been thinking about, please do me a favour: could you briefly look at the usages of the `init` function in your code? I suppose that the “initialization functions” you pass to the `init` function all have the following shape:

1. They create an initial value of the representation type. In my function signatures, it has the type `r`.
2. As the last step, they pass that value to the “raise” function of type `r -> t`, which I pass as the first parameter to your initialization functions.

After lunch, Rupert skims through their code.

``````intCounter : Int -> Counter
intCounter =
...
|> IF.init (\raise n -> raise n)
--                                    -
--   initial value of type Int: n
--   passed to "raise"

listCounter : Int -> Counter
listCounter =
...
|> IF.init (\raise n -> raise (List.repeat n ()))
--                                     ----------------
--   initial value of type List (): List.repeat n ()
--   passed to "raise"

fifo : List (Node state) -> Buffer state
fifo =
...
|> IF.init (\raise rep -> raise rep)
--                                      ---
--   initial value of type List (Node state): rep
--   passed to "raise"

shape bboxFn =
...
|> IF.init (\raise rep -> raise rep)
--                                      ---
--   initial value: rep
--   passed to "raise"
``````

He calls Jeremy and tells him: You’re right. They all have the shape you supposed. How did you know that?

The initialization functions, which you pass as the first parameter to the `init` function, have to have the signature `(r -> t) -> i -> t` (in my abstract type notation). This means that they have to return a value of type `t`, in the counter example a value of type `Counter`.

How can you create such a value? The only way to do this is by calling the magic “raise” function, which takes a value of the internal representation type `r`. So, in your initialization functions, you first have to create this internal value and then pass it to the “raise” function.

After I recognized this, I knew that I could remove this last step (calling the “raise” function) from the user-supplied initialization functions and move it into the `Interface` module. This would make the `init` function look like

``````init : (i -> r) -> W r t t -> i -> t
init ir rtrt i =
let
rt : r -> t
rt r =
rtrt rt r
in
rt (ir i)
``````

Now the initialization functions have the simpler type `i -> r`. They don’t get the “raise” function anymore, and just have to create an `r` value from the given `i` value. I’ll then call the former “raise” function, internally called “rt”, in the `init` function.

We can go even further: why should the `init` function be responsible to take the `i` value from the user and then directly pass it to the user-supplied initialization function? I don’t do anything else with the `i` value. Why shouldn’t the user perform the `ir i` call, which I do on the last line, in her own code and only give me the resulting `r` value? This would reduce the `init` function to

``````init : r -> W r t t -> t
init r rtrt =
let
rt : r -> t
rt r_ =
rtrt rt r_
in
rt r
``````

An example for its usage:

``````listCounter : Int -> Counter
listCounter start =
impl Counter
...
|> init (List.repeat start ())
``````

But why stop here? Syntactically, all the `init` function does in the “impl |> init” pipeline is to get another parameter of type `r`. If the `impl` function would take this parameter itself, we wouldn’t need a pipeline at all! We could combine the code of the `impl` and the `init` functions into one.

Before:

``````impl : (o -> t) -> W r t o -> W r t t
impl ot rtro rt r =
ot (rtro rt r)

init : r -> W r t t -> t
init r rtrt =
let
rt : r -> t
rt r_ =
rtrt rt r_
in
rt r
``````

After:

``````impl : (o -> t) -> W r t o -> r -> t
impl ot rtro r =
let
rtrt : W r t t
rtrt rt_ r_ =
ot (rtro rt_ r_)

rt : r -> t
rt r_ =
rtrt rt r_
in
rt r
``````

If we inline the internal `rtrt` function and remove the outer “r” parameter (to reduce shadowing), we get

``````impl : (o -> t) -> W r t o -> r -> t
impl ot rtro =
let
rt : r -> t
rt r =
(\rt_ r_ -> ot (rtro rt_ r_)) rt r
in
rt
``````

We can simplify the last line in the internal `rt` function to get the final version:

``````impl : (o -> t) -> W r t o -> r -> t
impl ot rtro =
let
rt : r -> t
rt r =
ot (rtro rt r)
in
rt
``````

In fact, in the next version of the `Interface` module, I’ll even remove the `W` type alias, because in the meantime it is only used once. The signature of the `impl` function then will be

``````impl : (o -> t) -> ((r -> t) -> r -> o) -> (r -> t)
``````

Oh, by the way, Rupert, did you notice that we don’t have a value of type `i` anymore in the signature? Coming back to your original question, this means that you are completely free to choose how you want to create the initial `r` value. You can take one value from your user as before, two or more values, or no value at all. Here’s a version of your `listCounter` function starting always at zero:

``````zeroListCounter : Counter
zeroListCounter =
IF.impl Counter
(\raise rep ->
{ up = \n -> raise (List.repeat n () ++ rep)
, down = \n -> raise (List.drop n rep)
, value = List.length rep
}
)
[]
``````

Note how you now simply pass the initial `[]` value of type `List ()` (representing the counter value “zero”) as the last parameter to the `impl` function?

Rupert nods and says: it always feels extremely satisfying if you manage to improve your code just by removing parts of it, doesn’t it?

You can find the actual code in this Ellie. In the next part, we’ll be adding some code again…

3 Likes

So you have reduced the initial set of functions down to just a single function? Pretty neat.

``````impl : (o -> t) -> ((r -> t) -> r -> o) -> (r -> t)
impl ot rtro =
let
rt : r -> t
rt r =
ot (rtro rt r)
in
rt
``````
2 Likes

Do you think that using continuations to chain state like this, could lead to this compiler bug? Elm seems able to form bad closures when returning a lambda function. So far I have not run into any issues whilst exploring this.

1 Like

Hmm, I didn’t know these issues. But aren’t they caused by an error in Elm’s tail call optimization? If this is the cause, then I don’t think that Jeremy’s interface technique is affected by this problem, because it doesn’t use tail recursive functions, or does it?

Maybe the problem only shows up with the specific combination of TCO and returning a lambda - I don’t know. I get a little suspicious sometimes if you push the Elm compiler to do something weird thats all. Might take a little time this week to investigate this bug more fully and understand exactly what is needed to cause it.

Change #3: Separate Interface from Implementation

Remember that the characters in the story are purely fictional…

Time has passed, and Rupert is using interfaces in more and more places. He even wrote a blog post about it. Reflected as he is, he notices that his role in the interface business has shifted recently. He is mainly defining the interfaces, leaving the implementation to others, for example to Pit, who loves to use `List`s for everything, just as in the `listCounter` example.

Just like Jeremy managed to hide the “raise” function from the users of his `init` function in the last part, Rupert would love to hide the “raise” function from the folks implementing his interfaces.

Applying the “raise” function is something which has to be repeated in each interface implementation:

``````intCounter : Int -> Counter
intCounter start =
impl Counter
(\raise rep ->
{ up = \n -> raise (rep + n)
, down = \n -> raise (rep - n)
, value = rep
}
)
start

listCounter : Int -> Counter
listCounter start =
impl Counter
(\raise rep ->
{ up = \n -> raise (List.repeat n () ++ rep)
, down = \n -> raise (List.drop n rep)
, value = List.length rep
}
)
(List.repeat start ())
``````

Wouldn’t it be nice, if he could somehow hide the call of the “raise” function, so that the code would look more like

``````intCounter : Int -> Counter
intCounter start =
impl Counter
(\rep ->
{ up = \n -> rep + n
, down = \n -> rep - n
, value = rep
}
)
start

listCounter : Int -> Counter
listCounter start =
impl Counter
(\rep ->
{ up = \n -> List.repeat n () ++ rep
, down = \n -> List.drop n rep
, value = List.length rep
}
)
(List.repeat start ())
``````

As always in a situation like this, he calls Jeremy and tells him about his new idea. Jeremy is skeptical at first, but promises to get back to Rupert.

Indeed, just a few days later, Jeremy calls back and says:

Jeremy

Rupert, I’m sorry, but this isn’t something I can do for you, because there’s no general way to define this behavior for each and every interface. But if you are willing to tell me which fields of the operations record need a “raise” call and which don’t, then I can change the `impl` function in a way that lets you completely hide the “raise” function from the implementers of your interfaces.

Of course, Rupert would gladly provide this information to the `Interface` module, so Jeremy sends him the new version of the module and guides him in the process to transform Rupert’s existing code to the new version.

Jeremy

OK Rupert, let’s start: first, you need to add a type parameter to the operations record. And could you rename the operations record from `CounterRecord` to, say, `CounterOperations`? In my mind they are always interface “operations”…

Rupert is fine with the name change. After all, he doesn’t know yet how the code will look like in the end, so he trusts Jeremy that the new name will be well chosen. The new types look like this:

``````type Counter
= Counter (CounterOperations Counter)

type alias CounterOperations t =
{ up : Int -> t
, down : Int -> t
, value : Int
}
``````

Jeremy

Now you have to use a new function from the `Interface` module. The new version of the module doesn’t expose an `impl` function anymore, but a new `interface` method. You’ll see why I renamed the function, soon.

Jeremy’s new method has the following signature:

``````interface :
(ot -> t)
-> ((r -> t) -> or -> ot)
-> (r -> or)
-> (r -> t)
``````

Rupert has worked with Jeremy’s code long enough to easily map the abstract type names to his concrete use cases. Here’s the mapping for the `Counter` example:

Abstract Concrete
t Counter
r hidden type, for example `Int` or `List ()`
ot CounterOperations Counter
or CounterOperations r

Rupert

Ok, Jeremy, I can translate the types to the counter example. What should I do with the function?

Jeremy

You as the interface designer should publish the `interface` function partially applied with the first two parameters.

Oh-K… Rupert looks at the first two parameters in more detail.

Rupert

The first parameter of type `ot -> t` (in my example `CounterOperations Counter -> Counter`) is the constructor function (`Counter`).

Jeremy

Right. I can tell you a little bit about the second parameter of type `(r -> t) -> or -> ot`.

It is a function that takes the well-known “raise” function of type `r -> t` and an operations record, parameterized with the type `r` (in your example `CounterOperations r`).

The function should return the same operations record, but now parameterized with the type `t` (in your example `CounterOperations Counter`).

So, basically, this is just a `map` function on the operations record.

Rupert

OK, a `map` function seems to be easy.

Jeremy

If I might add another naming proposal: I’d name the partially applied function “xxxInterface”.

Rupert writes the following `counterInterface` function:

``````counterInterface =
interface Counter <|
\raise ops ->
{ up = ops.up >> raise
, down = ops.down >> raise
, value = ops.value
}
``````

His IDE shows that the function signature is

``````counterInterface : (r -> CounterOperations r) -> (r -> Counter)
``````

Rupert

Hey, I’m starting to understand how your new `interface` function helps to separate the interface definition from the implementation:

The second parameter tells the `Interface` module that the `up` and `down` fields of the `CounterOperations` record need a “raise” call, but the `value` field doesn’t. This is exactly the information I wanted to hide from the interface implementers.

Plus, I think I also understand why you named the function “interface”: the interface designers use it to define their interfaces.

So what can the interface implementers do with my `counterInterface` function?

Jeremy

They need to provide the next parameter, the one with type `r -> CounterOperations r`. This implements the counter operations for one specific internal representation type.

Rupert

No problem. I can easily extract the logic from the previous implementations for `Int` and `List ()`.

As these will be the interface implementations, I’d probably name them `counterImplInt` and `counterImplList`. What do you think?

Jeremy

The names sound great. Go ahead.

Rupert writes the following implementations:

``````counterImplInt : Int -> CounterOperations Int
counterImplInt rep =
{ up = \n -> rep + n
, down = \n -> rep - n
, value = rep
}

counterImplList : List () -> CounterOperations (List ())
counterImplList rep =
{ up = \n -> List.repeat n () ++ rep
, down = \n -> List.drop n rep
, value = List.length rep
}
``````

Rupert

Yes! This is exactly what I wanted to achieve: the implementers just have to be concerned with their internal type and nothing else. No need to call a “raise” function. All they need to do is to implement the interface operations using their internal type.

Jeremy

You’ve got it.

Now look again at the signature of the `interface` function. The last remaining part is `r -> t`.

When the implementers pass one of the “implementation” functions to the provided “interface” function, they get a function of type `r -> t`, which lets them create “objects” implementing the interface.

Rupert

Let’s just do this for the `listCounter` example. I really want to see the final result.

He writes:

``````listCounter : Int -> Counter
listCounter start =
counterInterface counterImplList <|
List.repeat start ()
``````

Rupert

It compiles!

What do you think? Should I reverse the order? Let’s see…

``````intCounter : Int -> Counter
intCounter start =
start
|> counterInterface counterImplInt
``````

Rupert

I’ll have to play with this a little bit more. At least the code is very modular now. Thank you very much, Jeremy.

Jeremy

If you’re not sure about the “look-and-feel” of the code: there are many more options to structure the code. The only important thing is that you somehow collect the three values needed by the `interface` function and that you know how to combine them to get the `r -> t` function in the end.

Rupert

Ok, let me summarize the three values…

Rupert writes:

Type Contents
ot → t Constructor function
(r → t) → or → ot “map” function for the operations record
r → or Implementation of the interface operations

Rupert

Done. But how could I structure the code differently?

Jeremy

I’ll show you an example soon, but let’s first look at the implementation of the `interface` function to see how these three values can be combined:

``````interface : (ot -> t) -> ((r -> t) -> or -> ot) -> (r -> or) -> (r -> t)
interface ott rtorot ror =
let
rt : r -> t
rt r =
ott (rtorot rt (ror r))
in
rt
``````

With the abstract names representing the types, it’s easy to visually type-check the code. The implementation of this new function looks very similar to the implementation of the previous `impl` function. Jeremy’s magic shines again!

Jeremy

OK, with that out of the way, here’s another possible syntax / DSL for the counter interface example:

The `Counter` and `CounterOperations` types stay the same as above.

The interface designer could supply her information in this way:

``````counterOperations : IF2.Operations r Counter (CounterOperations r) (CounterOperations Counter)
counterOperations =
IF2.defineOperations <|
\raise ops ->
{ up = ops.up >> raise
, down = ops.down >> raise
, value = ops.value
}
``````

Jeremy

I’m using an opaque type `Operations` here which could be defined in an `Interface2` module and a function `defineOperations` to create such a value. I can show you their implementation later.

I’m sure you see what kind of value this type holds?

Rupert

Of course. It’s the “map” function for the `CounterOperations` record used to “raise” the parameterized record from the internal representation type to the `Counter` type.

Jeremy

Right. The interface implementers could use this to add their knowledge, too:

``````counterImplInt : IF2.Implementation Int Counter (CounterOperations Int) (CounterOperations Counter)
counterImplInt =
IF2.implement counterOperations <|
\rep ->
{ up = \n -> rep + n
, down = \n -> rep - n
, value = rep
}

counterImplList : IF2.Implementation (List ()) Counter (CounterOperations (List ())) (CounterOperations Counter)
counterImplList =
IF2.implement counterOperations <|
\rep ->
{ up = \n -> List.repeat n () ++ rep
, down = \n -> List.drop n rep
, value = List.length rep
}
``````

Jeremy

Again, I’m using an opaque type `Implementation` and a function `implement` from a possible `Interface2` module.

Rupert

Nice. Here you add the type-specific implementations of the interface operations.

But didn’t we miss the first value that is needed, the type constructor function?

Jeremy

I left it for the last part where we put all three things together. If the constructor function is named like the interface type, in your case `Counter` for both the type and the constructor, then the following code reads nicely:

``````intCounter : Int -> Counter
intCounter start =
IF2.createInstanceOf Counter <|
IF2.implementedBy counterImplInt <|
IF2.fromValue <|
start

listCounter : Int -> Counter
listCounter start =
IF2.createInstanceOf Counter <|
IF2.implementedBy counterImplList <|
IF2.fromValue <|
List.repeat start ()
``````

Jeremy

I know that this is a very contrived example, but I wanted to show you what is possible.

Rupert

Ok, ok. This reads very nicely, indeed. I’m not sure I like the syntax with the many backward pipe operators, but I understand what you wanted to teach me: that we have many ways to design the interface API.

Can you show me the implementation of those types and functions?

Jeremy

Sure. Here they are:

``````type Operations r t or ot
= Operations ((r -> t) -> or -> ot)

type Implementation r t or ot
= Implementation
{ rtorot : (r -> t) -> or -> ot
, ror : r -> or
}

type Instance r t or ot
= Instance
{ rtorot : (r -> t) -> or -> ot
, ror : r -> or
, r : r
}

type Value r
= Value r

defineOperations :
((r -> t) -> or -> ot)
-> Operations r t or ot
defineOperations =
Operations

implement :
Operations r t or ot
-> (r -> or)
-> Implementation r t or ot
implement (Operations rtorot) ror =
Implementation
{ rtorot = rtorot
, ror = ror
}

createInstanceOf :
(ot -> t)
-> Instance r t or ot
-> t
createInstanceOf ott (Instance inst) =
let
rt : r -> t
rt r =
ott (inst.rtorot rt (inst.ror r))
in
rt inst.r

implementedBy :
Implementation r t or ot
-> Value r
-> Instance r t or ot
implementedBy (Implementation impl) (Value r) =
Instance
{ rtorot = impl.rtorot
, ror = impl.ror
, r = r
}

fromValue :
r
-> Value r
fromValue =
Value
``````

Rupert

Oh, wow. I have to look at this in more detail, but I think I see how you use the types to gradually collect the needed values and finally put them all together in the… wait… the `createInstanceOf` function.

I’m sure I could change the DSL syntax, for example, to use forward pipe operators now.

Once again: thank you, Jeremy. You gave me a lot to play with.

Jeremy

Once again: I’m glad I could help. Feel free to ask me if you get stuck.

Is this the end of the story? A happy end?

Maybe… maybe not…

Here’s an Ellie with the new code and another one with the DSL like syntax.

3 Likes

What are the practical benefits of doing it this way versus Jeremeny’s original code? Is it simply so that implementors of interfaces do not have to use `raise`?

My other comment is that I find this highly unreadable. Its all the `rt rtor or r t oror` stuff, both in type var name and fields. I know the exact choice of names for these things is hard to come up with, and that you chose these names to represent how the parts compose together, but I think the original set of names was also good. I mean `rep` is the representation of the data structure, `raise` raises the representation into its higher level hidden form, and so on. I can speak these names and attach meaning to them, which helps me a lot when building up a mental picture of what is going on. I might have a go at doing some renaming on it and see if I can get it back to something a bit more sane…

``````-- JEREMY'S MAGIC INTERFACE MODULE

type Operations r t or ot
= Operations ((r -> t) -> or -> ot)

type Implementation r t or ot
= Implementation
{ rtorot : (r -> t) -> or -> ot
, ror : r -> or
}

type Instance r t or ot
= Instance
{ rtorot : (r -> t) -> or -> ot
, ror : r -> or
, r : r
}

type Value r
= Value r

defineOperations :
((r -> t) -> or -> ot)
-> Operations r t or ot
defineOperations =
Operations

implement :
Operations r t or ot
-> (r -> or)
-> Implementation r t or ot
implement (Operations rtorot) ror =
Implementation
{ rtorot = rtorot
, ror = ror
}

createInstanceOf :
(ot -> t)
-> Instance r t or ot
-> t
createInstanceOf ott (Instance inst) =
let
rt : r -> t
rt r =
ott (inst.rtorot rt (inst.ror r))
in
rt inst.r

implementedBy :
Implementation r t or ot
-> Value r
-> Instance r t or ot
implementedBy (Implementation impl) (Value r) =
Instance
{ rtorot = impl.rtorot
, ror = impl.ror
, r = r
}

fromValue :
r
-> Value r
fromValue =
Value
``````
1 Like

Hi Rupert, thank you once more for your valuable feedback

My other comment is that I find this highly unreadable. Its all the `rt rtor or r t oror` stuff, both in type var name and fields.

I’m sorry if it gave the impression that I would recommend the current state of the implementation. You’re absolutely right that Jeremy’s names are a much better fit. In fact, here’s the version I used in my experiments:

``````interface : (opsTyp -> typ) -> ((rep -> typ) -> opsRep -> opsTyp) -> (rep -> opsRep) -> (rep -> typ)
interface constructor mapOps impl =
let
repTyp : rep -> typ
repTyp rep =
constructor (mapOps repTyp (impl rep))
in
repTyp
``````

(You could rename `repTyp` to `raise` to remove the last “abstract” name.)

But up to this point, I used the abstract names only. They helped me personally to come up with other possibilities to structure the code. Only when I felt I had reached the end of my experiments, I thought about better names.

But in this series of posts, I’m not finished yet, so I’m still using the abstract names.

What are the practical benefits of doing it this way versus Jeremeny’s original code? Is it simply so that implementors of interfaces do not have to use `raise`?

Yeah, until now, this is the only reason. But remember, I’m not finished yet…

I see, you are using these strange names as mathematical tools to explore the possibilities, very clever. I will hold off doing any renaming then, until you have finished.

It seems that I’m not able to change the table of contents in the first post anymore. If I find no other way, I’ll post the final table of contents after the last part of this series.

Intermezzo #1: Back to the Real World

I love happy ends, so it’s a perfect time to end the story of Jeremy, Rupert, and Pit.

The last thing for me to do is to apply the new interface technique to my introductory example: a list of different things. If you remember, I wanted to create a list with `String`s, `Int`s, and `Bool`s:

``````myListOfThings : List Thing
myListOfThings =
[ AString "one", AnInt 1, AnInt 12, ABool True ]
``````

and then be able to apply functions like

``````thingSize : Thing -> Int

thingDouble : Thing -> Thing
``````

to the list elements. The problem with the original implementation was that, in order to add yet another wrapped type, for example `Char`, we needed to

1. Implement the type-specific functions (charSize, charDouble)
2. Add the new subtype to the wrapper type
3. Add a new case branch to the wrapper functions (thingSize, thingDouble)

I promised that it’s possible to omit tasks #2 and #3, and this is exactly what Jeremy’s magic interface technique enables us to achieve.

First, we have to define the interface type and the operations:

``````type Thing
= Thing (ThingOperations Thing)

type alias ThingOperations t =
{ size : Int
, double : t
}
``````

I add some convenience functions for the users of the `Thing` type:

``````thingSize : Thing -> Int
thingSize (Thing ops) =
ops.size

thingDouble : Thing -> Thing
thingDouble (Thing ops) =
ops.double
``````

As the designer of the interface, I have to provide:

• the constructor function: `Thing`
• a `map` function for the operations record:
``````thingOps : (r -> t) -> ThingOperations r -> ThingOperations t
thingOps raise ops =
{ size = ops.size
, double = raise ops.double
}
``````

As an implementer of the interface, I have to implement the operations for my internal representation type:

``````thingImplString rep =
{ size = stringSize rep
, double = stringDouble rep
}

thingImplInt rep =
{ size = intSize rep
, double = intDouble rep
}

thingImplBool rep =
{ size = boolSize rep
, double = boolDouble rep
}
``````

Using the three parts (constructor, map for operations record, implementation of operations), I can provide functions to create the various kinds of things:

``````aString : String -> Thing
aString =
IF.createInstanceOf Thing thingOps thingImplString

anInt : Int -> Thing
anInt =
IF.createInstanceOf Thing thingOps thingImplInt

aBool : Bool -> Thing
aBool =
IF.createInstanceOf Thing thingOps thingImplBool
``````

I really like the look-and-feel of the code

(Note that I used the `interface` function from the last part of the story, but renamed it to `createInstanceOf`. I’m still experimenting with the `Interface` API…)

Now I can put on the interface user’s hat and create a list of different things:

``````myListOfThings : List Thing
myListOfThings =
[ aString "one", anInt 1, anInt 12, aBool True ]
``````

Instead of the constructor functions `AString`, `AnInt`, and `ABool` from the former wrapper type, I now use the instance creation functions `aString`, `anInt`, and `aBool`. Nice and easy!

I create a small test program (here’s an Ellie with the code):

``````main : Html msg
main =
myListOfThings
|> List.map thingSize
|> Debug.toString
|> Html.text
``````

I start `elm reactor`, navigate to my source file, and get:

Initialization Error

RangeError: Out of memory

What ??? Has all this just been a fairy tale ???

No, no, no. I need a happy end! The story has to be continued…

Did it blow the stack?

It’s an infinite recursion, and depending on how you run the code you get different errors (for example, in elm-test, I get a JavaScript error).

FYI: unfortunately, this would happen in Jeremy’s original code, too.

Stay tuned

Maybe this is known and just awaiting prose, but the sense left in the last few messages isn’t clear.

You are pre-computing the doubled object which in turn has a doubled-object to pre-compute, etc. The interface should have a function from unit to the doubled object and the top-level Thing wrapper can have a function which hides. (If Elm 0.19 had not removed lazy values, you could put a lazy object into the interface and force it in the top-level wrapper to avoid recomputes.)

Hi Mark, thank you for your remark. Nice to see your name again.

The next parts of this series are in the works, but unfortunately I have a lot of other things to do currently, so it will take me a few more days to publish the next part. Sorry about that.

Your post not only contained a hint how to deal with the last problem, but also gave me a few more days before this topic will be closed

This is one of the last parts of the story, I promise…

In the meantime, Rupert’s colleague Pit finally wants to understand the interface technology Rupert keeps talking about. As always, he starts to do so by modifying the existing code.

He adds a `reset` operation to the `CounterOperations` record, meaning to set the counter value to zero:

``````type alias CounterOperations t =
{ up : Int -> t
, down : Int -> t
, value : Int
, reset : t
}
``````

and adds a utility function for the `Counter` users:

``````reset : Counter -> Counter
reset (Counter counterOperations) =
counterOperations.reset
``````

He handles the new operation in the `map`-like `counterOps` function:

``````counterOps : (rep -> typ) -> CounterOperations rep -> CounterOperations typ
counterOps raise ops =
{ up = ops.up >> raise
, down = ops.down >> raise
, value = ops.value
, reset = ops.reset |> raise
}
``````

`ops.reset` is a value of type `rep`, so he has to pipe the value with `|>` into the `raise` function, rather than using the function composition operator `>>` as in the `up` and `down` operations.

It’s easy for him to add the new operation to both the `Int` and `List ()` counter implementations:

``````counterImplInt : Int -> CounterOperations Int
counterImplInt rep =
{ up = \n -> rep + n
, down = \n -> rep - n
, value = rep
, reset = 0
}

counterImplList : List () -> CounterOperations (List ())
counterImplList rep =
{ up = \n -> List.repeat n () ++ rep
, down = \n -> List.drop n rep
, value = List.length rep
, reset = []
}
``````

The counter value “zero” is represented by the integer value `0` or by the empty list `[]`, respectively.

Everything compiles, but trying to create a new `Counter` he immediately gets an error:

Initialization Error

RangeError: Out of memory

Since he has no idea what could be wrong, he asks Rupert for help. Rupert hasn’t seen this error in the context of interfaces before, but it looks like an endless recursion to him.

Unfortunately, Jeremy is visiting the type system demons and wizards conference and therefore isn’t available. Rupert and Pit have to help themselves.

It’s clear that the culprit must be the new `reset` operation, because without it everything works fine. Rupert and Pit try the example with both versions of Jeremy’s magic function they have seen before, but both versions exhibit the same behavior.

What happens upon creating an instance of the modified `Counter` interface?

Rupert suggests:

Let’s write down the steps of the evaluation.

Pit

I wouldn’t be able to do this, but if you can do it… May be we’ll see where the problem is. How do you want to proceed?

Rupert

Let’s remove all the code which isn’t relevant to the problem. Let’s use a `Counter` type with just a single `reset` function.

Rupert chooses the magic `impl` function from the fifth post (because the code is shorter):

``````impl : (ops -> typ) -> ((rep -> typ) -> rep -> ops) -> (rep -> typ)
impl constructor map =
let
raise : rep -> typ
raise rep =
constructor (map raise rep)
in
raise

type Counter
= Counter CounterOperations

type alias CounterOperations =
{ reset : Counter }

intCounter : Int -> Counter
intCounter start =
impl Counter (\raise rep -> { reset = raise 0 }) start

myCounter : Counter
myCounter =
intCounter 12
``````

He starts to write the following evaluation steps:

Creating the `Counter`:

1. `intCounter 12`

Using the body of `intCounter`:

1. `impl Counter (\raise rep -> { reset = raise 0 }) 12`

The body of `impl`:

1. `raise 12`

Definition of `raise`:

1. `Counter ((\raise rep -> { reset = raise 0 }) raise 12)`

Evaluation of the function call:

1. `Counter { reset = raise 0 }`

Definition of `raise`:

1. `Counter { reset = Counter ((\raise rep -> { reset = raise 0 }) raise 0) }`

Evaluation of the function call:

1. `Counter { reset = Counter { reset = raise 0 } }`

Definition of `raise`:

1. `Counter { reset = Counter { reset = Counter ((\raise rep -> { reset = raise 0 }) raise 0) } }`

Evaluation of the function call:

1. `Counter { reset = Counter { reset = Counter { reset = raise 0 } } }`

Pit

Oh, I can see the recursion! If we look at steps 5, 7, and 9:

`Counter { reset = raise 0 }`

`Counter { reset = Counter { reset = raise 0 } }`

`Counter { reset = Counter { reset = Counter { reset = raise 0 } } }`

it’s clear that this never ends. Elm tries to create an endlessly nested structure. But why didn’t it happen before?

Rupert

I think I understand now. Just as an exercise: why don’t you write down the steps for a version of `Counter` where the interface just has a single `up` operation?

Pit

Having you on my side, I can try it.

He more or less copies Rupert’s steps from aboove:

Creating the `Counter`:

1. `intCounter 12`

Using the body of `intCounter`, now with an `up` operation:

1. `impl Counter (\raise rep -> { up = \n -> raise (rep + n) }) 12`

The body of `impl`:

1. `raise 12`

Definition of `raise`:

1. `Counter ((\raise rep -> { up = \n -> raise (rep + n) }) raise 12)`

Evaluation of the function call:

1. `Counter { up = \n -> raise (12 + n) }`

Definition of `raise`:

Rupert

Stop! We don’t need more steps. Elm stops the evaluation right here. We now have a record where the `up` field is a function. This function is only evaluated when it is called.

Pit

OK, but don’t we have a function in the `reset` case, too?

1. `Counter { reset = raise 0 }`

Rupert

For `reset`, we have a function application, or a function call. For `up`, we have a function definition. A function call can be evaluated immediately, if the arguments of the function call are constant, as they are in the `reset` case.

Pit

I see. So this is the difference between the `up` and the `reset` operations?

Rupert

Yes, I think this is it. Do you have an idea now what we could try to make the `reset` case work?

Pit

Hmm. From what you said, we should try to use a function definition for `reset`, too. But how do we do this?

Rupert

Do you know the `lazy` functions in the `Json.Decode` and `Parser` modules? They use a function of type `() -> ...`, sometimes called a “thunk”, to prevent endless recursions. You could do the same for the `reset` operation.

Pit changes his code accordingly…

He modifies the `reset` operation in the `CounterOperations` record to be a function:

``````type alias CounterOperations t =
{ up : Int -> t
, down : Int -> t
, value : Int
, reset : () -> t
}
``````

The utility function for the `Counter` users has to be changed, too:

``````reset : Counter -> Counter
reset (Counter counterOperations) =
counterOperations.reset ()
``````

In the `map`-like `counterOps` function, he uses the function composition operator in the `reset` field, too, because now `ops.reset` is a function:

``````counterOps : (rep -> typ) -> CounterOperations rep -> CounterOperations typ
counterOps raise ops =
{ up = ops.up >> raise
, down = ops.down >> raise
, value = ops.value
, reset = ops.reset >> raise
}
``````

Finally the `Int` and `List ()` counter implementations:

``````counterImplInt : Int -> CounterOperations Int
counterImplInt rep =
{ up = \n -> rep + n
, down = \n -> rep - n
, value = rep
, reset = \() -> 0
}

counterImplList : List () -> CounterOperations (List ())
counterImplList rep =
{ up = \n -> List.repeat n () ++ rep
, down = \n -> List.drop n rep
, value = List.length rep
, reset = \() -> []
}
``````

Fingers crossed, they try the new code, and… it works! Success!

A few days later, Jeremy returns from the demons and wizards conference. Rupert tells him about Pit’s problem and shows him the new code.

Jeremy

Yeah, I’ve already heard of your experiment. Congratulations that you were able to fix the problem on your own!

I have been thinking about it. Are you satisfied with the solution?

Rupert

Hmm, I thought that I am, but if you ask me like that… Can the code be improved, again?

Jeremy

Do you remember when we separated the interface definition from the implementation in order to hide the `raise` function from the interface implementations?

Rupert

Of course I do. Do you think that we can hide the laziness from the implementations, too?

Jeremy

Not only that, we can hide it even from the `counterOps` function.

But we have to change other things, above all the function in the `Interface` module. Have you decided which name the function should have, finally?

Rupert

You mean your “magic” function? Currently I tend to name it `make` or `create` because I like `create Counter ...`

Jeremy

OK. We’ll name it `create`.

In order to hide the laziness, we have to add the additional `()` parameter to the definition of the `raise` function. This makes the `create` function look like this:

``````create : (opsTyp -> typ) -> ((rep -> () -> typ) -> opsRep -> opsTyp) -> (rep -> opsRep) -> (rep -> typ)
create constructor mapOps impl rep =
let
raise : rep -> () -> typ
raise rep_ () =
constructor (mapOps raise (impl rep_))
in
raise rep ()
``````

Can you see why this works?

Rupert

Hmm. It looks a little bit different than the “normal” lazy functions. You added the `()` as the second parameter to the `raise` function, not as the first.

Jeremy

Yes. In order to have the desired effect, in the end it is necessary to have a partially applied function call where only the `()` argument is missing.

We call `raise` in the `counterOps` function and pass the current internal state called “rep” into it. Thus, the missing `()` argument has to come after the “rep” parameter.

With this change, each `raise` call in the `counterOps` function is missing the final `()` argument. Evaluation is guaranteed to stop there.

Rupert

I see. Just to be sure, can you show me the new `counterOps` function?

Jeremy

Of course. Here it is:

``````counterOps : (rep -> typ) -> CounterOperations rep -> CounterOperations typ
counterOps raise ops =
{ up = ops.up >> raise
, down = ops.down >> raise
, value = ops.value
, reset = ops.reset |> raise
}
``````

Rupert

Wait - this looks exactly like Pit’s original code, including the forward pipe operator and even the function signature! How can this work? Don’t we have to change the function signature at least, because we changed the type of the `raise` function?

Jeremy

You are right. I cheated a little bit. To be precise, the function signature should be

``````counterOps : (rep -> () -> typ) -> CounterOperations rep -> CounterOperations (() -> typ)
``````

As you can see from this version of the function signature, we also have to change the main interface type to

``````type Counter
= Counter (CounterOperations (() -> Counter))
``````

But if we want to, we can totally hide this from the `counterOps` function by generalizing the type `() -> typ` to simply `typ`. It still compiles, because it’s still just a `map` function from `CounterOperations a` to `CounterOperations b`.

Rupert

Magic

Jeremy

Oh, it’s more a little bit of cheating than magic here.

Because of the change in `Counter`, we have to change the utility functions, too, those which return a new `Counter`:

``````up : Int -> Counter -> Counter
up n (Counter counterOperations) =
counterOperations.up n ()

down : Int -> Counter -> Counter
down n (Counter counterOperations) =
counterOperations.down n ()

reset : Counter -> Counter
reset (Counter counterOperations) =
counterOperations.reset ()
``````

But besides that, the implicit laziness is completely hidden from the remaining code.

Rupert

I stand by my opinion: it’s magic

If you want to try the magic for yourself, here’s an Ellie with implicit laziness.

3 Likes