What’s in a type (alias)?…
A quick quack on type aliases in Elm.
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 =
Stringtype 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 =
Stringtype alias Label =
Stringtype alias Name =
Stringtype alias Age =
Inttype 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> 🦆 ➡️