1

I've been working on understanding Monads in Haskell a bit better so I've started reading Philip Wadler's paper Monads for functional programming. In order to internalize the principles a bit better, and expose myself to as much Haskell code as possible, I decided I would code up and test all the examples as I go through the paper.

Right off 2.2 Variation one: Exceptions is giving me a little trouble. Here is my code.

data Term = Con Int | Div Term Term
data M a = Raise Exception | Return a
type Exception = String

answer, error_ :: Term
answer = (Div (Div (Con 1972) (Con 2)) (Con 23))
error_ = (Div (Con 1) (Con 0))

eval :: Term -> M Int
eval (Con a) = Return a
eval (Div t u) = case eval t of 
                    Raise e -> Raise e
                    Return a ->
                        case eval u of 
                            Raise e -> Raise e
                            Return b ->
                                if  b == 0 
                                    then Raise "divide by zero"
                                    else Return (a `div` b)

I can load the code into GHCi, but when I try to run

eval answer

it throws the error

No instance for (Show (M Int)) arising from a use of ‘print’
In a stmt of an interactive GHCi command: print it

I read this post which explains why ‘print’ is being called and concluded that maybe I needed to add an instance to Show for M a. But when I added

instance Show (M a) where
show (M a) = show a

to my code I tried to reload the file I got this error.

Not in scope: data constructor ‘M’

This confused me at first but this post explains that I was defining a type constructor not a data constructor.

Anyway, I feel like I am going down a rabbit hole that may or may not have the solution and I figured I'd post the question over here. My code looks to be line-for-line the same as his code. What changes do I need to make to make the code run in GHCi?

  • 1
    There is no data constructor `M`, only `Raise` and `Return`, so you will need to implement those two cases. – Willem Van Onsem Aug 28 '18 at 06:14
  • 3
    Another reason to use the `deriving` mechanism - other than saving writing code - is that Haskell will generate an instance that properly handles precedence and nesting of data. So something like `show (Return (Con 4))` will properly result in the string `"Return (Con 4)"` rather than `"Return Con 4"` (assuming a proper `Show` instance for `Term`). – Mor A. Aug 28 '18 at 06:33

2 Answers2

5

The added Show instance will not work for two reasons:

  1. there is no constraint that says that the a of M a is an instance of Show; and
  2. the M data type has no data constructor M, it has a Raise, and a Return.

So a straightforward implementation would look like:

instance Show a => Show (M a) where
    show (Raise a) = "Raise " ++ show a
    show (Return a) = "Return " ++ show a

But we can save us the trouble, and let Haskell automatically derive an instance for Show with:

data M a = Raise Exception | Return a deriving Show
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Thanks! That worked. The deriving tool is pretty slick, but I can see why you might want to have custom implementations. For example, it might make sense for the `show (Return a)` to just be just `show a`, and have the `show (Raise a)` case to be `"Exception: " ++ show a`, or something. – Mitchell Turner Aug 28 '18 at 23:39
3

If you want to do it manually, you have to do something like

instance (Show a) => Show (M a) where
    show (Raise e)  = "Raise " ++ show e
    show (Return a) = "Return " ++ show a

Your type (M a) has two data constructors, Raise and Return.

The easy solution is to just say

data M a = Raise Exception | Return a
    deriving (Show)

Alternatively you can also manually deconstruct M a values every time you want to inspect them in ghci:

case eval answer of { Raise e -> "An error occurred: " ++ e; Return x -> show x }
-- instead of 'eval answer'

But that gets annoying quickly.

melpomene
  • 84,125
  • 8
  • 85
  • 148