Html msg and phantom types

It looks like a lot of beginners (including myself) get confused with Html msg and Html Msg. Searching for explanations brings a lot of helpful discussions and explanations [1], [2], [3], [4]. And at the end of that reading I thought I did understand the difference, but that feeling vanished when I started going through the source of elm-spa-example code base. Here are some snippets:

type Article a
    = Article Internals a
mapAuthor : (Author -> Author) -> Article a -> Article a
mapAuthor transform (Article info extras) =
    Article { info | author = transform info.author } extras
fromPreview : Body -> Article Preview -> Article Full
fromPreview newBody (Article info Preview) =
    Article info (Full newBody)

Type constructors

As I was going through the source code of different projects I got confused with many things, one of which was type constructors. If you look at one of the snippets above it has Article on both left and right side of the equation. How does it work?

In Elm it is common to see this pattern [5] [6] [7]. The Article on the left side is a type definition. We are telling Elm that we want a new type called Article. The right side creates a function called Article which essentially acts as constructor function that accepts two arguments and returns an instance of Article. I am not a Java developer but I liked an example from Mike Knepper to illustrate this in another language:

public class Article {
    public Article(Internals i, Object a) {
        // ...
    }
}

We can create a simplified code to illustrate this pattern:

> type User = User String
> User
<function> : String -> User
> myUser = User "Batman"
User "Batman" : User

Here we have a type User and a type constructor also called User that accepts a String and returns type User.

Phantom types

Now that I could understand type annotations for different functions, it was time to get back to Html msg and similar constructs. And this is where phantom types come into play.

Phantom types are usually referred to advanced Elm usage, however these are instrumental to understanding what does List a, Article a, or Html msg (which can be written as Html a too) mean and what that a is.

Elm Guide has a nice explanation of type variables. During the definition we don't care what the type of that variable is, it can be an Int, a Float, or something else. The result of the function doesn't depend on the type of variable. A List.length definition doesn't need to know if you're passing a list of characters or tuples, the result will always be an integer, i.e. length of the list. But List.reverse not only takes a generic type but also returns that:

> List.reverse
<function> : List a -> List a

The reverse can receive any type and Elm guarantees you that you will receive the exact same type back. So if we were to give it a list of strings, we will get a list of strings back too. Let's try that concept with our own custom type.

In US people measure temperature in Fahrenheit, in Europe in Celsius, and scientists use Kelvins to measure colour temperature of stars.

type Temperature
  = Temperature Int

We can now have define our temperature in the app:

> temperature = Temperature 10000
Temperature 10000 : Temperature

No matter the scale, it is still pretty high temperature. But what exactly are we dealing with? Are we looking at Earth values? Is it Europe? Or US? Or maybe our Sun? We need to define the scale:

type Fahrenheit
  = Fahrenheit Temperature
  
type Celsius
  = Celsius Temperature
  
type Kelvin
  = Kelvin Temperature

And can now work with different temperatures:

newYorkTemperature = Temperature 40
newYorkTemperatureInFahrenheit = Fahrenheit newYorkTemperature

-- Or we can make it all in one line

temperatureInNewYork = Fahrenheit (Temperature 40)

Now that we have different scales we can create functions to add, subtract, and convert between each:

diff : Fahrenheit -> Fahrenheit -> Fahrenheit
diff (Fahrenheit (Temperature a)) (Fahrenheit (Temperature b)) =
  Fahrenheit (Temperature (a - b))
  
f1 = Fahrenheit (Temperature 40)
f2 = Fahrenheit (Temperature 30)

diff f1 f2
-- Fahrenheit (Temperature 10) : Fahrenheit

For each scale we would need to create an almost exact same function. The only thing that would change is the scale type. This can be simplified using phantom types. First we need to re-define our types to use type variable:

type Temperature a
  = Temperature Int
  
type Fahrenheit
  = Fahrenheit
  
type Celsius
  = Celsius
  
type Kelvin
  = Kelvin

With these types it is now possible to specify temperature in major cities and even outside the Earth:

newYorkTemperature : Temperature Fahrenheit
newYorkTemperature
  = Temperature 31
-- Temperature 31 : Temperature Fahrenheit

parisTemperature : Temperature Celsius
parisTemperature
  = Temperature 8
-- Temperature 8 : Temperature Celsius

tempOfTheSum : Temperature Kelvin
tempOfTheSum
  = Temperature 5778
-- Temperature 5778 : Temperature Kelvin

Finally, we need to create just a single diff function that could work with Fahrenheit, Celsius, and Kelvin scales:

diff : Temperature a -> Temperature a -> Temperature a
diff (Temperature a) (Temperature b) =
  Temperature (a - b)
  
-- We're passing two temperatures in Fahrenheit
diff chicagoTemperature newYorkTemperature

-- Now we're passing two temperatures in Celsius to the same function
diff londonTemperature parisTemperature

-- Finally we can compare temperature on the Sun and Proxima Centauri
diff theSun proximaCentauri

Difference between msg and Msg

Now that it is hopefully clear what Html a is (which is the same as Html msg), the difference between Html Msg and Html msg will be easy to explain.

Our Elm program produces HTML which gets rendered on the screen, and then the computer sends back messages like "button clicked" or "form submitted". Since it is a convention to call the type of message simply Msg (and not Message or Event), beginner developers get confused. This is especially true with simple render functions that do not produce any messages:

renderUser : User -> Html Msg
renderUser user =
  text user.fullName
  
-- since the above function doesn't produce any messages of type `Msg`
-- we can swap Msg with msg

renderUser : User -> Html msg
renderUser user =
  text user.fullName

If your view produces events of type Msg (or some other type), then use Html Msg, if like in the example above it send any messages, then using Html a might be better and less confusing.

Show Comments