---
title: 'Lessons Learned: Avoiding Primitives in Elm'
teaser: 'Switching from primitive types to product types can improve confidence in
  correctness and compiler errors, but not without a cost.

  '
tags: web,elm,haskell
author: Josh Clayton
published_on: 2017-01-10
---

[Ben], [Joël] and I recently built an Elm application over two investment days,
with a focus on trying out a new (to us!) paradigm in Elm. In this application,
we avoided type aliases and primitive types (`String`, `Int`, etc.) in favor of
[product types].

[Ben]: https://twitter.com/r00k
[Joël]: https://twitter.com/joelquen
[product types]: http://blog.jenkster.com/2016/06/functional-mumbo-jumbo-adts.html

One of our types was for currency. Let's break down the three ways we could
declare this:

```elm
-- primitive type (Int)

type alias Player =
  { dollarsOnHand : Int
  , dollarsInBank : Int
  }

-- type alias

type alias Dollar = Int

type alias Player =
  { dollarsOnHand : Dollar
  , dollarsInBank : Dollar
  }

-- product type

type Dollar = Dollar Int

type alias Player =
  { dollarsOnHand : Dollar
  , dollarsInBank : Dollar
  }
```

## Avoid Primitive Obsession

[Primitive obsession] outlines the use of primitive types (often `String`,
`Int`, etc.) instead of defining domain-specific types. In our case, the first
`Player` uses the type `Int` to describe currency. In Elm, however, this is
almost counter-intuitive, and relies on function names alone to convey intent.

Imagine the type signature `Int -> Int -> Int`; what do those `Int`s represent?
What operations could apply that take two `Int`s and return an `Int`? The
possibilities are endless, and within an application, can be quite confusing
without looking at the function name.

This both reduces readability and increases the opportunity for bugs to sneak
in.

Next, imagine `Dollar -> Dollar -> Dollar` or even `Dollar -> Int -> Dollar`.
These tell a more compelling story. Even without the function name, my mind
immediately fills in the first as likely describing addition or subtraction,
and the second multiplication or division.

Finally, imagine `Dollar -> Quantity -> Dollar`. This might be a function to
calculate the total cost given a specific number are purchased.

[Primitive obsession]: http://wiki.c2.com/?PrimitiveObsession

## Type Aliases Improve Readability, but Still No Help from the Compiler

With what seems like a clear win, let's move on to the next option: type
aliases.

Type aliases allow developers to declare functions with more specific types as
we did in the examples above; however, the compiler will still allow any
appropriate type in! This means we have an improvement on the readability front
(since the type signature can more accurately convey what's happening), but the
compiler still can't protect us from applying values in the wrong order.

Let's loop back to the `Dollar -> Quantity -> Dollar` example; with a type
alias, any appropriately-typed value (an `Int`) would be allowed, meaning the
compiler reduces the signature to `Int -> Int -> Int` still.

I've been bitten by order-of-operations bugs that can be hidden because of
mis-ordered arguments.

Imagine this buggy code (`fee` and `quantity` are flipped in the list of
arguments in `calculateTotalPrice`):

```elm
type alias Dollar = Int
type alias Quantity = Int
type alias AdditionalFee = Dollar

calculateTotalPrice : Dollar -> Quantity -> AdditionalFee -> Dollar
calculateTotalPrice dollar fee quantity = -- quantity and fee are flipped!
    dollar * quantity + fee

calculateTotalPrice 10 10 5
-- the code compiles, but the type signature doesn't help ensure correctness
```

This returns `60 : Int` (10 * 5 + 10) instead of `105 : Int` (10 * 10 + 5).

The order of arguments being flipped (`fee` and `quantity`) isn't obvious to
spot, but the compiler can help us here.

## Product Types

Let's add product types for `Dollar` and `Quantity`, as well as the
mathematical operations we'll be using:

```elm
type Dollar = Dollar Int
type Quantity = Quantity Int
type alias AdditionalFee = Dollar

multiply : Dollar -> Int -> Dollar
multiply (Dollar value) quantity =
    Dollar <| value * quantity

plus : Dollar -> Dollar -> Dollar
plus (Dollar a) (Dollar b) =
    Dollar <| a + b

calculateTotalPrice : Dollar -> Quantity -> AdditionalFee -> Dollar
calculateTotalPrice dollar fee quantity =
    (multiply dollar quantity)
    |> plus fee

calculateTotalPrice (Dollar 10) (Quantity 10) (Dollar 5) -- compilation error!
```

This results in a compilation error:

    Detected errors in 1 module.


    -- TYPE MISMATCH ---------------------------------------------------------------

    The argument to function `plus` is causing a mismatch.

    19|        plus fee
                    ^^^
    Function `plus` is expecting the argument to be:

        Dollar

    But it is:

        Quantity

    ... truncated

Excellent! The compiler is telling us we can't add a `Dollar` and a `Quantity`
together, pointing out our error. Let's make the fix:

```elm
calculateTotalPrice : Dollar -> Quantity -> AdditionalFee -> Dollar
calculateTotalPrice dollar (Quantity quantity) fee =
    (multiply dollar quantity)
    |> plus fee

calculateTotalPrice (Dollar 10) (Quantity 10) (Dollar 5)
-- Dollar 105 : Dollar
```

This compiles successfully, returns the correct value, and is arguably easier
to understand:

```elm
-- what do these numbers mean!?
calculateTotalPrice 10 10 5

-- more obvious what the numbers mean
calculateTotalPrice (Dollar 10) (Quantity 10) (Dollar 5)
```

Product types allow for expressive type signatures that correctly convey
intent, and the compiler protects us from doing silly things. All is right in
the world... or is it?

## Costs to Product Types

Introducing product types isn't without a cost, however. When we started down
the path of using product types in our application, we were unwrapping the
value, applying the value to a function, and re-wrapping the result. This
became incredibly tedious. After thinking about the problem, though, we
recognized that we were basically describing Elm's version of `map` (check out
[`List.map`] and [`Maybe.map`] for polymorphic versions).

