forked from moocfi/haskell-mooc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSet15.hs
440 lines (392 loc) · 16.4 KB
/
Set15.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
module Set15 where
import Mooc.Todo
import Examples.Validation
import Control.Applicative
import Data.Char
import Text.Read (readMaybe)
------------------------------------------------------------------------------
-- Ex 1: Sum two Maybe Int values using Applicative operations (i.e.
-- liftA2 and pure). Don't use pattern matching.
--
-- Examples:
-- sumTwoMaybes (Just 1) (Just 2) ==> Just 3
-- sumTwoMaybes (Just 1) Nothing ==> Nothing
-- sumTwoMaybes Nothing Nothing ==> Nothing
sumTwoMaybes :: Maybe Int -> Maybe Int -> Maybe Int
sumTwoMaybes = liftA2 (+)
------------------------------------------------------------------------------
-- Ex 2: Given two lists of words, xs and ys, generate all statements
-- of the form "x is [not] y". Use Applicative
-- operations like liftA2!
--
-- The order of the results doesn't matter.
--
-- Examples:
-- statements ["beauty"] ["suffering"]
-- ==> ["beauty is suffering","beauty is not suffering"]
-- statements ["beauty","code"] ["suffering","life"]
-- ==> ["beauty is suffering","beauty is life",
-- "beauty is not suffering","beauty is not life",
-- "code is suffering","code is life",
-- "code is not suffering","code is not life"]
statements :: [String] -> [String] -> [String]
statements xs ys = concatMap combine xs
where
combine x = [x ++ " is " ++ y | y <- ys] ++ [x ++ " is not " ++ y | y <- ys]
------------------------------------------------------------------------------
-- Ex 3: A simple calculator with error handling. Given an operation
-- (negate or double) and a number, as strings, compute the result.
-- Return Nothing for an unknown operation or invalid number.
--
-- Use Applicative operations, don't use pattern matching.
--
-- Hint: remember the function readMaybe
--
-- Examples:
-- calculator "negate" "3" ==> Just (-3)
-- calculator "double" "7" ==> Just 14
-- calculator "doubl" "7" ==> Nothing
-- calculator "double" "7x" ==> Nothing
calculator :: String -> String -> Maybe Int
calculator op num = case op of
"negate" -> fmap negate (readMaybe num)
"double" -> fmap (* 2) (readMaybe num)
_ -> Nothing
------------------------------------------------------------------------------
-- Ex 4: Safe division. Implement the function validateDiv that
-- divides two integers, but returns an error ("Division by zero!") if
-- the divisor is zero.
--
-- NB! The constructors of Validation are not exported, so you can't
-- pattern match on Validation, you must use the Applicative methods
-- and the invalid and check functions.
--
-- Examples:
-- validateDiv 6 2 ==> Ok 3
-- validateDiv 6 0 ==> Errors ["Division by zero!"]
-- validateDiv 0 3 ==> Ok 0
validateDiv :: Int -> Int -> Validation Int
validateDiv dividend divisor = if divisor == 0
then invalid "Division by zero!"
else pure (dividend `div` divisor)
------------------------------------------------------------------------------
-- Ex 5: Validating street addresses. A street address consists of a
-- street name, a street number, and a postcode.
--
-- Implement the function validateAddress which constructs an Address
-- value if the input is valid:
--
-- * Street length should be at most 20 characters
-- (if not, error "Invalid street name")
-- * Street number should only contain digits
-- (if not, error "Invalid street number")
-- * Postcode should be exactly five digits long
-- (if not, error "Invalid postcode")
--
-- Examples:
-- validateAddress "Haskell road" "35" "13337"
-- ==> Ok (Address "Haskell road" "35" "13337")
-- validateAddress "Haskell road" "35a" "13337"
-- ==> Errors ["Invalid street number"]
-- validateAddress "Haskell road" "35a" "1333"
-- ==> Errors ["Invalid street number","Invalid postcode"]
-- validateAddress "Haskeller's favourite road" "35a" "1333"
-- ==> Errors ["Invalid street name","Invalid street number","Invalid postcode"]
data Address = Address String String String
deriving (Show,Eq)
validateStreetName :: String -> Validation String
validateStreetName name =
if length name <= 20
then pure name
else invalid "Invalid street name"
validateStreetNumber :: String -> Validation String
validateStreetNumber number =
if all isDigit number
then pure number
else invalid "Invalid street number"
validatePostcode :: String -> Validation String
validatePostcode code =
if length code == 5 && all isDigit code
then pure code
else invalid "Invalid postcode"
validateAddress :: String -> String -> String -> Validation Address
validateAddress streetName streetNumber postCode = Address
<$> validateStreetName streetName
<*> validateStreetNumber streetNumber
<*> validatePostcode postCode
------------------------------------------------------------------------------
-- Ex 6: Given the names, ages and employment statuses of two
-- persons, wrapped in Applicatives, return a list of two Person
-- values, wrapped in an applicative.
--
-- Examples:
-- twoPersons (Just "Clarice") (Just 35) (Just True) (Just "Hannibal") (Just 50) (Just False)
-- ==> Just [Person "Clarice" 35 True,Person "Hannibal" 50 False]
-- twoPersons (Just "Clarice") (Just 35) (Just True) (Just "Hannibal") Nothing (Just False)
-- ==> Nothing
-- twoPersons ["Clarice"] [25,35] [True] ["Hannibal"] [50] [False]
-- ==> [[Person "Clarice" 25 True,Person "Hannibal" 50 False],
-- [Person "Clarice" 35 True,Person "Hannibal" 50 False]]
data Person = Person String Int Bool
deriving (Show, Eq)
twoPersons :: Applicative f =>
f String -> f Int -> f Bool -> f String -> f Int -> f Bool
-> f [Person]
twoPersons name1 age1 employed1 name2 age2 employed2 =
pure (\n1 a1 e1 n2 a2 e2 -> [Person n1 a1 e1, Person n2 a2 e2])
<*> name1 <*> age1 <*> employed1
<*> name2 <*> age2 <*> employed2
------------------------------------------------------------------------------
-- Ex 7: Validate a String that's either a Bool or an Int. The return
-- type of the function uses Either Bool Int to be able to represent
-- both cases. Use <|> to combine two validators and to produce two
-- errors if the value is not an Int or a Bool.
--
-- Hint: remember readMaybe
--
-- PS. The tests won't test special cases of Int literals like hexadecimal
-- (0x3a) or octal (0o14).
--
-- Examples:
-- boolOrInt "True" ==> Ok (Left True)
-- boolOrInt "13" ==> Ok (Right 13)
-- boolOrInt "13.2" ==> Errors ["Not a Bool","Not an Int"]
-- boolOrInt "Falseb" ==> Errors ["Not a Bool","Not an Int"]
validateBool :: String -> Validation Bool
validateBool str =
case readMaybe str of
Just boolValue
| boolValue == True || boolValue == False -> pure boolValue
_ -> invalid "Not a Bool"
validateInt :: String -> Validation Int
validateInt str =
case readMaybe str of
Just intValue -> pure intValue
_ -> invalid "Not an Int"
boolOrInt :: String -> Validation (Either Bool Int)
boolOrInt str =
Left <$> validateBool str <|> Right <$> validateInt str
------------------------------------------------------------------------------
-- Ex 8: Improved phone number validation. Implement the function
-- normalizePhone that, given a String:
--
-- * removes all spaces from the string
-- * checks that there are at most 10 remaining characters
-- * checks that all remaining characters are digits, and logs an
-- error for every nonvalid character
-- * returns the string, stripped of whitespace, if no errors
--
-- Examples:
-- normalizePhone "123 456 78" ==> Ok "12345678"
-- normalizePhone "123 4x6 78"
-- ==> Errors ["Invalid character: x"]
-- normalizePhone "123 4x6 7y"
-- ==> Errors ["Invalid character: x","Invalid character: y"]
-- normalizePhone "123 4x6 7y 999"
-- ==> Errors ["Too long","Invalid character: x","Invalid character: y"]
-- normalizePhone "123 456 78 999"
-- ==> Errors ["Too long"]
normalizePhone :: String -> Validation String
normalizePhone s = checkLength strippedS *> checkDigits strippedS *> pure strippedS
where
strippedS = filter (/= ' ') s
checkLength str = check (length str <= 10) "Too long" str
checkDigits str = traverse checkDigit str *> pure str
checkDigit c
| isDigit c = pure c
| otherwise = invalid ("Invalid character: " ++ [c])
------------------------------------------------------------------------------
-- Ex 9: Parsing expressions. The Expression type describes an
-- arithmetic expression that has an operator (+ or -) and two
-- arguments that can be either numbers or single-letter variables.
-- The operator and the arguments are always separated by spaces. Here
-- are some examples of expressions like this: 1 + 2, y + 7, z - w
--
-- Implement the function parseExpression that uses the Validation
-- applicative to convert strings like "y + 7" to Expression values
-- like Plus (Variable 'y') (Number 7).
--
-- The parser should produce the following errors:
-- * For operators other than + or -: "Unknown operator: %"
-- * For variables that aren't single letters: "Invalid variable: xy"
-- * For arguments that aren't numbers: "Invalid number: 1x" --
-- * For expressions that don't consist of three words:
-- "Invalid expression: 1 + 2 +"
-- "Invalid expression: 1 -"
--
-- Hint: The functions `words` and `isAlpha`
--
-- Hint: If you have problems with the ordering of errors, remember
-- that Validation collects errors left-to-right!
--
-- Examples:
-- parseExpression "1 + 2" ==> Ok (Plus (Number 1) (Number 2))
-- parseExpression "z - A" ==> Ok (Minus (Variable 'z') (Variable 'A'))
-- parseExpression "1 * 2" ==> Errors ["Unknown operator: *"]
-- parseExpression "1 + 2x"
-- ==> Errors ["Invalid number: 2x","Invalid variable: 2x"]
-- parseExpression ". % 2x"
-- ==> Errors ["Unknown operator: %",
-- "Invalid number: .","Invalid variable: .",
-- "Invalid number: 2x","Invalid variable: 2x"]
data Arg = Number Int | Variable Char
deriving (Show, Eq)
data Expression = Plus Arg Arg | Minus Arg Arg
deriving (Show, Eq)
parseExpression :: String -> Validation Expression
parseExpression s = case words s of
[a, op, b] -> makeExpression <$> parseOperator op <*> parseArg a <*> parseArg b
_ -> invalid $ "Invalid expression: " ++ s
where
parseArg :: String -> Validation Arg
parseArg [c] | isAlpha c = pure (Variable c)
parseArg str | all isDigit str, Just n <- readMaybe str = pure (Number n)
parseArg str = invalid ("Invalid number: " ++ str) *> invalid ("Invalid variable: " ++ str)
parseOperator :: String -> Validation (Arg -> Arg -> Expression)
parseOperator "+" = pure Plus
parseOperator "-" = pure Minus
parseOperator op = invalid $ "Unknown operator: " ++ op
makeExpression f x y = f x y
------------------------------------------------------------------------------
-- Ex 10: The Priced T type tracks a value of type T, and a price
-- (represented by an Int). Implement the Functor and Applicative
-- instances for Priced. They should work like this:
--
-- * Transforming a Priced value with fmap keeps the price the same
-- * pure should create a value with price 0
-- * liftA2 should sum the prices of the things to be combined
--
-- Examples:
-- fmap reverse (Priced 3 "abc")
-- ==> Priced 3 "cba"
-- liftA2 (*) (pure 2) (pure 3) :: Priced Int
-- ==> Priced 0 6
-- liftA2 (+) (Priced 1 3) (Priced 1 5)
-- ==> Priced 2 8
-- traverse (\x -> Priced (length x) x) ["abc","de","f"]
-- ==> Priced 6 ["abc","de","f"]
data Priced a = Priced Int a
deriving (Show, Eq)
instance Functor Priced where
fmap f (Priced p a) = Priced p (f a)
instance Applicative Priced where
pure a = Priced 0 a
liftA2 f (Priced p1 a1) (Priced p2 a2) = Priced (p1 + p2) (f a1 a2)
------------------------------------------------------------------------------
-- Ex 11: This and the next exercise will use a copy of the
-- Applicative type class called MyApplicative. MyApplicative lacks
-- the Functor requirement that Applicative has, and also the <*> type
-- class method. You'll get to implement them instead.
--
-- First you'll reimplement <*> using liftA2. In practical terms,
-- implement the operator <#> that works like <*>, using myPure and
-- myLiftA2.
--
-- As long as you get the types right, your implementation is pretty
-- much guaranteed to be correct.
--
-- Examples:
-- Just succ <#> Just 2 ==> Just 3
-- Nothing <#> Just 2 ==> Nothing
-- [(*2),(+1)] <#> [10,100] ==> [20,200,11,101]
class MyApplicative f where
myPure :: a -> f a
myLiftA2 :: (a -> b -> c) -> f a -> f b -> f c
-- Some instances for testing:
instance MyApplicative Maybe where
myPure = pure
myLiftA2 = liftA2
instance MyApplicative [] where
myPure = pure
myLiftA2 = liftA2
(<#>) :: MyApplicative f => f (a -> b) -> f a -> f b
f <#> x = myLiftA2 ($) f x
------------------------------------------------------------------------------
-- Ex 12: Reimplement fmap using liftA2 and pure. In practical terms,
-- implement the function myFmap below using the methods myPure and
-- myLiftA2 from the type class MyApplicative.
--
-- As long as you get the types right, your implementation is pretty
-- much guaranteed to be correct. However, this time there are a
-- couple of different possible implementations!
--
-- Examples:
-- myFmap negate (Just 1) ==> Just (-1)
-- myFmap negate Nothing ==> Nothing
-- myFmap negate [1,2,3] ==> [-1,-2,-3]
myFmap :: MyApplicative f => (a -> b) -> f a -> f b
myFmap f x = myLiftA2 (const f) (myPure ()) x
------------------------------------------------------------------------------
-- Ex 13: Given a function that returns an Alternative value, and a
-- list, try the function on all the elements in the list and produce
-- any successes.
--
-- Hint: traverse won't help you since it succeeds only if all the
-- calls succeed. You need to use <|>.
--
-- Examples:
--
-- The Maybe Applicative returns the first success:
-- tryAll (\x -> if x>0 then pure x else empty) [0,3,2] :: Maybe Int
-- ==> Just 3
-- tryAll (\x -> if x>0 then pure x else empty) [0,-1,0] :: Maybe Int
-- ==> Nothing
-- The list Applicative returns all successes:
-- tryAll (\x -> if x>0 then pure x else empty) [0,3,2] :: [Int]
-- ==> [3,2]
-- The Validation Applicative returns the first success or all errors:
-- tryAll (\x -> if x>0 then pure x else invalid "zero") [0,3,2]
-- ==> Ok 3
-- tryAll (\x -> if x>0 then pure x else invalid "zero") [0,0,0]
-- ==> Errors ["zero","zero","zero"]
tryAll :: Alternative f => (a -> f b) -> [a] -> f b
tryAll f = foldr (\x acc -> f x <|> acc) empty
------------------------------------------------------------------------------
-- Ex 14: Here's the type `Both` that expresses the composition of
-- functors. Here are some example values and types:
--
-- Both (Just [True]) :: Both Maybe [] Bool
-- Both [Just True, Nothing, Just False] :: Both [] Maybe Bool
-- Both [[True,False],[]] :: Both [] [] Bool
--
-- Both (Ok (Just "value")) :: Both Validation Maybe String
-- Both (Just (Errors ["wrong"])) :: Both Maybe Validation a
--
-- Implement a Functor instance for Both f g, given that f and g are
-- both Functors.
--
-- Examples:
-- fmap not (Both (Just [True])) ==> Both (Just [False])
-- fmap not (Both [Nothing]) ==> Both [Nothing]
-- fmap (+1) (Both [[1,2,3],[4,5]]) ==> Both [[2,3,4],[5,6]]
newtype Both f g a = Both (f (g a))
deriving Show
instance (Functor f, Functor g) => Functor (Both f g) where
fmap h (Both x) = Both (fmap (fmap h) x)
------------------------------------------------------------------------------
-- Ex 15: The composition of two Applicatives is also an Applicative!
-- Implement the instance Applicative (Both f g) (given that f and g
-- are already Applicatives).
--
-- Again, there's only one way to implement this that gets the types
-- right.
--
-- Examples:
-- pure 1 :: Both Maybe [] Int
-- ==> Both (Just [1])
-- liftA2 (+) (Both (Just [10,100])) (Both (Just [1,2]))
-- ==> Both (Just [11,12,101,102])
-- liftA2 (+) (Both (Just [10,100])) (Both Nothing)
-- ==> Both Nothing
-- liftA2 (&&) (Both (Just (invalid "err"))) (Both (Just (pure True)))
-- ==> Both (Just (Errors ["err"]))
-- liftA2 (&&) (Both (Just (invalid "err"))) (Both (Just (invalid "umm")))
-- ==> Both (Just (Errors ["err","umm"]))
-- liftA2 (+) (Both [pure 1, invalid "fail 1"])
-- (Both [pure 10, pure 100, invalid "fail 2"])
-- ==> Both [Ok 11,Ok 101,Errors ["fail 2"],
-- Errors ["fail 1"],Errors ["fail 1"],
-- Errors ["fail 1","fail 2"]]
instance (Applicative f, Applicative g) => Applicative (Both f g) where
pure a = Both (pure (pure a))
liftA2 h (Both x) (Both y) = Both (liftA2 (liftA2 h) x y)