From 9d59812bbf66a87029d4349fee9cc08f32cadaab Mon Sep 17 00:00:00 2001 From: Niklas Heim Date: Wed, 22 May 2024 08:02:10 +0200 Subject: [PATCH] missing file; prettify --- labs/lab12.md | 29 ++++++++++-------- labs/lab13.md | 69 ++++++++++++++++++++++++++----------------- public/code/Random.hs | 38 ++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 39 deletions(-) create mode 100644 public/code/Random.hs diff --git a/labs/lab12.md b/labs/lab12.md index dd6141a..1bbd278 100644 --- a/labs/lab12.md +++ b/labs/lab12.md @@ -361,15 +361,18 @@ digit = sat isDigit char :: Char -> Parser Char char c = sat (== c) +``` +::: code-group +```haskell [do-notation] string :: String -> Parser String string [] = return [] string (x:xs) = do char x string xs return (x:xs) ``` -::: details Alternative definition using Applicative -```haskell + +```haskell [Alternative definition using Applicative] string :: String -> Parser String string [] = return [] string (x:xs) = char x *> string xs *> pure (x:xs) @@ -407,7 +410,8 @@ Thus `many` can handle (possibly empty) sequences (e.g., an arbitrary series of (e.g., non-empty sequences of digits representing an integer). To disregard sequences of spaces, we define the following parser together with the function `token` that transforms any parser to omit spaces at the beginning and the end. -```haskell +::: code-group +```haskell [do-notation] space :: Parser () space = do many (sat isSpace) return () @@ -419,8 +423,7 @@ token p = do space return x ``` -::: details Alternative definition by Applicative combinators -```haskell +```haskell [Applicative] space :: Parser () space = do many (sat isSpace) *> pure () @@ -476,7 +479,10 @@ Just ((3,2),"") Next, we focus on maze parsing. We define simple parsers for blocks. Out of them, we can create a parser for rows. Using the operator `<|>`, we can define the parser `wall <|> free` which parses either the wall or free block. -```haskell + +::: details Solution: `wall`, `free`, `row` +::: code-group +```haskell [do-notation] wall :: Parser Block wall = do char '#' return W @@ -491,9 +497,7 @@ row = do bs <- many (wall <|> free) return bs ``` -::: details Applicative approach - -```haskell +```haskell [Applicative] wall :: Parser Block wall = char '#' *> pure W @@ -508,7 +512,9 @@ Just ([F,F,W,W,W,F,W,F],"# #\n") ``` A maze is just a (possibly empty) sequence of rows. The input starts with the start and goal definitions, followed by a maze. -```haskell +::: details Solution: `file` +::: code-group +```haskell [do-notation] mapP :: Parser Maze mapP = do rs <- many row return (M rs) @@ -520,8 +526,7 @@ file = do p <- def "start" return (p,q,m) ``` -::: details Functor and Applicative approach -```haskell +```haskell [Functor & Applicative] mapP :: Parser Maze mapP = M <$> many row diff --git a/labs/lab13.md b/labs/lab13.md index ff99e9e..1757450 100644 --- a/labs/lab13.md +++ b/labs/lab13.md @@ -1,6 +1,8 @@ # Lab 13: State Monad -This lab is focused on the state monad `State`. In the lecture, I show you how it is implemented. In this lab, we are going to use the implementation from the lecture [State.hs](https://drive.google.com/file/d/10lPsFg__39AQBT5schwiqI9FHKxpPXDY/view?usp=sharing). So include the following lines in your source file: +This lab is focused on the state monad `State`. In the lecture, I show you how it is implemented. In +this lab, we are going to use the implementation from the lecture [`State.hs`](/code/State.hs). So +include the following lines in your source file: ```haskell import Control.Monad @@ -13,25 +15,28 @@ The third import is important as we are going to work with pseudorandom numbers. ::: tip Hint If `import System.Random` doesn't work for you, you need to install the package `random` as follows: - - either locally into the current directory by ```bashcabal install --lib random --package-env .``` - - or globally by ```bashcabal install --lib random``` + - either locally into the current directory by `cabal install --lib random --package-env .` + - or globally by `cabal install --lib random` -If you don't have `cabal` (e.g. computers in labs), put the file [Random.hs](https://drive.google.com/file/d/11C-EC-5uhlBCTHlPF-tVdHT_aY2xauSM/view?usp=sharing) into the directory containing your Lab-13 code and replace `import System.Ramdom` with `import Random`. +If you don't have `cabal` (e.g. computers in labs), put the file [`Random.hs`](/code/Random.hs) into +the directory containing your Lab-13 code and replace `import System.Ramdom` with `import Random`. ::: -The state monad `State s a` is a type constructor taking two parameters `s` and `a` representing type of states and output, respectively. -You can imagine this type as +The state monad `State s a` is a type constructor taking two parameters `s` and `a` representing +type of states and output, respectively. You can imagine this type as ```haskell newtype State s a = State { runState :: s -> (a, s) } ``` -The unary type constructor `State s` is an instance of `Monad`. -The values enclosed in this monadic context are functions taking a state and returning a pair whose first component is an output value and the second one is a new state. Using the bind operator, we can compose such functions in order to create more complex stateful computations out of -simpler ones. -The function `runState :: State s a -> s -> (a, s)` is the accessor function extracting the actual function from -the value of type `State s a`. +The unary type constructor `State s` is an instance of `Monad`. The values enclosed in this monadic +context are functions taking a state and returning a pair whose first component is an output value +and the second one is a new state. Using the bind operator, we can compose such functions in order +to create more complex stateful computations out of simpler ones. The function `runState :: State s +a -> s -> (a, s)` is the accessor function extracting the actual function from the value of type +`State s a`. -As `State s` is a monad, we can use all generic functions working with monads, including the do-notation. Apart from that, -the implementation of the state monad comes with several functions allowing to handle the state. +As `State s` is a monad, we can use all generic functions working with monads, including the +do-notation. Apart from that, the implementation of the state monad comes with several functions +allowing to handle the state. ```haskell get :: State s s -- outputs the state but doesn't change it @@ -106,7 +111,7 @@ reverseS = mapM_ (modify . (:)) ::: ## Task 1 - Suppose you are given a list of elements. Your task is to return a list of all its pairwise different elements together with the number +Suppose you are given a list of elements. Your task is to return a list of all its pairwise different elements together with the number of their occurrences. E.g. for `"abcaacbb"` it should return `[('a',3),('b',3),('c',2)]` in any order. A typical imperative approach to this problem is to keep a map from elements to their number of occurrences in memory (as a state). This state is updated as we iterate through the list. With the state monad, we can implement this approach in Haskell. First, we need a data structure representing a map from elements to their numbers of occurrences. We can simply represent it as a list of pairs @@ -118,15 +123,20 @@ type Map a b = [(a,b)] type Freq a = State (Map a Int) () ``` -::: tip Hint - First implement the following pure function taking an element `x` and a map `m` and returning an updated map. If the element -`x` is already in `m` (i.e., there is a pair `(x,n)`), then return the updated map, which is the same as `m` except the pair `(x,n)` -is replaced by `(x,n+1)`. If `x` is not in `m`, return the map extending `m` by `(x,1)`. To check that `x` is in `m`, -use the function `lookup :: Eq a => a -> [(a, b)] -> Maybe b` that returns `Nothing` if `x` is not in `m` and otherwise -`Just n` where `n` is the number of occurrences of `x`. -::: +First implement a pure function `update` taking an element `x` and a map `m` and returning an updated map. +```haskell +update :: Eq a => a -> Map a Int -> Map a Int +``` +If the element `x` is already in `m` (i.e., there is a pair `(x,n)`), then return the updated map, +which is the same as `m` except the pair `(x,n)` is replaced by `(x,n+1)`. If `x` is not in `m`, +return the map extending `m` by `(x,1)`. To check that `x` is in `m`, use the function +```haskell +lookup :: Eq a => a -> [(a, b)] -> Maybe b +``` +that returns `Nothing` if `x` is not in `m` and otherwise `Just n` +where `n` is the number of occurrences of `x`. -::: details Solution: `update` +::: details Solution: `update` (just copy this if you want to practice state monads only) ```haskell update :: Eq a => a -> Map a Int -> Map a Int update x m = case lookup x m of @@ -144,19 +154,24 @@ that computes the map of occurrences once executed. E.g. ``` ::: details Solution: `freqS` -Via `mapM_`: -```haskell + +::: code-group +```haskell [mapM_] freqS :: Eq a => [a] -> State (Map a Int) () freqS = mapM_ (modify . update) ``` -Or with do-notation: -```haskell +```haskell [modify] +freqS [] = return () +freqS (x:xs) = do modify (update x) + freqS xs +``` + +```haskell [get/put] freqS [] = return () freqS (x:xs) = do m <- get let m' = update x m put m' - --modify (update x) -- or replace the first 3 lines with this freqS xs ``` ::: diff --git a/public/code/Random.hs b/public/code/Random.hs new file mode 100644 index 0000000..8c3eae5 --- /dev/null +++ b/public/code/Random.hs @@ -0,0 +1,38 @@ +{-# LANGUAGE NamedFieldPuns, RecordWildCards #-} + +module Random ( + StdGen, + mkStdGen, + Random (..) +) where + +newtype StdGen = Gen { gen :: Integer } deriving Show + +mkStdGen :: Int -> StdGen +mkStdGen = Gen . fromIntegral + +class Random a where + randomR :: (a,a) -> StdGen -> (a, StdGen) + +-- randR :: Integral a => (a,a) -> StdGen -> (a, StdGen) +randR :: (Integer,Integer) -> StdGen -> (Integer, StdGen) +randR (l,u) g@Gen{ gen } | l > u = randR (u,l) g + | u == l = (l, g) + | otherwise = (n, Gen{ gen=gen' }) + where gen' = (6364136223846793005 * gen + 1442695040888963407) `mod` (2^64-1) + n = l + (fromIntegral (gen' `div` 2^16) `mod` (u-l+1)) + +instance Random Int where + randomR (l,u) g = (fromIntegral n, g') + where (n, g') = randR (fromIntegral l, fromIntegral u) g + +instance Random Integer where + randomR = randR + +instance Random Double where + randomR (l,u) g | u < l = randomR (u,l) g + | u == l = (l, g) + | otherwise = (r, g') + where mb = 2^32 :: Integer + (p, g') = randR (0, mb) g :: (Integer, StdGen) + r = l + (u-l)*(fromIntegral p / fromIntegral mb) \ No newline at end of file