Imperative OOP Ceremony: Design Patterns Pt. 1

The naming of Reader, Writer and State is not very intuitive. For starters, a Reader is a function, a Writer is a tuple and a State is a function that returns a tuple. The magic lies in the implementation of Reader, Writer and State as monads.

  • The Reader function binds to an operator that "reads" both the function's argument and its return value.
  • The Writer tuple is a state-value pair that binds to a function that takes the value element and creates a new state-value pair. The implementation of >>= "writes" the new state element to the original state element.
  • The State function takes a state and returns a state-value pair. It binds to an operator that "reads" both the new state element and the new value element.

Reader

Since a Reader is a mere function, its type is basically ->. The following two type signatures are equivalent:

  • a -> b
  • (->) a b

For the sake of readability, we will define a type synonym R for ->. The next two type signatures are equivalent to the former two:

  • a `R` b
  • R a b
{-# LANGUAGE TypeSynonymInstances #-}

import Prelude hiding (Monad (..), (.))
x.f = f(x)

class Monad m where
  (>>=)  :: m a -> (a -> m b) -> m b
  return :: a -> m a

-- show
type R = (->)
instance Monad (R domain) where
  return(x) = \(_) -> x
  g >>= f = \(x) -> g(x).f!(x)
    where (!) = ($)

reader = (*3) >>= (-)

main = print(reader(42))
-- /show

Writer

Since a Writer is a mere tuple, its type is basically (,). ((a, b) is syntactic sugar for (,) a b.) For the sake of readability, we will define a type synonym W for (,), making the following type signatures equivalent:

  • (a, b)
  • (,) a b
  • W a b

In order for a state value to be "writeable" it must be a Monoid, i.e. it must implement

  • <> (or mappend), an operator that appends one Monoid to another Monoid and
  • mempty, an "empty" Monoid.
{-# LANGUAGE TypeSynonymInstances #-}

import Data.Monoid
import Prelude hiding (Monad (..), (.))
x.f = f(x)

class Monad m where
  (>>=)  :: m a -> (a -> m b) -> m b
  return :: a -> m a

-- show
type W = (,)
instance Monoid this => Monad (W this) where
  return(x) = (mempty, x)
  (this, x) >>= f = (this <> this', x')
    where (this', x') = f(x)

write(x) = ("inc " ++ show(x) ++ "! ", x + 1)

main = print $ (mempty, 42) >>= write >>= write >>= write 
-- /show

State

A State is combination of Reader and Writer. It's a Reader function that takes a state and returns a Writer state-value pair.

Let's define a type synonym S this a for R this (W this a), i.e. this -> (this, a).

import Data.Monoid
import Prelude hiding ((.), Monad(..))
x.f = f(x)

type R = (->)
type W = (,)

-- show
type S this a = R this (W this a)
-- "instance Monad (S this a) where"
return :: a -> S this a
return(x) = \(this) -> (this, x)
(>>=) :: S this a -> (a -> S this b) -> S this b
start >>= f = \(this) ->
  let (this',  x') = this.start
  in   this'.f(x')

start = \(this) -> (this ++ "!", 0)
append(x) = \(this) -> (this ++ ".", length(this) - x)

main = do {
  print(start $ "hi");
  print(start >>= append $ "hi");
  print(start >>= append >>= append $ "hi");
}
-- /show

In the next tutorial we shall reimplement Reader, Writer and State as newtype instances instead of type synonyms (type).