Skip to content

Commit 1876aa6

Browse files
marermilesfrain
andauthored
Ch11 added tests and solutions (#226)
* Ch11 added tests and solutions * Cleanup testParens Some trailing whitespace snuck in Co-authored-by: milesfrain <[email protected]>
1 parent 80dc0e0 commit 1876aa6

File tree

4 files changed

+219
-4
lines changed

4 files changed

+219
-4
lines changed

exercises/chapter11/spago.dhall

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ You can edit this file as you like.
1010
, "ordered-collections"
1111
, "psci-support"
1212
, "strings"
13+
, "test-unit"
1314
, "transformers"
1415
, "yargs"
1516
]

exercises/chapter11/test/Main.purs

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,105 @@ module Test.Main where
22

33
import Prelude
44

5+
import Test.MySolutions
6+
import Test.NoPeeking.Solutions -- Note to reader: Delete this line
7+
58
import Effect (Effect)
6-
import Effect.Class.Console (log)
9+
import Control.Monad.Writer (runWriterT, execWriter)
10+
import Control.Monad.Except (runExceptT)
11+
import Control.Monad.State (runStateT)
12+
import Data.Either (Either(..))
13+
import Data.Monoid.Additive (Additive(..))
14+
import Data.Newtype (unwrap)
15+
import Data.Tuple (Tuple(..))
16+
import Test.Unit (TestSuite, success, suite, test)
17+
import Test.Unit.Assert as Assert
18+
import Test.Unit.Main (runTest)
719

