Creating Classes

A light introduction to newtype, classes, and instances.

This material motivated by Haskell Programming from first principles, by Allen and Moronuki. The Github user kingparra has notes that might be useful.

If you are using a compile before GHC 2021, you will need to add FlexibleInstances to your language. You need this line if you have problems with your Demo String (see below).

{-# LANGUAGE FlexibleInstances #-}

Newtype

You already know two ways to create a type:

type MyInt1 = Int
data MyInt2 = MyInt2 Int

There is a way that is similar to the second that is commonly used to rename a single type.

newtype MyInt3 = MyInt3 Int

A newtype renames a single type. It cannot hold more than one thing. It is used to be able to tell the difference between things that would otherwise look the same.

For example, if you wanted to use Double to represent both money and time, you could use newtypes to distinguish them.

newtype Money = Money Double
newtype Time = Time Double

This makes mistaking dollars for hours impossible inside your program.

Usually you want to use deriving (Show, Eq) just like in data.

Creating Classes I

A class declaration specifies the names and signatures of methods that have to be available for instances of that class.

The Demo class below just requires a function that turns takes in something and produces an Int.

class Demo a where
   numit :: a -> Int

In order to make String an instance of the Demo class, we need a way of getting a number out of any String. We could use the length.

instance Demo String where
   numit s = length s

Technically, less logical choices would also be allowed like always making the output number zero.

  1. Make a Posn into an instance of the Demo class by adding the coordinates.

     type Posn = (Int, Int)
    
  2. Make a SInt into an instance of Demo my multiplying the number by the length of the String.

     data SInt = SInt Int String
    

Creating Classes II

  1. Write a class declaration for StepTwo so that StepTwo a means that there is a method rev :: a -> a.

  2. Make Integer an instance of StepTwo where rev makes a number into its opposite.

  3. Make String into an instance of StepTwo by having rev reverse the incoming string.

  4. Write a class declaration for ProbThree so that ProbThree a means there is a combination method, comb :: a -> a -> a

  5. Make Double an instance of ProbThree by using multiplication to combine.

  6. Write a class declaration for Bracket that abstracts the pattern aITEMa, like "apple" becomes "DocappleDoc". The myname method (below) would give "Doc" in this case.

    Write code so that Bracket b means that there are two items available:

     myname :: b
     brack :: String -> b
    
  7. Make String an instance of Bracket so that these tests pass:

     x :: String
     x = myname
     y = brack "WY"
     checkBracket = [x  == "MrHarris", y == "MrHarrisWYMrHarris" ]
    
  8. Make a newtype IntBrack that is just like an Int. Make IntBrack an instance of the Bracket class by choosing the number 42 as the “name” and doing 42 + length word + 42 for the brack operation.

Creating Classes III

  1. Create a new type called AInt that is the same as an Int.

  2. Write a function g :: Int -> AInt that doubles the number given.

  3. Verify that g 5 == AInt 10 but g (g 5) is an error.

  4. Add AInt to the Show typeclass so that show (AInt 7) returns "I:7". (Optional: verify this on the computer.)

    main = do
        let x = AInt 7
        putStrLn $ show x
    
  5. (Bonus) Add AInt to the Ord typeclass by providing a (<=) method that compares the ints.

  6. Create a Scholar class so that Scholar a means there is method study :: a -> Int.

  7. Make AInt an instance of the Scholar class, where the result of studying is just 5 more than the integer in the AInt.

  8. Make String an instance of the Scholar class, where the result of studying is 5 times the length of the string.

Creating Classes IV

You can have functions that take in more than one item in your class.

  1. Make a Join class that has a method together :: a -> a -> a.

  2. Make [Int] an instance of the Join class in some logical way.

  3. Make Int an instance of the Join class by multiplying.

  4. Make AInt an instance of the Join class by adding.

Creating Classes V

In this section we declare and use the class Bizarre a, which has two methods

weird :: a
(<-->) :: Int -> a -> [a]
  1. Declare Bizarre.
  2. Make Int an instance of this class.
  3. Make String an instance of this class.
  4. Make your own newtype Boring (Int) and make it Bizarre by always choosing 1 and the list [2,3]. (Not quite literally…)

Defining Typeclasses

Make a new typeclass building on something that already exists. For example, a type called Maxer that acts the same as Integer.

newtype Maxer = Maxer Integer

New define a typeclass called Sniffle.

class Sniffle a where
    sneeze :: a -> a
    cough :: a

Then an instance of the typeclass:

instance Sniffle Maxer where
     cough = Maxer 0
     sneeze (Maxer x) (Maxer y) = Maxer (x + y)

–>