[`List.map`]: http://package.elm-lang.org/packages/elm-lang/core/latest/List#map
[`Maybe.map`]: http://package.elm-lang.org/packages/elm-lang/core/latest/Maybe#map

```elm
map : (Int -> Int) -> Dollar -> Dollar
map f (Dollar i) =
    Dollar <| f i

map (* 5) (Dollar 5) -- Dollar 25 : Dollar
```

With `map` identified, we noticed another pattern: adding and subtracting
`Dollar` values. `(+)` and `(-)` both have the signature `number -> number -> number`,
so `map2` feels very similar:

```elm
map2 : (Int -> Int -> Int) -> Dollar -> Dollar -> Dollar
map2 f (Dollar a) (Dollar b) =
    Dollar <| f a b

map2 (+) (Dollar 17) (Dollar 8) -- Dollar 25 : Dollar
```

This allowed for further sugar by defining `add` and `subtract`, leveraging `map2`.

Within the investment time project, here's what we ended up with:

```elm
module Currency exposing (..)


type Currency
    = Currency Int


zero : Currency
zero =
    Currency 0


add : Currency -> Currency -> Currency
add =
    map2 (+)


subtract : Currency -> Currency -> Currency
subtract =
    map2 (-)


divideBy : Int -> Currency -> Currency
divideBy divisor =
    map (flip (//) divisor)


fromInt : Int -> Currency
fromInt =
    Currency


toInt : Currency -> Int
toInt (Currency int) =
    int


greaterThan : Currency -> Currency -> Bool
greaterThan (Currency a) (Currency b) =
    a > b


map : (Int -> Int) -> Currency -> Currency
map f (Currency a) =
    Currency <| f a


map2 : (Int -> Int -> Int) -> Currency -> Currency -> Currency
map2 f (Currency a) (Currency b) =
    Currency <| f a b
```

As you can see, `Currency.map` and `Currency.map2` ended up being cornerstones
to a number of the other functions, and that makes a lot of sense! Because of
the underlying `Int` that we're wrapping, the functions passed are always
either `Int -> Int` or `Int -> Int -> Int`.

## Ties to Haskell

This path came from my liberal use of `newtype` in Haskell; I enjoy the safety
`newtype` brings and wanted to see how well it translated to Elm.

If you're familiar with Haskell, you might notice similarities between the
signatures of `Currency.map`/`Currency.map2` and [`mono-traversable`]'s
[`omap`] and [`ozipWith`].

[`mono-traversable`]: https://hackage.haskell.org/package/mono-traversable-1.0.1
[`omap`]: https://hackage.haskell.org/package/mono-traversable-1.0.1/docs/Data-MonoTraversable.html#v:omap
[`ozipWith`]: https://hackage.haskell.org/package/mono-traversable-1.0.1/docs/Data-Containers.html#v:ozipWith

The Haskell equivalent with `mono-traversable` could be written as:

```haskell
{-# LANGUAGE TypeFamilies #-}

module Currency where

import Data.Containers (MonoZip, ozipWith)
import Data.MonoTraversable (MonoFunctor, Element, omap)

newtype Currency = Currency Int deriving (Eq, Ord, Show)
type instance Element Currency = Int

instance MonoFunctor Currency where
    omap f (Currency i) = Currency $ f i

instance MonoZip Currency where
    ozipWith f (Currency i) (Currency i') = Currency $ f i i'

add :: Currency -> Currency -> Currency
add = ozipWith (+)

subtract :: Currency -> Currency -> Currency
subtract = ozipWith (-)

divideBy :: Int -> Currency -> Currency
divideBy divisor = omap (`quotInt` divisor)
  where
    quotInt i i' = fromIntegral i `quot` fromIntegral i'

zero :: Currency
zero = Currency 0

fromInt :: Int -> Currency
fromInt = Currency

toInt :: Currency -> Int
toInt (Currency i) = i
```

Note that `greaterThan :: Currency -> Currency -> Bool` isn't necessary because
`Currency` derives the `Ord` type class, allowing us to use the `>` operator
directly.

One could *also* derive `Num` (with `GeneralizedNewtypeDeriving`) to leverage
`(+)`, `(-)`, `(*)`, and the like:

```haskell
2 * Currency 5 -- Currency 10
Currency 2 - Currency 1 -- Currency 1
```

However, it allows for odd interactions:

```haskell
Currency 2 * Currency 2 -- Currency 4
Currency 5 - 2 -- Currency 3
```

This feels sloppy, and as such, I favor the explicit functions for the
operations I expect to perform on `Currency`.

## Final Thoughts

Compilers are wonderful things, and type safety (especially with concepts as
important as currency) is an asset I've found invaluable in these cases. Even
though there's a perception of verbosity and extra work mirroring a handful of
functions that are available to `Int`, it feels like a worthwhile trade-off.
