Haskell | Struggling to understand type declaration and abbreviations

Viewed 124

In a course I am following about Haskell, I am learning about type declaration. Currently, it is discussing abbreviations, something I am confused about, and that I cannot find the information I am looking for online.

It gives the following example:

type Pair a = (a, a)

along with a snippet of text: Abbreviations - new name for existing types

I have seen an example on the documentation that closely resembles the code above:

type Name = String

but from that I still cannot make sense of the above example.

I am struggling to understand what it is trying to explain, so can someone else make sense of it for me and give simple example of its usage in a program?

2 Answers

This is a type alias [Haskell wiki]. It is used to often give a name to more complicated types.

For example if you define a function:

sum2 :: Pair Int -> Int
sum2 (x, y) = x + y

then behind the curtains, this is thus resolved to:

sum2 :: (Int, Int) -> Int
sum2 (x, y) = x + y

Type aliases are often used to

  1. to give a special structure a more convenient name;
  2. to shorten complicated types; and
  3. to make it easy to switch from type.

For example a String is defined as:

type String = [Char]

A String is thus simply a list of Characters. But a signature with String focuses on the fact that it works with textual data.

Type aliases are often used to abstract away complexity of a type. For example we can define a type Operator:

type Operator a = a -> a -> a

This is thus an alias for a function that maps two parameters of type a to a value of type a. This is interesting if we later want to create functions that work with this operator. For example:

maxOperator :: Ord a => Operator a -> Operator a -> Operator a
maxOperator f g x y = max (f x y) (g x y)

This is more likely more readable than:

maxOperator :: Ord a => (a -> a -> a) -> (a -> a -> a) -> (a -> a -> a)
maxOperator f g x y = max (f x y) (g x y)

and it definitely is more readable if we would later use Operator (Operator a), which thus resolves to (a -> a -> a) -> (a -> a -> a) -> a -> a -> a.

Finally types are sometimes used if it is not yet completely clear what type you will use. If you are for example implementing a package and you have not yet decided to work with Floats or Doubles, you can define a type alias:

type Scalar = Float

If you then later change your mind, you can rewrite it to type Scalar = Double, and all places where you used Scalar will now resolve to Double.

When you write a statement with the type keyword, you are defining a type synonym. Thus in the following example, the type Pair Int and the type (Int, Int) are exactly the same type and can be used interchangeably:

type Pair a = (a, a)

myPair :: Pair Int
myPair = (1, 2)

sumPair :: Num a => Pair a -> a
sumPair = uncurry (+)

This example works even though the type of uncurry specifies a two-tuple instead of a Pair because a Pair is a two-tuple.

This contrasts with the similar declaration newtype, which defines a wrapper around another type. In the following example, the types Pair Int and (Int, Int) are distinct at compile-time and cannot be used interchangeably:

newtype Pair a = Pair { unpair :: (a, a) }

myPair :: Pair Int
myPair = Pair (1, 2)

sumPair :: Num a => Pair a -> a
sumPair = uncurry (+) . unpair

It is necessary in the second example to call Pair to wrap the two-tuple into a pair before assigning it to myPair and unpair to unwrap the pair into a standard two-tuple before we can use uncurry because Pair Int and (Int, Int) are treated as distinct types.

Related