A.k.a Ducky.

What’s in a type (alias)?…

A quick quack on type aliases in Elm.

Michel Belleville
Published in
4 min readDec 10, 2018

--

me> Wat.

wat> What?

me> Yes, you. 🙂

wat> Ah. Need some ducky help I guess. What’s the rub? 😉

me> I want to figure out this type thing.

wat> What type thing?

me> In Elm. Like, what’s the difference between type and type alias, why there are types like String and others like List Int or Parser a b

wat> Sure, let’s dive in 😜 why do you think we have types in Elm in the first place?

me> Because… we need to know what is what? Like, if I want a function to cut a String in smaller bits, and I give it an Int, the compiler will complain that it’s inappropriate?

wat> Exactly. We have types because it allows us to know when we’re doing something wrong, and more broadly to communicate intent to the compiler, but also to ourselves. Consider this function signature:
myAwesomeStringCutter : String -> List String
It tells us that this function takes a String, not an Int, not a Float, and returns a List String, not a lone String, nor a List Int or any other type. That’s a useful thing to know.

me> Sure. Other languages have types too.

wat> That they do. Even those that don’t necessarily make a big deal of demanding them upfront, like JavaScript, Python, Ruby…

me> I hear Ruby has duck-typing. 🦆

wat> Har-har. The thing is, with that kind of weak typing, you never know what a function expects or returns, which can lead to problems if one is not careful.

me> I get it. What about type alias?

wat> Those are mostly cosmetic. They allow you to give another name to an existing type.

me> Why? Isn’t it simpler when things have only one name? 🤔

wat> Yes and no. Consider this function signature:

poorlyNamedFunction :
(String -> String)
-> { id: String, label: String, name: String, age: Int }
-> { id: String, label: String, name: String, age: Int }

wat> Can you tell me what this does at a glance?

me> Er… so, it takes a function that takes a String and returns a String, so I guess this is used to transform a String… and then… it gets complicated. Is that even allowed? 😲

wat> It is, but it’s not necessarily recommended, because it’s pretty hard to read and understand the intent.

me> What are those { foo: bar, ...} things?

wat> Records. Simple records. And they’re one of the main reasons why we use type aliases. Let me clear things up a bit by introducing a meaningful type alias:

type alias Person =
{ id: String
, label: String
, name: String
, age: Int
}
poorlyNamedFunction :
(String -> String)
-> Person
-> Person

me> Oh, I see! poorlyNamedFunction is a function that takes a Person and changes it using a function that transforms a String… but how?

wat> Looks like String is pretty ambiguous there. It could be linked to the id, label or name field… let’s use another type alias to make things clearer:

type alias Label =
String
type alias Person =
{ id: String
, label: Label
, name: String
, age: Int
}
poorlyNamedFunction :
(Label -> Label)
-> Person
-> Person

me> Ok, now it looks pretty clear we have a function that can be used to transform the label of a Person 😃 but the label is still just a String, right?

wat> Exactly. A type alias is just a fancy new name over a known type. And they are interchangeable. I could still write my poorlyNamedFunction with a String -> String transformation and it would still work the same. Also, since the compiler doesn’t know the difference between a String and a Label, it won’t help you if you pass what is intended as anId or Name instead of a Label. But it helps to communicate intent to the human reader and make more meaningful signatures already that can reduce the number of mistakes one is likely to make.

me> About communicating intent, wouldn’t it have been simpler to name your function updateLabel or something? 🤨

wat> Sure. Naming your functions well and using meaningful types are both part of communicating intent, as is giving documentation and examples when needed 🙂 we could also have given type alias for the other fields to make them more explicit. Like this:

type alias PersonId =
String
type alias Label =
String
type alias Name =
String
type alias Age =
Int
type alias Person =
{ id: PersonId
, label: Label
, name: Name
, age: Age
}
updateLabel :
(Label -> Label)
-> Person
-> Person

me> Looks a bit verbose.

wat> It is. But clarity trumps brevity most of the time. And it comes with interesting side-effects. Imagine I need to change the type of the Label field to, say, a List String ; the compiler will infer that all the functions and types working with the Label type have changed and tell me where I need to adapt my code, giving me more meaningful errors that help me fix them.

me> Yes, but more code to write 😫

wat> Yes, but less trouble figuring out what that code does three months from now 😉 also, you might want to go the extra mile sometimes and make your own types for better safety.

me> Hrumpf. Ok. Let’s put a bookmark here, I need a break.

wat> 🦆 ➡️

--

--