Skip to content

Commit 5be5ba7

Browse files
author
Răzvan C. Rădulescu
authored
Reivew ch7, ch8 and ch10 text and code (#244)
* Reivew ch7 text and code * ch7 minor formatting * Match code ch8 to ch7 and add anchors * Ch8 text with anchors and minor re-formatting * Update .gitignore * Update and format ch10 code * Format and fix ch10 text
1 parent d8ed83d commit 5be5ba7

File tree

17 files changed

+279
-302
lines changed

17 files changed

+279
-302
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
deploy_key
2+
package-lock.json
3+
dist
4+
.cache
5+
/index.html
6+
/book

exercises/chapter10/src/Data/AddressBook.purs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
module Data.AddressBook where
22

33
import Prelude
4+
5+
-- ANCHOR: import
46
import Data.Argonaut (class DecodeJson, class EncodeJson)
5-
import Data.Argonaut.Encode.Generic.Rep (genericEncodeJson)
67
import Data.Argonaut.Decode.Generic.Rep (genericDecodeJson)
8+
import Data.Argonaut.Encode.Generic.Rep (genericEncodeJson)
79
import Data.Generic.Rep (class Generic)
10+
-- ANCHOR_END: import
11+
import Data.Generic.Rep.Show (genericShow)
812

913
type Address
1014
= { street :: String
@@ -21,19 +25,12 @@ data PhoneType
2125
| CellPhone
2226
| OtherPhone
2327

24-
instance showPhoneType :: Show PhoneType where
25-
show HomePhone = "HomePhone"
26-
show WorkPhone = "WorkPhone"
27-
show CellPhone = "CellPhone"
28-
show OtherPhone = "OtherPhone"
29-
30-
derive instance genericPhoneType :: Generic PhoneType _
31-
32-
instance encodeJsonPhoneType :: EncodeJson PhoneType where
33-
encodeJson = genericEncodeJson
34-
35-
instance decodeJsonPhoneType :: DecodeJson PhoneType where
36-
decodeJson = genericDecodeJson
28+
-- ANCHOR: PhoneType_generic
29+
derive instance genericPhoneType :: Generic PhoneType _
30+
instance encodeJsonPhoneType :: EncodeJson PhoneType where encodeJson = genericEncodeJson
31+
instance decodeJsonPhoneType :: DecodeJson PhoneType where decodeJson = genericDecodeJson
32+
-- ANCHOR_END: PhoneType_generic
33+
instance showPhoneType :: Show PhoneType where show = genericShow
3734

3835
type PhoneNumber
3936
= { "type" :: PhoneType
Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,62 @@
11
module Data.AddressBook.Validation where
22

33
import Prelude
4+
45
import Data.AddressBook (Address, Person, PhoneNumber, address, person, phoneNumber)
56
import Data.Either (Either(..))
67
import Data.String (length)
78
import Data.String.Regex (Regex, test, regex)
89
import Data.String.Regex.Flags (noFlags)
910
import Data.Traversable (traverse)
10-
import Data.Validation.Semigroup (V, unV, invalid)
11+
import Data.Validation.Semigroup (V, invalid, toEither)
1112
import Partial.Unsafe (unsafePartial)
1213

1314
type Errors
1415
= Array String
1516

16-
nonEmpty :: String -> String -> V Errors Unit
17-
nonEmpty field "" = invalid [ "Field '" <> field <> "' cannot be empty" ]
18-
19-
nonEmpty _ _ = pure unit
20-
21-
arrayNonEmpty :: forall a. String -> Array a -> V Errors Unit
22-
arrayNonEmpty field [] = invalid [ "Field '" <> field <> "' must contain at least one value" ]
17+
nonEmpty :: String -> String -> V Errors String
18+
nonEmpty field "" = invalid [ "Field '" <> field <> "' cannot be empty" ]
19+
nonEmpty _ value = pure value
2320

24-
arrayNonEmpty _ _ = pure unit
21+
validatePhoneNumbers :: String -> Array PhoneNumber -> V Errors (Array PhoneNumber)
22+
validatePhoneNumbers field [] =
23+
invalid [ "Field '" <> field <> "' must contain at least one value" ]
24+
validatePhoneNumbers _ phones =
25+
traverse validatePhoneNumber phones
2526

26-
lengthIs :: String -> Int -> String -> V Errors Unit
27-
lengthIs field len value
28-
| length value /= len = invalid [ "Field '" <> field <> "' must have length " <> show len ]
29-
30-
lengthIs _ _ _ = pure unit
27+
lengthIs :: String -> Int -> String -> V Errors String
28+
lengthIs field len value | length value /= len =
29+
invalid [ "Field '" <> field <> "' must have length " <> show len ]
30+
lengthIs _ _ value = pure value
3131

3232
phoneNumberRegex :: Regex
3333
phoneNumberRegex =
3434
unsafePartial case regex "^\\d{3}-\\d{3}-\\d{4}$" noFlags of
3535
Right r -> r
3636

37-
matches :: String -> Regex -> String -> V Errors Unit
38-
matches _ regex value
39-
| test regex value = pure unit
40-
41-
matches field _ _ = invalid [ "Field '" <> field <> "' did not match the required format" ]
37+
matches :: String -> Regex -> String -> V Errors String
38+
matches _ regex value | test regex value =
39+
pure value
40+
matches field _ _ =
41+
invalid [ "Field '" <> field <> "' did not match the required format" ]
4242

4343
validateAddress :: Address -> V Errors Address
4444
validateAddress a =
45-
address <$> (nonEmpty "Street" a.street *> pure a.street)
46-
<*> (nonEmpty "City" a.city *> pure a.city)
47-
<*> (lengthIs "State" 2 a.state *> pure a.state)
45+
address <$> nonEmpty "Street" a.street
46+
<*> nonEmpty "City" a.city
47+
<*> lengthIs "State" 2 a.state
4848

4949
validatePhoneNumber :: PhoneNumber -> V Errors PhoneNumber
5050
validatePhoneNumber pn =
5151
phoneNumber <$> pure pn."type"
52-
<*> (matches "Number" phoneNumberRegex pn.number *> pure pn.number)
52+
<*> matches "Number" phoneNumberRegex pn.number
5353

5454
validatePerson :: Person -> V Errors Person
5555
validatePerson p =
56-
person <$> (nonEmpty "First Name" p.firstName *> pure p.firstName)
57-
<*> (nonEmpty "Last Name" p.lastName *> pure p.lastName)
58-
<*> validateAddress p.homeAddress
59-
<*> (arrayNonEmpty "Phone Numbers" p.phones *> traverse validatePhoneNumber p.phones)
56+
person <$> nonEmpty "First Name" p.firstName
57+
<*> nonEmpty "Last Name" p.lastName
58+
<*> validateAddress p.homeAddress
59+
<*> validatePhoneNumbers "Phone Numbers" p.phones
6060

6161
validatePerson' :: Person -> Either Errors Person
62-
validatePerson' p = unV Left Right $ validatePerson p
62+
validatePerson' p = toEither $ validatePerson p

exercises/chapter10/src/Main.purs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ mkAddressBookApp =
106106
validateAndSave = do
107107
log "Running validators"
108108
case validatePerson' person of
109-
Left errs -> alert $ "There are " <> show (length errs) <> " validation errors."
109+
Left errs -> alert $ "There are " <> show (length errs) <> " validation errors."
110110
Right validPerson -> do
111111
setItem "person" $ stringify $ encodeJson validPerson
112112
log "Saved"
@@ -157,9 +157,8 @@ mkAddressBookApp =
157157
processItem :: Json -> Either String Person
158158
processItem item = do
159159
jsonString <- lmap ("No string in local storage: " <> _) $ decodeJson item
160-
j <- lmap ("Cannot parse JSON string: " <> _) $ jsonParser jsonString
161-
(p :: Person) <- lmap ("Cannot decode Person: " <> _) $ decodeJson j
162-
pure p
160+
j <- lmap ("Cannot parse JSON string: " <> _) $ jsonParser jsonString
161+
lmap ("Cannot decode Person: " <> _) $ decodeJson j
163162

164163
main :: Effect Unit
165164
main = do
@@ -178,10 +177,10 @@ main = do
178177
-- Retrieve person from local storage
179178
item <- getItem "person"
180179
initialPerson <- case processItem item of
181-
Left err -> do
180+
Left err -> do
182181
alert $ "Error: " <> err <> ". Loading examplePerson"
183182
pure examplePerson
184-
Right p -> pure p
183+
Right p -> pure p
185184
let
186185
-- Create JSX node from react component.
187186
app = element addressBookApp { initialPerson }

exercises/chapter10/test/Examples.purs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Test.Examples where
22

33
import Prelude
4+
45
import Control.Promise (Promise, toAffE)
56
import Data.Argonaut (Json, decodeJson, encodeJson)
67
import Data.Either (Either)

exercises/chapter10/test/Main.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module Test.Main where
22

33
import Prelude
44
import Test.Examples
5-
import Test.Solutions
5+
import Test.MySolutions
66
import Control.Monad.Free (Free)
77
import Data.Argonaut (decodeJson, encodeJson)
88
import Data.Either (Either(..), isLeft)

exercises/chapter10/test/Solutions.purs renamed to exercises/chapter10/test/MySolutions.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Test.Solutions where
1+
module Test.MySolutions where
22

33
import Prelude
44
import Control.Alt (alt)

exercises/chapter7/src/Data/AddressBook/Validation.purs

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,32 @@ import Data.String (length)
77
import Data.String.Regex (Regex, test, regex)
88
import Data.String.Regex.Flags (noFlags)
99
import Data.Traversable (traverse)
10-
import Data.Validation.Semigroup (V, unV, invalid)
10+
import Data.Validation.Semigroup (V, invalid)
1111
import Partial.Unsafe (unsafePartial)
1212

1313
-----------------
1414
-- Some simple early examples returning `Either` instead of `V`:
1515

1616
-- ANCHOR: nonEmpty1
17-
nonEmpty1 :: String -> Either String Unit
18-
nonEmpty1 "" = Left "Field cannot be empty"
19-
nonEmpty1 _ = Right unit
17+
nonEmpty1 :: String -> Either String String
18+
nonEmpty1 "" = Left "Field cannot be empty"
19+
nonEmpty1 value = Right value
2020
-- ANCHOR_END: nonEmpty1
2121

2222
-- ANCHOR: validatePerson1
2323
validatePerson1 :: Person -> Either String Person
2424
validatePerson1 p =
25-
person <$> (nonEmpty1 p.firstName *> pure p.firstName)
26-
<*> (nonEmpty1 p.lastName *> pure p.lastName)
25+
person <$> nonEmpty1 p.firstName
26+
<*> nonEmpty1 p.lastName
2727
<*> pure p.homeAddress
2828
<*> pure p.phones
2929
-- ANCHOR_END: validatePerson1
3030

3131
-- ANCHOR: validatePerson1Ado
3232
validatePerson1Ado :: Person -> Either String Person
3333
validatePerson1Ado p = ado
34-
f <- nonEmpty1 p.firstName *> pure p.firstName
35-
l <- nonEmpty1 p.lastName *> pure p.firstName
34+
f <- nonEmpty1 p.firstName
35+
l <- nonEmpty1 p.lastName
3636
in person f l p.homeAddress p.phones
3737
-- ANCHOR_END: validatePerson1Ado
3838

@@ -44,24 +44,24 @@ type Errors
4444
-- ANCHOR_END: Errors
4545

4646
-- ANCHOR: nonEmpty
47-
nonEmpty :: String -> String -> V Errors Unit
48-
nonEmpty field "" = invalid [ "Field '" <> field <> "' cannot be empty" ]
49-
nonEmpty _ _ = pure unit
47+
nonEmpty :: String -> String -> V Errors String
48+
nonEmpty field "" = invalid [ "Field '" <> field <> "' cannot be empty" ]
49+
nonEmpty _ value = pure value
5050
-- ANCHOR_END: nonEmpty
5151

52-
-- ANCHOR: arrayNonEmpty
53-
arrayNonEmpty :: forall a. String -> Array a -> V Errors Unit
54-
arrayNonEmpty field [] =
52+
-- ANCHOR: validatePhoneNumbers
53+
validatePhoneNumbers :: String -> Array PhoneNumber -> V Errors (Array PhoneNumber)
54+
validatePhoneNumbers field [] =
5555
invalid [ "Field '" <> field <> "' must contain at least one value" ]
56-
arrayNonEmpty _ _ =
57-
pure unit
58-
-- ANCHOR_END: arrayNonEmpty
56+
validatePhoneNumbers _ phones =
57+
traverse validatePhoneNumber phones
58+
-- ANCHOR_END: validatePhoneNumbers
5959

6060
-- ANCHOR: lengthIs
61-
lengthIs :: String -> Int -> String -> V Errors Unit
61+
lengthIs :: String -> Int -> String -> V Errors String
6262
lengthIs field len value | length value /= len =
6363
invalid [ "Field '" <> field <> "' must have length " <> show len ]
64-
lengthIs _ _ _ = pure unit
64+
lengthIs _ _ value = pure value
6565
-- ANCHOR_END: lengthIs
6666

6767
-- ANCHOR: phoneNumberRegex
@@ -72,62 +72,60 @@ phoneNumberRegex =
7272
-- ANCHOR_END: phoneNumberRegex
7373

7474
-- ANCHOR: matches
75-
matches :: String -> Regex -> String -> V Errors Unit
75+
matches :: String -> Regex -> String -> V Errors String
7676
matches _ regex value | test regex value =
77-
pure unit
77+
pure value
7878
matches field _ _ =
7979
invalid [ "Field '" <> field <> "' did not match the required format" ]
8080
-- ANCHOR_END: matches
8181

8282
-- ANCHOR: validateAddress
8383
validateAddress :: Address -> V Errors Address
8484
validateAddress a =
85-
address <$> (nonEmpty "Street" a.street *> pure a.street)
86-
<*> (nonEmpty "City" a.city *> pure a.city)
87-
<*> (lengthIs "State" 2 a.state *> pure a.state)
85+
address <$> nonEmpty "Street" a.street
86+
<*> nonEmpty "City" a.city
87+
<*> lengthIs "State" 2 a.state
8888
-- ANCHOR_END: validateAddress
8989

9090
-- ANCHOR: validateAddressAdo
9191
validateAddressAdo :: Address -> V Errors Address
9292
validateAddressAdo a = ado
93-
street <- (nonEmpty "Street" a.street *> pure a.street)
94-
city <- (nonEmpty "City" a.city *> pure a.city)
95-
state <- (lengthIs "State" 2 a.state *> pure a.state)
93+
street <- nonEmpty "Street" a.street
94+
city <- nonEmpty "City" a.city
95+
state <- lengthIs "State" 2 a.state
9696
in address street city state
9797
-- ANCHOR_END: validateAddressAdo
9898

9999
-- ANCHOR: validatePhoneNumber
100100
validatePhoneNumber :: PhoneNumber -> V Errors PhoneNumber
101101
validatePhoneNumber pn =
102102
phoneNumber <$> pure pn."type"
103-
<*> (matches "Number" phoneNumberRegex pn.number *> pure pn.number)
103+
<*> matches "Number" phoneNumberRegex pn.number
104104
-- ANCHOR_END: validatePhoneNumber
105105

106106
-- ANCHOR: validatePhoneNumberAdo
107107
validatePhoneNumberAdo :: PhoneNumber -> V Errors PhoneNumber
108108
validatePhoneNumberAdo pn = ado
109109
tpe <- pure pn."type"
110-
number <- (matches "Number" phoneNumberRegex pn.number *> pure pn.number)
110+
number <- matches "Number" phoneNumberRegex pn.number
111111
in phoneNumber tpe number
112112
-- ANCHOR_END: validatePhoneNumberAdo
113113

114114
-- ANCHOR: validatePerson
115115
validatePerson :: Person -> V Errors Person
116116
validatePerson p =
117-
person <$> (nonEmpty "First Name" p.firstName *> pure p.firstName)
118-
<*> (nonEmpty "Last Name" p.lastName *> pure p.lastName)
117+
person <$> nonEmpty "First Name" p.firstName
118+
<*> nonEmpty "Last Name" p.lastName
119119
<*> validateAddress p.homeAddress
120-
<*> (arrayNonEmpty "Phone Numbers" p.phones *>
121-
traverse validatePhoneNumber p.phones)
120+
<*> validatePhoneNumbers "Phone Numbers" p.phones
122121
-- ANCHOR_END: validatePerson
123122

124123
-- ANCHOR: validatePersonAdo
125124
validatePersonAdo :: Person -> V Errors Person
126125
validatePersonAdo p = ado
127-
firstName <- (nonEmpty "First Name" p.firstName *> pure p.firstName)
128-
lastName <- (nonEmpty "Last Name" p.lastName *> pure p.lastName)
126+
firstName <- nonEmpty "First Name" p.firstName
127+
lastName <- nonEmpty "Last Name" p.lastName
129128
address <- validateAddress p.homeAddress
130-
numbers <- (arrayNonEmpty "Phone Numbers" p.phones *>
131-
traverse validatePhoneNumber p.phones)
129+
numbers <- validatePhoneNumbers "Phone Numbers" p.phones
132130
in person firstName lastName address numbers
133131
-- ANCHOR_END: validatePersonAdo

exercises/chapter7/test/no-peeking/Solutions.purs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module Test.NoPeeking.Solutions where
33
import Prelude
44
import Control.Apply (lift2)
55
import Data.AddressBook (Address, PhoneNumber, address)
6-
import Data.AddressBook.Validation (Errors, arrayNonEmpty, matches, nonEmpty, validateAddress, validatePhoneNumber)
6+
import Data.AddressBook.Validation (Errors, matches, nonEmpty, validateAddress, validatePhoneNumbers)
77
import Data.Either (Either(..))
88
import Data.Generic.Rep (class Generic)
99
import Data.Generic.Rep.Eq (genericEq)
@@ -147,10 +147,10 @@ personOptionalAddress firstName lastName homeAddress phones = { firstName, lastN
147147
validatePersonOptionalAddress :: PersonOptionalAddress -> V Errors PersonOptionalAddress
148148
validatePersonOptionalAddress p =
149149
personOptionalAddress
150-
<$> (nonEmpty "First Name" p.firstName *> pure p.firstName)
151-
<*> (nonEmpty "Last Name" p.lastName *> pure p.lastName)
152-
<*> (traverse validateAddress p.homeAddress *> pure p.homeAddress)
153-
<*> (arrayNonEmpty "Phone Numbers" p.phones *> traverse validatePhoneNumber p.phones)
150+
<$> nonEmpty "First Name" p.firstName
151+
<*> nonEmpty "Last Name" p.lastName
152+
<*> traverse validateAddress p.homeAddress
153+
<*> validatePhoneNumbers "Phone Numbers" p.phones
154154

155155
-- Exercise 6
156156
sequenceUsingTraverse :: forall a m t. Traversable t => Applicative m => t (m a) -> m (t a)

0 commit comments

Comments
 (0)