820
main :: Effect Unit
9-
main = do
10-
log "🍝"
11-
log "You should add some tests."
21+
main =
22+
runTest do
23+
test "" success
24+
{- Move this block comment starting point to enable more tests
25+
Note to reader: Delete this line to expand comment block -}
26+
suite "Exercises Group - The State Monad" do
27+
suite "testParens" do
28+
let
29+
runTestParens :: Boolean -> String -> TestSuite
30+
runTestParens expected str =
31+
test testName do
32+
Assert.equal expected $ testParens str
33+
where testName = "str = \"" <> str <> "\""
34+
runTestParens true ""
35+
runTestParens true "(()(())())"
36+
runTestParens true "(hello)"
37+
runTestParens false ")"
38+
runTestParens false "(()()"
39+
runTestParens false ")("
40+
suite "Exercises Group - The Reader Monad" do
41+
suite "indents" do
42+
let
43+
expectedText =
44+
"Here is some indented text:\n\
45+
\ I am indented\n\
46+
\ So am I\n\
47+
\ I am even more indented"
48+
test "should render with indentations" do
49+
Assert.equal expectedText
50+
$ render $ cat
51+
[ line "Here is some indented text:"
52+
, indent $ cat
53+
[ line "I am indented"
54+
, line "So am I"
55+
, indent $ line "I am even more indented"
56+
]
57+
]
58+
suite "Exercises Group - The Writer Monad" do
59+
suite "sumArrayWriter" do
60+
test "should sum arrays" do
61+
Assert.equal (Additive 21)
62+
$ execWriter $ do
63+
sumArrayWriter [1, 2, 3]
64+
sumArrayWriter [4, 5]
65+
sumArrayWriter [6]
66+
suite "collatz" do
67+
let
68+
expected_11 =
69+
Tuple 14 [11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
70+
expected_15 =
71+
Tuple 17 [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]
72+
test "c = 11" do
73+
Assert.equal expected_11
74+
$ collatz 11
75+
test "c = 15" do
76+
Assert.equal expected_15
77+
$ collatz 15
78+
suite "Exercises Group - Monad Transformers" do
79+
suite "parser" do
80+
let
81+
runParser p s = unwrap $ runExceptT $ runWriterT $ runStateT p s
82+
test "should parse a string" do
83+
Assert.equal (Right (Tuple (Tuple "abc" "def") ["The state is abcdef"]))
84+
$ runParser (string "abc") "abcdef"
85+
test "should fail if string could not be parsed" do
86+
Assert.equal (Left ["Could not parse"])
87+
$ runParser (string "abc") "foobar"
88+
suite "indents with ReaderT and WriterT" do
89+
let
90+
expectedText =
91+
"Here is some indented text:\n\
92+
\ I am indented\n\
93+
\ So am I\n\
94+
\ I am even more indented"
95+
test "should render with indentations" do
96+
Assert.equal expectedText
97+
$ render' $ do
98+
line' "Here is some indented text:"
99+
indent' $ do
100+
line' "I am indented"
101+
line' "So am I"
102+
indent' $ do
103+
line' "I am even more indented"
104+
105+
{- Note to reader: Delete this line to expand comment block
106+
-}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Test.MySolutions where
2+
3+
import Prelude
4+
5+
-- Note to reader : Add your solutions to this file
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
module Test.NoPeeking.Solutions where
2+
3+
import Prelude
4+
5+
import Control.Monad.Except (ExceptT, throwError)
6+
import Control.Monad.Reader (Reader, ReaderT, ask, lift, local, runReader, runReaderT)
7+
import Control.Monad.State (State, StateT, get, put, execState, modify_)
8+
import Control.Monad.Writer (Writer, WriterT, tell, runWriter, execWriterT)
9+
import Data.Identity (Identity)
10+
import Data.Maybe (Maybe(..))
11+
import Data.Monoid (power)
12+
import Data.Monoid.Additive (Additive(..))
13+
import Data.Newtype (unwrap)
14+
import Data.String (joinWith)
15+
import Data.String.CodeUnits (stripPrefix, toCharArray)
16+
import Data.String.Pattern (Pattern(..))
17+
import Data.Traversable (sequence, traverse_)
18+
import Data.Tuple (Tuple)
19+
20+
--
21+
22+
testParens :: String -> Boolean
23+
testParens str =
24+
let
25+
openTally :: Char -> Int -> Int
26+
-- Open parens only considered if not already in deficit.
27+
-- No recovery from too-many closed parens.
28+
openTally '(' tally | tally >= 0 = tally + 1
29+
openTally ')' tally = tally - 1
30+
-- Non-parens has no effect
31+
openTally _ tally = tally
32+
33+
sumParens :: Array Char -> State Int Unit
34+
sumParens = traverse_ \c -> modify_ $ openTally c
35+
36+
finalTally :: Int
37+
finalTally = execState (sumParens $ toCharArray str) 0
38+
in
39+
finalTally == 0
40+
41+
--
42+
43+
type Level = Int
44+
type Doc = (Reader Level) String
45+
46+
line :: String -> Doc
47+
line str = do
48+
level <- ask
49+
pure $ (power " " level) <> str
50+
51+
indent :: Doc -> Doc
52+
indent = local $ (+) 1
53+
54+
cat :: Array Doc -> Doc
55+
cat = sequence >=> joinWith "\n" >>> pure
56+
57+
render :: Doc -> String
58+
render doc = runReader doc 0
59+
60+
--
61+
62+
sumArrayWriter :: Array Int -> Writer (Additive Int) Unit
63+
sumArrayWriter = traverse_ \n -> do
64+
tell $ Additive n
65+
pure unit
66+
67+
--
68+
69+
collatz :: Int -> Tuple Int (Array Int)
70+
collatz c = runWriter $ cltz 0 c
71+
where
72+
cltz :: Int -> Int -> Writer (Array Int) Int
73+
cltz i 1 = do
74+
tell [ 1 ]
75+
pure i
76+
cltz i n = do
77+
tell [ n ]
78+
if mod n 2 == 0
79+
then cltz (i + 1) (n / 2)
80+
else cltz (i + 1) ((3 * n) + 1)
81+
82+
--
83+
84+
type Errors = Array String
85+
type Log = Array String
86+
type Parser = StateT String (WriterT Log (ExceptT Errors Identity))
87+
88+
string :: String -> Parser String
89+
string prefix = do
90+
st <- get
91+
lift $ tell ["The state is " <> st]
92+
case stripPrefix (Pattern prefix) st of
93+
Just rest -> do
94+
put rest
95+
pure prefix
96+
_ -> do
97+
lift $ lift $ throwError ["Could not parse"]
98+
99+
--
100+
101+
type Level' = Int
102+
type Doc' = (WriterT (Array String) (ReaderT Level' Identity)) Unit
103+
104+
line' :: String -> Doc'
105+
line' s = do
106+
level <- lift $ ask
107+
tell [ (power " " level) <> s ]
108+
pure unit
109+
110+
indent' :: Doc' -> Doc'
111+
indent' = local $ (+) 1
112+
113+
render' :: Doc' -> String
114+
render' doct = joinWith "\n" $ unwrap $ runReaderT (execWriterT doct) 0

0 commit comments

Comments
 (0)