Difference in Elm between type and type alias?

In Elm, I can't figure out when type is the appropriate keyword vs. type alias. The documentation doesn't seem to have an explanation of this, nor can I find one in the release notes. Is this documented somewhere?


How I think of it:

type is used for defining new union types:

type Thing = Something | SomethingElse

Before this definition Something and SomethingElse didn't mean anything. Now they are both of type Thing, which we just defined.

type alias is used for giving a name to some other type that already exists:

type alias Location = { lat:Int, long:Int }

{ lat = 5, long = 10 } has type { lat:Int, long:Int }, which was already a valid type. But now we can also say it has type Location because that is an alias for the same type.

It is worth noting that the following will compile just fine and display "thing". Even though we specify thing is a String and aliasedStringIdentity takes an AliasedString, we won't get an error that there is a type mismatch between String/AliasedString:

import Graphics.Element exposing (show)

type alias AliasedString = String

aliasedStringIdentity: AliasedString -> AliasedString
aliasedStringIdentity s = s

thing : String
thing = "thing"

main =
  show <| aliasedStringIdentity thing

The key is the word alias. In the course of programming, when you want to group things that belong together, you put it in a record, like in the case of a point

{ x = 5, y = 4 }  

or a student record.

{ name = "Billy Bob", grade = 10, classof = 1998 }

Now, if you needed to pass these records around, you'd have to spell out the entire type, like:

add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
  { a.x + b.x, a.y + b.y }

If you could alias a point, the signature would be so much easier to write!

type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
  { a.x + b.x, a.y + b.y }

So an alias is a shorthand for something else. Here, it's a shorthand for a record type. You can think of it as giving a name to a record type you'll be using often. That's why it's called an alias--it's another name for the naked record type that's represented by { x:Int, y:Int }

Whereas type solves a different problem. If you're coming from OOP, it's the problem you solve with inheritance, operator overloading, etc.--sometimes, you want to treat the data as a generic thing, and sometimes you want to treat it like a specific thing.

A commonplace where this happens is when passing around messages--like the postal system. When you send a letter, you want the postal system to treat all messages as the same thing, so you only have to design the postal system once. And besides, the job of routing the message should be independent of the message contained within. It's only when the letter reaches its destination do you care about what the message is.

In the same way, we might define a type as a union of all the different types of messages that could happen. Say we're implementing a messaging system between college students to their parents. So there are only two messages college kids can send: 'I need beer money' and 'I need underpants'.

type MessageHome = NeedBeerMoney | NeedUnderpants

So now, when we design the routing system, the types for our functions can just pass around MessageHome, instead of worrying about all the different types of messages it could be. The routing system doesn't care. It only needs to know it's a MessageHome. It's only when the message reaches its destination, the parent's home, that you need to figure out what it is.

case message of
  NeedBeerMoney ->
    sayNo()
  NeedUnderpants ->
    sendUnderpants(3)

If you know the Elm architecture, the update function is a giant case statement, because that's the destination of where the message gets routed, and hence processed. And we use union types to have a single type to deal with when passing the message around, but then can use a case statement to tease out exactly what message it was, so we can deal with it.