Main function entry point
> main :: IO ()
> main = do
> putStrLn "An exmaple picture"
> myPicture
> _ <- putStrLn "enter any text to continue " >> getLine
> gen0 <- getStdGen
> playNewGame gen0
> return ()
Handré Stolp
January 13, 2014
A
and copy all the text to a .lhs
file and run in GHCi
> {-# LANGUAGE FlexibleContexts #-}
> module Slides where
>
> import Control.Applicative
> import Control.Monad
> import Data.Monoid
> import Data.Maybe
> import Data.Char
> import Debug.Trace
> import System.Random
$
, it just takes a function and applies a value to it but has very low level of associativity.
f $ g $ h x = f (g (h x))
.
which is infixr 9
($) :: (a -> b) -> a -> b
f $ x = f x
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
Functor
is a "container"Functor
represents some "computational context"Its definition :
class Functor f where
-- Take function (a -> b) and return function f a -> f b
fmap :: (a -> b) -> f a -> f b
infix operator <$>
is a synonym for fmap
ie
g <$> x == g `fmap` x == fmap g x
mapping the identity function over every item in a container has no effect.
fmap id = id
mapping a composition of two functions over every item in a container is the same as first mapping one function, and then mapping the other.
fmap (g . h) = (fmap g) . (fmap h)
(->) e
or functions (e -> a)
are functors with element/contextual values of type a
IO
so you can modify the results of monadic actions using fmap
Functor
and Monad
Functor
lifts a "normal" function to some context but does not allow applying a function in a context to a value in a contextApplicative
provides this by the lifted function application operator <*>
Additionally provides pure
to embed a value in an "effect free" context.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
<*>
takes a function in context f
and applies it to a value in context f
returning a value in context f
<*>
is similar to a lifted $
The identity law:
pure id <*> v = v
Homomorphism: Applying a non-effectful function to a non-effectful argument is the same as applying the function to the argument and then injecting into the context.
pure f <*> pure x = pure (f x)
Interchange: When evaluating the application of an effectful function to a pure argument, the order does not matter
u <*> pure y = pure ($ y) <*> u
Composition: The trickiest law to gain intuition for. Expressing a sort of associativity <*>
u <*> (v <*> w) = pure (.) <*> u <*> v <*> w
relation to fmap
: fmap g x
is the same as lifting g
using pure
and applying to x
fmap g x = pure g <*> x
(a (+) b) (+) c == a (+) (b (+) c)
a (+) b == b (+) a
NOT REQUIRED.a + 0 == a == 0 + a
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
mconcat = foldr mappend mempty
mempty
is the empty elementmappend
is the binary associative operation between two elementsmconcat
is a convenience function which may be specialized and used to collapse a list of values using mempty
and mappend
<>
infix operator is a synonym for mappend
ie a <> b == mappend a b
mempty `mappend` x = x
x `mappend` mempty = x
(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)
Monoid b => Monoid (a -> b)
or any function from a
to b
where b
is a Monoid
is also a Monoid
Monoid
instance of b
newtype
Coord
maps from (a,a)
to Coord
unCoord
maps from (a,a)
to Coord
show
for Coord to be the same show
for tuple> newtype Coord a = Coord {unCoord :: (a,a)} deriving (Eq, Ord)
>
> instance Show a => Show (Coord a) where show = show . unCoord
Coord
a Functor
instanceCoord
> instance Functor Coord where
> fmap f (Coord (x,y)) = Coord (f x, f y)
Applicative
instance allows us to apply lifted function to values in Coord
contextpure
fills both elements with the same value<*>
applies the functions in each element of LHS Coord
to values in RHS Coord
respectively> instance Applicative Coord where
> pure a = Coord (a,a)
> Coord (g, h) <*> Coord (x, y) = Coord (g x, h y)
Monoid
instance if the element type has onemempty
is just mempty
for the element typemappend
is just mappend
for the element type applied per element respectively> instance Monoid a => Monoid (Coord a) where
> mempty = Coord (mempty, mempty)
> Coord (lx, ly) `mappend` Coord (rx, ry) = Coord (lx <> rx, ly <> ry)
Coord
valuesNum
Num
gives access to +
and -
+
and -
Coord
is of class Applicative
we can define the operations by lifting +
and -
+
and apply to a
((+) <$> a
)Coord
to b
(<*> b
)> (|+|) :: Num a => Coord a -> Coord a -> Coord a
> infixl 6 |+|
> a |+| b = (+) <$> a <*> b
>
> (|-|) :: Num a => Coord a -> Coord a -> Coord a
> infixl 6 |-|
> a |-| b = (-) <$> a <*> b
realToFrac
, its used to coerce any real number to a fractional (for sqrt
)
> coordLength :: (Real a, Floating b) => Coord a -> b
> coordLength (Coord (x, y)) = sqrt . realToFrac $ x * x + y * y
Coord
valuesnewtype
wrap Coord
to create Extent
Extents
Extents
data constrcutor and use extentsFromCoord
which forces absolute valuesExtents
constructor from the moduleextentsFromCoord
maps Coord
to Extents
coordFromExtents
maps Extents
to Coord
Extents
constructor with the lifted abs
function over Coord
> newtype Extents a = Extents {coordFromExtents :: Coord a} deriving (Eq, Ord)
>
> extentsFromCoord :: Num a => Coord a -> Extents a
> extentsFromCoord c = Extents . fmap abs $ c
>
> instance Show a => Show (Extents a) where show = show . coordFromExtents
Bounds
as a centre with an extentsCoord
and Extents
Monoid
instance so that Bounds
may accumulate into larger Bounds
> data Bounds a = Bounds { boundsCentre :: Coord a
> , boundsExtent :: Extents a
> } deriving (Show, Eq, Ord)
Divisor
class and specialize it for the numeric types>
> class Divisor a where divideBy' :: a -> a -> a
> instance Divisor Double where divideBy' = (/)
> instance Divisor Float where divideBy' = (/)
> instance Divisor Int where divideBy' = div
> instance Divisor Integer where divideBy' = div
Monoid
instance requires that the element type be in Divisor
, Num
and Eq
so that all operations can be performed.Coord
is Applicative
notice how we can lift divideBy
to work on the result of |+|
> instance (Divisor a, Num a, Eq a) => Monoid (Bounds a) where
> -- A zero extents bounds is considered empty
> mempty = Bounds (Coord (0,0)) (extentsFromCoord . Coord $ (0,0))
> -- Appending empty to anything does not change it
> Bounds _ (Extents (Coord (0,0))) `mappend` r = r
> l `mappend` Bounds _ (Extents (Coord (0,0))) = l
> -- Appending two non empties
> l `mappend` r = Bounds c $ extentsFromCoord e
> where
> -- centre is the average of the two centres
> c = (`divideBy'`2) <$> boundsCentre l |+| boundsCentre r
> -- extents is the sum of the two extents
> e = (coordFromExtents . boundsExtent $ l) |+| (coordFromExtents . boundsExtent $ r)
coordI
constructor for Int
based Coord
values.> type CoordI = Coord Int
> type ExtentsI = Extents Int
> type BoundsI = Bounds Int
>
> coordI :: Int -> Int -> Coord Int
> coordI x y = Coord (x,y)
Fill
based on some Coord
element type c
and some value a
queryFill
maps Coord
inputs to some value a
fillBounds
Fill
around : moveFill
> data Fill c a = Fill { queryFill :: Coord c -> a
> , fillBounds :: Bounds c
> , moveFill :: Coord c -> Fill c a
> }
Fill c
a Functor
so that the associated values may be modified.(->) a
is an instance of Functor
we just fmap
g
over q
to get the modified query.
moveFill
is a Functor
but its result is also a Fucntor
so we just lift the function twice to get the new move.> instance Functor (Fill c) where
> fmap g Fill { queryFill = q
> , fillBounds = b
> , moveFill = m
> } = Fill (fmap g q) -- map g over q to get new query
> b
> ((fmap . fmap) g m) -- lift g twice before applying to m to the new move function
Fill
has a Monoid
instance given that
Bounds
has a Monoid
instance for the co-ordinate type c
a
has a Monoid
instance(->) a
has a Monoid
instance just 'concat' the query functions and the move functions> instance (Monoid a, Monoid (Bounds c)) => Monoid (Fill c a) where
> mempty = Fill (const mempty) mempty (const mempty)
> a `mappend` b = Fill (queryFill a <> queryFill b) -- concat the result of the query
> (fillBounds a <> fillBounds b) -- sum the bounds
> (moveFill a <> moveFill b) -- concat the results of the move
(Real c, Divisor c, Monoid a)
Monoid
instance of bounds)Monoid
instance exists for result value of the fill> fillCircle :: (Real c, Divisor c, Monoid a) => a -> c -> Coord c -> Fill c a
> fillCircle val radius pos = Fill qry bnds mv
> where
> -- When the coordinate is within the radius distance from the centre produce
> qry crd | coordLength (crd |-| pos) <= realToFrac radius = val
> | otherwise = mempty
> -- The bounds is a square centred around the position
> bnds = Bounds pos (Extents . Coord $ (radius, radius))
> -- Moving it construct circle with new centre
> mv pos' = fillCircle val radius (pos |+| pos')
>
> fillRectangle :: (Real c, Divisor c, Monoid a) => a -> c -> c -> Coord c -> Fill c a
> fillRectangle val w h pos = Fill qry bnds mv
> where
> -- When the coordinate is within bounds of the rectangle produce
> qry crd | let (x, y) = unCoord $ abs <$> (crd |-| pos)
> in x <= halfW && y <= halfH = val
> | otherwise = mempty
> halfW = w `divideBy'` 2
> halfH = h `divideBy'` 2
> -- the rectangle is its bounds
> bnds = Bounds pos (Extents . Coord $ (halfW, halfH))
> -- Moving it constructs a new rectangle centred on the new position
> mv pos' = fillRectangle val w h (pos |+| pos')
Fill
for which the produced value can map to a characterProduceChar
type classChar
Maybe
type for which a
embodies ProduceChar
Last
type for which a
embodies ProduceChar
Last
is a newtype
wrapper around Maybe
giving a Monoid
instance taking the last produced value if any.Last Char
a lot we add a helper for it> class ProduceChar a where produceChar :: a -> Char -- map some value to a Char
>
> instance ProduceChar Char where
> produceChar = id -- always produces itself (hence id)
>
> instance ProduceChar a => ProduceChar (Maybe a) where
> produceChar Nothing = ' ' -- when nothing produce space
> produceChar (Just a) = produceChar a -- when something produce the related Char
>
> instance ProduceChar a => ProduceChar (Last a) where
> produceChar = produceChar . getLast
>
> lastChar :: Char -> Last Char
> lastChar = Last . Just
Fill
to say how the matrix should be filledfmap
the function produceChar
over the Fill
> drawFillMatrix :: ProduceChar a => Int -> Int -> Fill Int a -> IO ()
> drawFillMatrix cs ls fl = putStrLn $ ios cs2 ls
> where
> cs2 = cs * 2
> -- we map produceChar over the result of the query
> flToChar = queryFill . fmap produceChar $ fl
> ios 0 0 = []
> -- end of each line we add a new line
> ios 0 l = '\n' : ios cs2 (l-1)
> -- we iterate over the coordinates in our matrix
> ios c l = (flToChar $ Coord (cs - c `div` 2, ls - l)) : ios (c-1) l
'+'
in the corners of the character matrix'X'
then '#'
, then rectangle of '$'
and finally a circle of ' '
Fill
values using the Monoid
append operator <>
Fill
that maps different coordinates to different characters> myPicture :: IO ()
> myPicture = drawFillMatrix 40 40 (border <> moveFill image (coordI 5 5))
> where
> border = fillRectangle (lastChar '+') 1 1 (coordI 0 0)
> <> fillRectangle (lastChar '+') 1 1 (coordI 40 40)
> <> fillRectangle (lastChar '+') 1 1 (coordI 40 0)
> <> fillRectangle (lastChar '+') 1 1 (coordI 0 40)
>
> image = fillCircle (lastChar 'X') 11 (coordI 15 15)
> <> fillCircle (lastChar '#') 7 (coordI 15 15)
> <> fillRectangle (lastChar '$') 6 6 (coordI 15 15)
> <> fillCircle (lastChar ' ') 2 (coordI 15 15)
+ +
XX
XXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX##XXXXXXXXXXXXXXXX
XXXXXXXXXXXX##############XXXXXXXXXXXX
XXXXXXXXXX##################XXXXXXXXXX
XXXXXXXXXX######################XXXXXXXXXX
XXXXXXXX######$$$$$$$$$$$$$$######XXXXXXXX
XXXXXXXX######$$$$$$ $$$$$$######XXXXXXXX
XXXXXXXX######$$$$ $$$$######XXXXXXXX
XXXXXXXX########$$ $$########XXXXXXXX
XXXXXXXX######$$$$ $$$$######XXXXXXXX
XXXXXXXX######$$$$$$ $$$$$$######XXXXXXXX
XXXXXXXX######$$$$$$$$$$$$$$######XXXXXXXX
XXXXXXXXXX######################XXXXXXXXXX
XXXXXXXXXX##################XXXXXXXXXX
XXXXXXXXXXXX##############XXXXXXXXXXXX
XXXXXXXXXXXXXXXX##XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXX
XX
+ +
+ +
XX
XX
XX
XX
CC
CCCCCCCCCCCC
CCCCCCCCCCCCCC
CCCCCCCCCCCC
CC
CC
CCCCCCCCCCCC
CCCCCCCCCCCCCC
CCCCCCCCCCCC
XX CC XX XX
XX
DD
DDDDDD
DDDDDDDDDD
DDDDDD
DD CC
DD CCCCCCCCCCCC
DDCCCCCCCCCCCCCC
DDDDDDCCCCCCCCCCCC
DDDDDDDDDDCC
DDDDDD
DD XX
CC
CCCCCCCCCCC
CCCCCCCCCCCCC
CCCCCCCCCCC
CC
+ +
Guess r c / Cheat 'c' / New Game 'n' / Quit 'q'
Cruiser
or a Destroyer
Cruiser
is drawn as the character 'C'Destroyer
is drawn as the character 'D'ShipVertical
or ShipHorizontal
> -- Type of ship
> data ShipType = Cruiser | Destroyer
>
> -- what character is produced by the ship type
> instance ProduceChar ShipType where
> produceChar Cruiser = 'C'
> produceChar Destroyer = 'D'
>
> -- How is the ship oriented on the board
> data ShipOrientation = ShipVertical | ShipHorizontal deriving (Show, Eq, Ord, Bounded)
cruiser
is a square with a 'circle' at the one end (looks like an arrow)destroyer
is a thinner square with 'circle' at either endsMonoid
append operator <>
over Fill
areasFill
types produce respective ShipType
values for the areas where they are defined> -- Make a cruiser ship given an orientation and a centre
> cruiser :: ShipOrientation -> CoordI -> Fill Int (Last ShipType)
> cruiser o pos = case o of
> ShipVertical -> fillRectangle t 2 3 pos <> fillCircle t 2 (pos |-| coordI 0 3)
> ShipHorizontal -> fillRectangle t 3 2 pos <> fillCircle t 2 (pos |-| coordI 3 0)
> where
> t = Last . Just $ Cruiser
>
> -- Make a destroyer ship given an orientation and a centre
> destroyer :: ShipOrientation -> CoordI -> Fill Int (Last ShipType)
> destroyer o pos = case o of
> ShipVertical -> fillRectangle t 1 2 pos
> <> fillCircle t 2 (pos |-| coordI 0 3)
> <> fillCircle t 2 (pos |+| coordI 0 3)
>
> ShipHorizontal -> fillRectangle t 2 1 pos
> <> fillCircle t 2 (pos |-| coordI 3 0)
> <> fillCircle t 2 (pos |+| coordI 3 0)
> where
> t = Last . Just $ Destroyer
> layoutBoard :: Int -> Int -> [Fill Int (Last ShipType)] -> [Fill Int (Last ShipType)]
> layoutBoard _ _ [] = []
> layoutBoard w h ships = ships'
> where
> ships' = foldl findPlace [] shipsInBnds
> shipsInBnds = map toBnds ships
>
> toBnds s = let
> bnds = fillBounds s
> Coord (cx, cy) = boundsCentre bnds
> Coord (ex, ey) = coordFromExtents . boundsExtent $ bnds
> dx = if cx < ex then ex - cx else (if cx + ex > w then w - cx - ex else 0)
> dy = if cy < ey then ey - cy else (if cy + ey > h then h - cy - ey else 0)
> in moveFill s (coordI dx dy)
>
> findPlace [] n = [n]
> findPlace ps n = ps <> [offset (mconcat ps) n (coordI 0 0) 0]
>
> offset chk n o m =
> let
> n' = if m >= 2 * w * h then error "fails" else moveFill n o
> bnds = fillBounds n'
> Coord (cx, cy) = boundsCentre bnds
> Coord (ex, ey) = coordFromExtents . boundsExtent $ bnds
> cs = [coordI x y | x <- [(cx - ex) .. (cx + ex)], y <- [(cy - ey) .. (cy + ey)]]
> in if isOk chk n' cs
> then n'
> else offset chk n (incOff o) (m+1)
>
> isOk chk n cs = let
> bnds = fillBounds n
> Coord (cx, cy) = boundsCentre bnds
> Coord (ex, ey) = coordFromExtents . boundsExtent $ bnds
> xOk = cx >= ex && cx + ex <= w
> yOk = cy >= ey && cy + ey <= h
> in xOk && yOk && (not . getAny . mconcat . map (Any . col chk n) $ cs)
>
> col chk n c = let
> a = getLast . queryFill chk $ c
> b = getLast . queryFill n $ c
> in case (a,b) of
> (Just _, Just _) -> True
> _ -> False
>
> incOff (Coord (x,y)) | x >= w && y >= h = Coord (0, 0)
> | x >= w = Coord (0, y + 1)
> | otherwise = Coord (x + 1, y)
newtype
wrapper over lists ZipList
which gives an alternate Applicative
implementation for lists.
([destroyer, cruiser] !!)
where the list index operator is partially applied to a list of functions.
Applicative
to combine infinite lists of functions over infinite lists of values to give infinite list of resultant ships.layoutBoard
to make sure its is a valid configuration.> randomBoard :: StdGen -> (StdGen, [Fill Int (Last ShipType)])
> randomBoard gen =
> let ship = ZipList . map ([destroyer, cruiser] !!) . randomRs (0, 1) $ gen
> orient = ZipList . map ([ShipVertical,ShipHorizontal] !!) . randomRs (0, 1) $ gen
> cxs = ZipList . randomRs (0, 40) $ gen
> cys = ZipList . randomRs (0, 40) $ gen
> in ( mkStdGen . fst . random $ gen
> , layoutBoard 40 40 . take 5 . getZipList $ ship <*> orient <*> (coordI <$> cxs <*> cys)
> )
Game
state value between different IO actions> data Game = Game { ships :: [Fill Int (Last ShipType)] -- the alive ships
> , board :: Fill Int (Last Char) -- the board showing choices and dead ships
> }
>
> -- Helper that maps a ship to characters
> shipToBrd :: Fill Int (Last ShipType) -> Fill Int (Last Char)
> shipToBrd s = Last . (fmap produceChar) . getLast <$> s
>
> -- Helper action that draws the game board for us
> drawBrd :: Fill Int (Last Char) -> IO ()
> drawBrd = drawFillMatrix 40 40
Applicitive
again(coordI <$> [0,40] <*> [0,40])
applies coordI
to all the possible combinations of corner coordinates>
> -- Action starting a new game
> playNewGame :: StdGen -> IO ()
> playNewGame gen = let
> -- we generate a random board
> (gen', ships) = randomBoard gen
> -- draw the border '+' characters using the normal Applicative instance for list
> -- to get all the corner combinations
> border = mconcat $ map (fillRectangle (lastChar '+') 1 1) (coordI <$> [0,40] <*> [0,40])
> -- and then we start the game
> in playGame gen' (Game ships border)
Monoid
append operator <>
to combined the current board
ships
flattened to a displayable Fill
usingmconcat
which flattens a list using Monoid
> -- when we chose to cheat we show the board with all the ships on it and then continue playing
> cheatGame :: StdGen -> Game -> IO ()
> cheatGame gen g = drawBrd (board g <> (mconcat . map shipToBrd . ships $ g)) >> playGame gen g
>
> -- when we win the game we can choice to play a new one
> wonGame :: StdGen -> IO ()
> wonGame gen = do
> putStrLn "You won the game. Play another ? 'y'/'n'"
> t <- getLine
> case filter (not . isSpace) t of
> 'y' : _ -> playNewGame gen
> 'Y' : _ -> playNewGame gen
> 'n' : _ -> return ()
> 'N' : _ -> return ()
> _ -> wonGame gen
Monoid
append operator <>
to update the board
Fill
using Monoid
> takeShot :: String -> StdGen -> Game -> IO ()
> takeShot t gen g = let
> r : c : _ = map (read) (words t)
> -- The board is updated
> b = board g
> -- With an 'X' showing where we guessed
> <> fillRectangle (lastChar 'X') 1 1 (coordI r c)
> -- And the display of all the ships which were hit
> <> (mconcat . map shipToBrd $ hit)
> -- Hit ships are those which produce at the coordinate
> produces = isJust . getLast . (\q -> q (coordI r c)) . queryFill
> hit = filter produces (ships g)
> -- Missed ships are those which do not produce at the coordinate
> miss = filter (not . produces) (ships g)
> -- The new game state is all the missed ships and the updated board
> in playGame gen (Game miss b)
> playGame :: StdGen -> Game -> IO ()
> playGame gen g = if win g then wonGame gen else do
> drawBrd . board $ g
> putStrLn "Guess r c / Cheat 'c' / New Game 'n' / Quit 'q'"
> t <- getLine
> case filter (not . isSpace) t of
> 'c' : _ -> cheatGame gen g
> 'C' : _ -> cheatGame gen g
> 'n' : _ -> playNewGame gen
> 'N' : _ -> playNewGame gen
> 'Q' : _ -> return ()
> 'q' : _ -> return ()
> _ -> takeShot t gen g
> where
> win (Game [] _) = True
> win _ = False
> main :: IO ()
> main = do
> putStrLn "An exmaple picture"
> myPicture
> _ <- putStrLn "enter any text to continue " >> getLine
> gen0 <- getStdGen
> playNewGame gen0
> return ()