Imagine a language which doesn\'t allow multiple value constructors for a data type. Instead of writing
data Color = White | Black | Blue
we wo
Given that you mention TypeScript, it is instructive to have a look at what its docs have to say about its union types. The example there starts from a function...
function padLeft(value: string, padding: any) { //etc.
... that has a flaw:
The problem with
padLeft
is that its padding parameter is typed asany
. That means that we can call it with an argument that’s neither anumber
nor astring
One plausible solution is then suggested, and rejected:
In traditional object-oriented code, we might abstract over the two types by creating a hierarchy of types. While this is much more explicit, it’s also a little bit overkill.
Rather, the handbook suggests...
Instead of
any
, we can use a union type for thepadding
parameter:function padLeft(value: string, padding: string | number) { // etc.
Crucially, the concept of union type is then described in this way:
A union type describes a value that can be one of several types.
A string | number
value in TypeScript can be either of string
type or of number
type, as string
and number
are subtypes of string | number
(cf. Alexis King's comment to the question). An Either String Int
value in Haskell, however, is neither of String
type nor of Int
type -- its only, monomorphic, type is Either String Int
. Further implications of that difference show up in the remainder of the discussion:
If we have a value that has a union type, we can only access members that are common to all types in the union.
In a roughly analogous Haskell scenario, if we have, say, an Either Double Int
, we cannot apply (2*)
directly on it, even though both Double
and Int
have instances of Num
. Rather, something like bimap
is necessary.
What happens when we need to know specifically whether we have a
Fish
? [...] we’ll need to use a type assertion:let pet = getSmallPet(); if ((
pet).swim) { ( pet).swim(); } else { ( pet).fly(); }
This sort of downcasting/runtime type checking is at odds with how the Haskell type system ordinarily works, even though it can be implemented using the very same type system (also cf. leftaroundabout's answer). In contrast, there is nothing to figure out at runtime about the type of an Either Fish Bird
: the case analysis happens at value level, and there is no need to deal with anything failing and producing Nothing
(or worse, null
) due to runtime type mismatches.