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 ()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 binfix operator <$> is a synonym for fmap ie
g <$> x == g `fmap` x == fmap g xmapping the identity function over every item in a container has no effect.
fmap id = idmapping 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 aIO so you can modify the results of monadic actions using fmapFunctor and MonadFunctor 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 = vHomomorphism: 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) <*> uComposition: The trickiest law to gain intuition for. Expressing a sort of associativity <*>
u <*> (v <*> w) = pure (.) <*> u <*> v <*> wrelation 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 + aclass Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
mconcat = foldr mappend memptymempty 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 bmempty `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 bnewtype
Coord maps from (a,a) to CoordunCoord maps from (a,a) to Coordshow 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 . unCoordCoord 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 <*> brealToFrac, 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 * yCoord valuesnewtype wrap Coord to create Extent
ExtentsExtents data constrcutor and use extentsFromCoord which forces absolute valuesExtents constructor from the moduleextentsFromCoord maps Coord to ExtentscoordFromExtents maps Extents to CoordExtents 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 . coordFromExtentsBounds as a centre with an extentsCoord and ExtentsMonoid 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' = divMonoid 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 afillBoundsFill 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 functionFill has a Monoid instance given that
Bounds has a Monoid instance for the co-ordinate type ca 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 classCharMaybe type for which a embodies ProduceCharLast type for which a embodies ProduceCharLast 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 . JustFill 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 DestroyerCruiser 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 boardships 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 genMonoid 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 ()