*I’ve been working with Elm for many years now, but only recently have I begun to learn Haskell for work. I often found myself confused by the explanations for even the most fundamental concepts, despite what I’m sure were well-meaning intentions. This was especially true when the explanations were termed in what to me was estranging academic vocabulary. Here’s my attempt at a blog post which I wish could have been referred to.*

# The Grand Divide

On the borders between the Elm and Haskell community, the value of type classes is often discussed (Probably too often!). From the conversations, it sounded to me like Haskell had type classes and Elm didn’t, so it came as a surprise to me when I found that Elm in fact does have type classes, and I had used them all along.

## Typeclasses in Elm

There are several type classes in Elm. One of them is `number`

, used all around the core modules. In the `Basics.elm`

module:

`(+) : number -> number -> number`

(-) : number -> number -> number

(*) : number -> number -> number

(/) : number -> number -> number

abs : number -> number

negate : number -> number

These annotations describe how the math functions cannot take arguments of any type, but only a select set. Intuitively, this makes sense: It’s obvious that `Int`

’s and `Float`

’s can be multiplied, but how would the compiler know how to add together two records which I defined? It can’t know. Thus `Int`

’s and `Float`

’s are categorized under the name `number`

because they share the ability to be manipulated by arithmetic operators. They are a *class of types* which share a set of functions, hence the name “type classes”. Members of a type class are called “instances” of that type class, thus `Int`

and `Float`

can be called instances of the `number`

type class.

## Typeclasses in Haskell

But if type classes exist in Elm, what more did those Haskell developers want? The big difference is that in Haskell you can define type classes and their instances yourself. If the `number`

type class from Elm was defined in Haskell, it could look something like this:

`class Number a where`

(+) :: a -> a -> a

(-) :: a -> a -> a

(*) :: a -> a -> a

(/) :: a -> a -> a

abs :: a -> a

negate :: a -> a

It tells the compiler that any instance of this type class must have these functions. Once you have defined your type class, you can add any type to this class, as long as you define the required functions, too.

type Person =

{ name :: List Char

, age :: Int

}instance Number Person where

(+) :: Person -> Person -> Person

(+) a b =

Person (take 4 a.name ++ drop 1 b.name) (a.age + b.age) (-) :: Person -> Person -> Person

(-) a b =

-- ... more silly implementations

With `Person`

defined as an instance of `Number`

we can now use the arithmetic operators on people.

`example :: Person`

example =

Person "Tereza" 23 + Person "Evan" 29 -- Person "Terevan" 52

This is of course a silly implementation, because treating people like numbers doesn’t make any sense, but anything is possible in Haskell! Depending on who you ask, that is a blessing or a curse.

## An aside on type constraints

Once you have defined a type class in Haskell, you (and anyone else) can create additional functions only accessible by types which are instances of your type class. This feature is called “type constraints”. Given our `Number`

type class in Haskell, one could add a `sum`

function like so:

`sum :: (Number a) => List a -> a`

sum as =

foldl (+) 0 as

A `sum`

function does not make sense unless its arguments can be treated like numbers. Thus, by adding `(Number a) =>`

in the beginning of the signature, we *constrain* all mentions of `a`

in the rest of the signature to be instances of the `Number`

type class. This is important because we need to use the `(+)`

operator in the definition, which is only accessible to instances of the `Number`

type class!

# The point of type classes

The number type class was actually the very first type class to be introduced. It’s a neat party trick because we often have `Float`

and `Int`

within the same scope and without type classes, we’d be required to have separate modules for `Float`

and `Int`

, each containing all of the arithmetic operators, and then qualifying those arithmetic operators upon usage:

-- Without type classesexample :: Int

example =

3 Int.(+) 5

-- With type classesexample :: Int

example =

3 + 5

This illustrates the essence of the feature: Type classes remove the need for qualifying functions. So if you don’t like qualifying your functions, having many type classes is your blessing.

# Common type classes

As it turns out, Haskell does not like qualifying functions, hence it has a lot of type classes. In fact, if we try to find all the functions which share a name across modules in Elm, they will likely be type classes in Haskell.

## Map

One of the first functions to come to mind may be the commonly occurring `map`

, as seen in many of the core modules, e.g. the `Maybe`

and `List`

module:

-- Maybe.elm

map : (a -> b) -> Maybe a -> Maybe b-- List.elm

