diff --git a/haskell_102/codelab/Codelab.hs b/haskell_102/codelab/Codelab.hs index cd350a5..ef3648a 100644 --- a/haskell_102/codelab/Codelab.hs +++ b/haskell_102/codelab/Codelab.hs @@ -56,6 +56,7 @@ may the odds always be in your favor! {-# OPTIONS_GHC -fno-warn-unused-imports #-} {-# OPTIONS_GHC -fno-warn-unused-matches #-} {-# OPTIONS_GHC -fno-warn-unused-binds #-} +{-# OPTIONS_GHC -fno-warn-type-defaults #-} module Codelab where @@ -341,6 +342,96 @@ countScore c1 c2 = codelab +{- ##################################################################### + SECTION 5: "do" notation + + While "do" notation is commonly used for "imperative" code, it can + be used with anything that is a monad (or even an applicative). As + we have seen in the slides, lists are monads, meaning we can use + "do" notation to write our list generators in a very flexible way! + + For example, a generator like this: + + [x + 1 | x <- [1..10]] + + can be written using "do" notation like this: + + do x <- [1..10] + return $ x + 1 + + Code inside the "do" block must evaluate to a "wrapped" value, + which would be a list in our case. If you want to return just one + value, you can use the "return" function to wrap a single value to + produce a value in the context. + +-} + +-- [5.1] +-- Can you rewrite allColors to use the "do" notation? +-- +-- Note that inside "do" blocks Haskell switches to indentation +-- sensitive mode! It is like Python but better, as the rules are +-- more nuanced. Make sure you follow the indendation proposed in the +-- examples. + +allCodesDo :: Int -> [Code] +allCodesDo s + | s < 0 = error "allCodes: size was lower than 0" + | s == 0 = codelab + | otherwise = do color <- codelab + code <- codelab + return codelab + +-- [5.2] +-- Unlike generators, a "do" block can return any wrapped value. For +-- lists, it means it can return any list, not necessarily a list of +-- length 1. Let's build a "generator" using the "do" notation that +-- would produce a list of duplicates of the specified length. For +-- example, for length 5 it should produce +-- +-- [1, 1, 2, 2, 3, 3, 4, 4, 5, 5] + +duplicatesList :: Int -> [Int] +duplicatesList len = + do i <- [1..codelab] + codelab + +-- [5.3] +-- How about the case when the length of different "blocks" would be +-- different? Let's build a "generator" similar to the previous one, +-- but that would duplicate only odd values. For example, for length +-- 5 it should produce +-- +-- [1, 1, 2, 3, 3, 4, 5, 5] +-- +-- I this, this is not something that can be easily expressed with +-- ordinary generator, is it? +-- +-- Do not forget about Hoogle, should you need a new function. +-- +-- Hint: Should you use a conditional expression, make sure to put it +-- in one line, as we did not discuss correct indentation rules for +-- that case. + +oddlyDuplicateList :: Int -> [Int] +oddlyDuplicateList len = + do codelab + +-- When you solve [5.3], think about the fact that when coding in "do" +-- notation you have the full power of the language, but you are +-- building something like a generator. For imperative code that uses +-- IO, your generator is producing sequences of actions that your +-- application needs to execute to actually acheive the desired +-- result. +-- +-- This approach provides an amazing level of flexibility in terms of +-- abstraction. You can apply all the abstraction, code sharing and +-- other techniques to the code that produces imperatives actions, as +-- you normally apply to the code that is building lists. + + + + {- ##################################################################### THE END diff --git a/haskell_102/codelab/Solution.hs b/haskell_102/codelab/Solution.hs index 79a0105..b9bca27 100644 --- a/haskell_102/codelab/Solution.hs +++ b/haskell_102/codelab/Solution.hs @@ -341,6 +341,93 @@ countScore c1 c2 = Score black white +{- ##################################################################### + SECTION 5: "do" notation + + While "do" notation is commonly used for "imperative" code, it can + be used with anything that is a monad (or even applicative). As we + have seen in the lecture lists are monads, meaning we can use "do" + notation to write our list generators in a very flexible way! + + For example, a generator like this: + + [x + 1 | x <- [1..10]] + + can be written using "do" notation like this: + + do x <- [1..10] + x + 1 + +-} + +-- [5.1] +-- Can you rewrite allColors to use the "do" notation? +-- +-- Note that inside "do" blocks Haskell switches to indentation +-- sensitive mode! It is like Python but better, as the rules are +-- more nuanced. Make sure you follow the indendation proposed in the +-- examples. + +allCodesDo :: Int -> [Code] +allCodesDo s + | s < 0 = error "allCodes: size was lower than 0" + | s == 0 = [[]] + | otherwise = do color <- allColors + code <- allCodesDo (s - 1) + return $ color:code + +-- [5.2] +-- Unlike generators, a "do" block can return any wrapped value. For +-- lists, it means it can return any list, not necessarily a list of +-- length 1. Let's build a "generator" using the "do" notation that +-- would produce a list of duplicates of the specified length. For +-- example, for length 5 it should produce +-- +-- [1, 1, 2, 2, 3, 3, 4, 4, 5, 5] + +duplicatesList :: Int -> [Int] +duplicatesList len = + do i <- [1..len] + [i, i] + + +-- [5.3] +-- How about the case when the length of different "blocks" would be +-- different? Let's build a "generator" similar to the previous one, +-- but that would duplicate only odd values. For example, for length +-- 5 it should produce +-- +-- [1, 2, 2, 3, 4, 4, 5] +-- +-- I this, this is not something that can be easily expressed with +-- ordinary generator, is it? +-- +-- Hint: Should you use a conditional expression, make sure to put it +-- in one line, as we did not discuss correct indentation rules for +-- that case. + +oddlyDuplicateList :: Int -> [Int] +oddlyDuplicateList len = + do i <- [1..len] + if odd i + then [i, i] + else [i] + +-- When you solve [5.3], think about the fact that when coding in "do" +-- notation you have the full power of the language, but you are +-- building something like a generator. For imperative code that uses +-- IO, your generator is producing sequences of actions that your +-- application needs to execute to actually acheive the desired +-- result. +-- +-- This approach provides an amazing level of flexibility in terms of +-- abstraction. You can apply all the abstraction, code sharing and +-- other techniques to the code that produces imperatives actions, as +-- you normally apply to the code that is building lists. + + + + {- ##################################################################### THE END diff --git a/haskell_102/codelab/Tests.hs b/haskell_102/codelab/Tests.hs index 0d307c6..0f8446f 100644 --- a/haskell_102/codelab/Tests.hs +++ b/haskell_102/codelab/Tests.hs @@ -62,7 +62,7 @@ newtype Section = Section Int instance Bounded Section where minBound = Section 1 - maxBound = Section 4 + maxBound = Section 5 instance Ord Section where compare (Section s1) (Section s2) = compare s1 s2 @@ -241,6 +241,29 @@ t45_3 = test "[4.5] countScore [B,B,C,G] [B,B,C,G]" (Score 4 0) $ countScore [B +{- ##################################################################### + SECTION 5: "do" notation +-} + +t51_1 = test "[5.1] # codes of size 0: 1" 1 $ length $ allCodesDo 0 +t51_2 = test "[5.1] # codes of size 1: 6" 6 $ length $ allCodesDo 1 +t51_3 = test "[5.1] # codes of size 4: 1296" 1296 $ length $ allCodesDo 4 +t51_4 = test "[5.1] all codes 0 have size 0" [0] $ nub $ length <$> allCodesDo 0 +t51_5 = test "[5.1] all codes 1 have size 1" [1] $ nub $ length <$> allCodesDo 1 +t51_6 = test "[5.1] all codes 4 have size 4" [4] $ nub $ length <$> allCodesDo 4 +t51_7 = test "[5.1] no duplicated codes" True $ on (==) length (allCodesDo 4) (nub $ allCodesDo 4) + +t52_1 = test "[5.2] len: 0: []" [] $ duplicatesList 0 +t52_2 = test "[5.2] len: 3: [1, 1, 2, 2, 3, 3]" [1, 1, 2, 2, 3, 3] $ duplicatesList 3 + +t53_1 = test "[5.3] len: 0: []" [] $ oddlyDuplicateList 0 +t53_2 = test "[5.3] len: 3: [1, 1, 2, 3, 3]" [1, 1, 2, 3, 3] $ oddlyDuplicateList 3 +t53_3 = test "[5.3] len: 5: [1, 1, 2, 3, 3, 4, 5, 5]" [1, 1, 2, 3, 3, 4, 5, 5] $ oddlyDuplicateList 5 + + + + + {- ##################################################################### Main -} @@ -275,6 +298,12 @@ tests sections = , t44_1, t44_2, t44_3, t44_4, t44_5 , t45_1, t45_2, t45_3 ] + Section 5 -> + [ display "#### Section 5" + , t51_1, t51_2, t51_3, t51_4, t51_5, t51_6, t51_7 + , t52_1, t52_2 + , t53_1, t53_2, t53_3 + ] Section unexpected -> [ display $ "Unexpected section requested: " ++ show unexpected ] diff --git a/haskell_102/slides/haskell_102.tex b/haskell_102/slides/haskell_102.tex index 785a8b9..7df2e0b 100644 --- a/haskell_102/slides/haskell_102.tex +++ b/haskell_102/slides/haskell_102.tex @@ -1297,12 +1297,12 @@ \section{Monadic syntax} %% Codelab -\section{Codelab - Sections 3 and 4} +\section{Codelab - Sections 3, 4, and 5} \begin{frame} - \frametitle{Codelab - Sections 3 and 4} + \frametitle{Codelab - Sections 3, 4, and 5} \begin{itemize} - \item Codelab - Sections 3 and 4 + \item Codelab - Sections 3, 4, and 5 \end{itemize} \end{frame}