-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmaybe.hs
101 lines (84 loc) · 3.15 KB
/
maybe.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
data Person = Person {
name :: String
, age :: Int
, pl :: String
} deriving (Eq, Show)
nonEmpty :: String -> Maybe String
-- pattern match; yes, there are other ways to do it
nonEmpty "" = Nothing
nonEmpty str = Just str
nonNegative :: Int -> Maybe Int
nonNegative n = if n >= 0 then Just n else Nothing
plCheck :: Person -> Maybe Person
plCheck c =
if (name c) == "Simon" && ((pl c) /= "Haskell")
then Nothing
else Just c
-- `name` and `pl` here are those record accessor functions from our record type
mkPerson :: String
-> Int
-> String
-> Maybe Person
mkPerson name' age' pl' =
-- have the ' to distinguish from the record-accessor
-- functions in the record datatype
case nonEmpty name' of
Nothing -> Nothing
Just name'' ->
case nonNegative age' of
Nothing -> Nothing
Just age'' ->
case nonEmpty pl' of
Nothing -> Nothing
Just pl'' ->
plCheck (Person name'' age'' pl'')
-- note that if any of the above returns a `Nothing`, we will never make it to the `plCheck`
-- `do` syntax isn't just for IO;
-- Maybe is also a Monad
-- and later computations (specifically plCheck)
-- do depend on results of earlier ones
-- following is same as above, but ~monadic~
mkPerson' :: String -> Int -> String -> Maybe Person
mkPerson' name' age' pl' = do
name'' <- nonEmpty name'
age'' <- nonNegative age'
lang'' <- nonEmpty pl'
plCheck (Person name'' age'' lang'')
-- desugaring `do` with applicative and bind
-- first we construct a Person value by applying the Person type constructor
-- to the results of the three function/values and then pass that as the (m a) to (>>=)
-- where the `m` is Maybe, and the (a -> m b) is plCheck (:: Person -> Maybe Person)
mkPer' :: String -> Int -> String -> Maybe Person
mkPer' name' age' pl' =
-- Person <$> (nonEmpty name')
-- <*> (nonNegative age')
-- <*> (nonEmpty pl')
per name' age' pl' >>= plCheck
-- Maybe Person >>= (Person -> Maybe Person)
noEmpty :: String -> Either String String
noEmpty "" = Left "Empty string."
noEmpty str = Right str
noNegative :: Int -> Either String Int
noNegative n | n >= 0 = Right n
| otherwise = Left "Negative age."
-- plChk :: Person -> Either String Person
-- plChk c =
-- let p = pl c
-- n = name c
-- in if n == "Simon" && (p /= "Haskell")
-- then Left "All Simons write Haskell."
-- else Right c
Person <$> (nonEmpty name') -- <$> is infix operator for `fmap`
<*> (nonNegative age') -- <*> is the applicative operator :: f (a -> b) -> f a -> f b
<*> (nonEmpty pl')
>>= plCheck
-- >>= is called "bind" and is the main operator in Monad :: m a -> (a -> m b) -> m b
-- this is overkill but if we factor out the applicative
-- part, it's easier to see the intermediate type signature
per :: String -> Int -> String -> Maybe Person
per name' age' pl' = Person <$> (nonEmpty name')
<*> (nonNegative age')
<*> (nonEmpty pl')
mkP :: String -> Int -> String -> Maybe Person
mkP name' age' pl' =
per name' age' pl' >>= plCheck