map : (a -> b) -> List a -> List b

and indeed, there is a type class for it in Haskell.

`class Functor f where`

fmap :: (a -> b) -> f a -> f b

Consider the type signature of `fmap`

: If you replace the generalized `f`

type variable with `Maybe`

or `List`

, you’ll see that it’s the exact same signature as that of the `map`

functions in Elm! This means that in Haskell, you don’t have to qualify your maps, you use `fmap`

, as shown in the following snippet.

-- Elmexample : Maybe Person

example =

Maybe.map (Person "Tereza") (Just 23)

-- Haskellexample :: Maybe Person

example =

fmap (Person "Tereza") (Just 23)

This type class is called the “Functor” type class, so when people say a type is a “functor”, it just means it’s mappable.

## Map2

Another example may be that of `map2`

as seen in the `Maybe`

and `Result`

core modules:

-- Maybe.elm

map2 : (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c-- Result.elm

map2 : (a -> b -> c) -> Result x a -> Result x b -> Result x c

These functions do not translate as literally into Haskell as `map`

did, but it does have an equivalent, merely a little manipulated. Take a look at the type class below:

`class Applicative f where`

pure :: a -> f a

(<*>) :: f (a -> b) -> f a -> f b

For the sake of this explanation, we will will look at the implementation of this type class in the case of `Maybe`

, resulting in the following functions:

pure :: a -> Maybe a

pure a =

Just a(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b

(<*>) maybeFunc maybeA =

case ( maybeFunc, maybeA ) of

( Just func, Just a) ->

Just (func a) _ ->

Nothing

The exciting part here is that from these two functions combined, you can make any `map`

you like:

`map2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c`

map2 func maybeA maybeB =

pure func <*> maybeA <*> maybeB

If you’re anything like me, this might be tying a knot in your brain, so here are the types in progressing order, but don’t worry if you have to play if over a few times.

pure -- a -> Maybe a

(<*>) -- Maybe (a -> b) -> Maybe a -> Maybe b

func -- a -> b -> cpure func -- Maybe (a -> b -> c)

pure func <*> maybeA -- Maybe (b -> c)

pure func <*> maybeA <*> maybeB -- Maybe c

Likewise, you can make `map3`

by tagging another `<*>`

on to the end:

`map3 :: (a -> b -> c -> d) -> Maybe a -> Maybe b -> Maybe c -> Maybe`

map3 func maybeA maybeB maybeC =

pure func <*> maybeA <*> maybeB <*> maybeC

And so on. This type class is in Haskell called the “Applicative” type class, meaning that when Haskell people say that a type is an “applicator”, they mean that it can be chained together using the `<*>`

operator.

If you’re curious about the application of this technique in Elm, it is used in `elm-json-decode-pipeline`

, even if a little stealthily. That’s why it does not have the `map`

, `map2`

, `map3`

… pattern.

## andThen

Another example of functions that often occur in Elm API’s, may be that of `andThen`

as seen in the `Maybe`

and `Result`

core modules:

-- Maybe.elm

andThen : (a -> Maybe b) -> Maybe a -> Maybe b-- Result.elm

andThen : (a -> Result x b) -> Result x a -> Result x b

Once again, this is also a type class in Haskell!

`class Monad m where`

(>>=) :: m a -> (a -> m b) -> m b

And again, if you exchange `m`

for `Maybe`

or `Result`

, you’ll see that the signature is the same as that of `andThen`

, except the arguments are flipped. They are flipped in Haskell because their version is an infix operator.

-- Elmexample : Maybe Int

example =

Just "23" |> Maybe.andThen String.toInt-- Haskellexample :: Maybe Int

example =

Just "23" >>= readMaybe

And that’s the infamous monad! Meaning that when Haskell people say a type is a “monad”, it just means `andThen`

can be applied to it.

# Conclusion

There isn’t actually anything you can do with type classes which you can’t do without them- it’s just a matter of how you prefer to use and talk about types and what abilities they have. Elm chose not to have custom type classes, because qualifying functions builds in hints in your code, helping you to keep track of what types you’re dealing with, and because the language is meant to be easily approachable to people who are not already familiar with functional languages. Haskell, as a research language, does not explicitly aim to be easily approachable, rather it is meant to be a playground for new and interesting features. Growing more familiar with Haskell, I think it’s wonderful to have both these worlds available.