Log For Haskell: Week 2

Day 1, April 17, 2020: Typeclasses

Typeclasses are among the most powerful features in Haskell. They allow us to define generic interfaces that provide a common feature set over a wide variety of types; defining  a set of functions that can have different implementations depending on the type of data they are given.

Here is a typeclass is defined:

class BasicEq a where
isEqual :: a -> a -> Bool
isEqual x y = not (isNotEqual3 x y)

isNotEqual :: a -> a -> Bool
isNotEqual x y = not (isEqual x y)

and here is how it is used (instantiated):

instance BasicEq Bool where
isEqual True True = True
isEqual False False = True
isEqual _ _ = False

and also

instance BasicEq Color where
isEqual Red Red = True
isEqual Green Green = True
isEqual Blue Blue = True
isEqual _ _ = False

This is like refl in Martin-Löf type theory.  If BasicEq or ==is not implemented for a type, you could say (in the abstract) that that type has no notion of equality since = is uninhabited.)  You can instantiate an existing typeclass for a new type:

instance Show Color where
show Red = "Red"
show Green = "Green"
show Blue = "Blue"

On the Read and Show typeclasses:

You may often have a data structure in memory that you need to store on disk for later retrieval or to send across the network. The process of converting data in memory to a flat series of bits for storage is called serialization. It turns out that read and show make excellent tools for serialization. show produces output that is both human- and machine-readable. Most show output is also syntactically valid Haskell, though it is up to people that write Show instances to make it so.

Example

First write a data structure to disk:

ghci> let d1 = [Just 5, Nothing, Nothing, Just 8]::[Maybe Int]
ghci> putStrLn (show d1)
[Just 5,Nothing,Nothing,Just 8]
ghci> writeFile "test" (show d1)

Now read it back:

ghci> input <- readFile "test"
"[Just 5,Nothing,Nothing,Just 8]"
ghci> let d2 = read input
Ambiguous type variable `a' in the constraint:
`Read a' arising from a use of `read' at <interactive>:1:9-18
Probable fix: add a type signature that fixes these type variable(s)

ghci> let d2 = (read input)::[Maybe Int]
ghci> print d1
[Just 5,Nothing,Nothing,Just 8]
ghci> print d2
[Just 5,Nothing,Nothing,Just 8]
ghci> d1 == d2
True

Automatic derivation

data Color = Red | Green | Blue
deriving (Read, Show, Eq, Ord)

Pragmas

{-# LANGUAGE TypeSynonymInstances #-}

Data versus Newtype

data DataInt = D Int
deriving (Eq, Ord, Show)

newtype NewtypeInt = N Int
deriving (Eq, Ord, Show)

When we declare a newtype, we must choose which of the underlying type’s typeclass instances we want to expose. Here, we’ve elected to make NewtypeInt provide Int’s instances for Eq, Ord, and Show. As a result, we can compare and print values of type

NewtypeInt: ghci> N 1 < N 2
True

Since we are not exposing Int’s Num or Integral instances, values of type NewtypeInt are not numbers. For instance, we can’t add them:

ghci> N 313 + N 37
--> error

Further example (hiding implementation):

newtype UniqueID = UniqueID Int
    deriving (Eq)

The compiler treats UniqueID as a different type from Int. As a user of UniqueID, we know only that we have a unique identifier; we cannot see that it is implemented as an Int.

Recap: defining types

  1. The datakeyword introduces a truly new algebraic data type.
  2. The type keyword gives us a synonym to use for an existing type. We can use the type and its synonym interchangeably.
  3. The newtype keyword gives an existing type a distinct identity. The original type and the new type are not interchangeable.

 

.

 

 

 

Published by

epsilon

Dabbling in code and music.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s