I think type aliases are universally not useful. Any time I see them, I believe a custom type would serve better. I like that custom types are more powerful (can be recursive, can include multiple variants), and don’t allow you to get “stringly typed” problems through the compiler.
In this case:
addNumbers : Bool -> Bool -> Int -> Int -> Int
-- with aliasing:
type alias RoundUp = Bool
type alias RoundToTens = Bool
addNumbers : RoundUp -> RoundToTens -> Int -> Int -> Int
Is much better solved like so:
type RoundingDirection = RoundUp | RoundDown
type RoundTo = RoundOnes | RoundTens
addNumbers : RoundingDirection -> RoundTo -> Int -> Int -> Int
And this case is better solved with a new type that can’t accidentally be used in the wrong place, or perhaps a record:
type alias Name = String
type Person = Person Name
-- this is more safe:
type Name = Name String
type Person = Person Name
-- this is another good option:
type Person = Person { name : String }
Does anyone disagree with this? Would love to hear counterpoints
I mainly use type alias for records. Sometimes you want to hide the structure of a record but a lot of the time, you want to use it, it is part of the API.
Another case for the alias is declaring a type in terms of another type. Something like this:
type ID = ID Int
type alias Collection a =
Dict Int a
get : ID -> Collection a -> Maybe a
get (ID id) =
Dict.get id
I think there are two competing concerns going on here:
booleans are inherently unclear
type aliases are sometimes unclear
For the boolean stuff, yeah: use a custom type. I’m on board with that every time. You will almost always get a clearer, safer API. Here’s a whole talk on that: Solving the Boolean Identity Crisis by Jeremy Fairbank.
As for type aliases in general, here are my rules:
never use type aliases for simple types like Int or Bool. They let you express intent but, crucially, are not checked by the compiler (e.g. RoundUp and RoundToTens in your example can be exchanged and will still compile.)
sometimes use type aliases for types with type parameters so that Result x a or Dict comparable a become HttpResponse or Students. These have a very low chance of being accidentally swapped in, and the damage is pretty low if they are. That said, I always start with a closed custom type and open it up if necessary instead of the other way around, and has no runtime cost as the compiler will unbox them if it can.