|
module Main where |
|
|
|
-------------------------------------------------------------------------------- |
|
-- | This is the `Maybe` data type: |
|
-- | |
|
-- | > data Maybe a |
|
-- | > = Nothing |
|
-- | > | Just a |
|
-- | |
|
-- | `Maybe` is a sum type that can be parameterized over a given `a` type, such |
|
-- | that values of type `Maybe a` can be one of `Nothing` or `Just a`. |
|
-- | |
|
-- | In Haskell, `Maybe` typically takes the place of `null` or `undefined` in |
|
-- | other languages. This lets us express that a function _may_ return a value |
|
-- | (or it may not), and forces us to deal with this indecision explicitly. |
|
-- | |
|
-- | Below, we'll use `Maybe`'s `Monad` instance (in the form of Haskell's |
|
-- | do-notation) to write imperative functions that operate over values which |
|
-- | may-or-may-not exist. |
|
-- | |
|
-- | Whenever the function binds a `Just` value to a variable name, it will |
|
-- | continue execution, ultimately wrapping the return value in a `Just` if no |
|
-- | `Nothing`s are encountered. |
|
-- | |
|
-- | If, however, the function attempts to bind a `Nothing` to a variable name, |
|
-- | it will short-circuit and immediately return `Nothing.` |
|
|
|
-- | "Succeed" by returning the sume of the value bound out from the `Just` and |
|
-- | a plain number, implicitly wrapping the result in the `Just` data type. |
|
maybeEx1 :: Maybe Integer |
|
maybeEx1 = do |
|
thing1 <- Just 21 |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | "Fail" by attempting to bind out the `Nothing`, thereby returning a |
|
-- | `Nothing`. |
|
maybeEx2 :: Maybe Integer |
|
maybeEx2 = do |
|
thing1 <- Nothing |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | Attempt to bind out the `Maybe Integer` argument and add it to a plain |
|
-- | number, returning either a `Nothing` (if the argument is a `Nothing`) or |
|
-- | the sum, implicitly wrapped in the `Just` constructor. |
|
maybeEx3 :: Maybe Integer -> Maybe Integer |
|
maybeEx3 maybeNumber = do |
|
thing1 <- maybeNumber |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-------------------------------------------------------------------------------- |
|
-- | This is the `Either` data type: |
|
-- | |
|
-- | > data Either e a |
|
-- | > = Left e |
|
-- | > | Right a |
|
-- | |
|
-- | `Either` is a sum type that can be parameterized over any `e` and `a` |
|
-- | types, such that values of type `Either e a` can be one of `Left e` or |
|
-- | `Right a`. |
|
-- | |
|
-- | `Either` is like a more descriptive `Maybe`, where typically values |
|
-- | indicating "success" are wrapped in `Right`s and values indicating |
|
-- | "failure" wrapped in `Left`s. |
|
-- | |
|
-- | Below, we'll use `Either`'s `Monad` instance (in the form of Haskell's |
|
-- | do-notation) to write imperative functions that operate over values which |
|
-- | may "succeed" (i.e. are `Right`) or "fail" (i.e. are `Left`). |
|
-- | |
|
-- | Whenever the function binds a `Right` value to a variable name, it will |
|
-- | continue execution, ultimately wrapping the return value in a `Right` if |
|
-- | no `Left`s are encountered. |
|
-- | |
|
-- | If, however, the function attempts to bind a `Left` value to a variable |
|
-- | name, it will short-circuit immediately and return that `Left` value. |
|
|
|
-- | "Succeed" by returning the the sum of the value bound out from the `Right` |
|
-- | and a plain number, implicitly wrapping the result in the `Right` |
|
-- | constructor. |
|
eitherStringEx1 :: Either String Integer |
|
eitherStringEx1 = do |
|
thing1 <- Right 21 |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | "Fail" by returning a `Left "Oops, no numbers here!"` value. |
|
eitherStringEx2 :: Either String Integer |
|
eitherStringEx2 = do |
|
thing1 <- Left "Oops, no numbers here!" |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | Attempt to bind out the `Either String Integer` argument and add it to a |
|
-- | plain number, returning either a `Left` value (if the argument is a |
|
-- | `Left`), or the sum, implicitly wrapped in the `Right` constructor. |
|
eitherStringEx3 :: Either String Integer -> Either String Integer |
|
eitherStringEx3 eitherNumber = do |
|
thing1 <- eitherNumber |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-------------------------------------------------------------------------------- |
|
-- | This is a `CustomError` sum type. |
|
-- | |
|
-- | Any value with the type `CustomError` can take one of the three forms |
|
-- | provided below: |
|
-- | |
|
-- | `AnError` - a type indicating an error. |
|
-- | `AnotherError` - a type indicating a different error. |
|
-- | `AnErrorWithContext String` - a type that wraps a `String`, letting us tag |
|
-- | values of this type with a descriptive message. |
|
data CustomError |
|
= AnError |
|
| AnotherError |
|
| AnErrorWithContext String |
|
deriving Show |
|
|
|
-- | "Succeed" by returning the sum of the value bound out from the `Right` |
|
-- | and a plain number, implicitly wrapped in the `Right` constructor. |
|
eitherCustomEx1 :: Either CustomError Integer |
|
eitherCustomEx1 = do |
|
thing1 <- Right 21 |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | "Fail" by returning a `Left AnError` value. |
|
eitherCustomEx2 :: Either CustomError Integer |
|
eitherCustomEx2 = do |
|
thing1 <- Left AnError |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | "Fail" by returning a `Left AnotherError` value. |
|
eitherCustomEx3 :: Either CustomError Integer |
|
eitherCustomEx3 = do |
|
thing1 <- Left AnotherError |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | "Fail" by returning a tagged `Left (AnErrorWithContext "Tag, you're it!")` |
|
-- | value. |
|
eitherCustomEx4 :: Either CustomError Integer |
|
eitherCustomEx4 = do |
|
thing1 <- Left (AnErrorWithContext "Tag, you're it!") |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | "Succeed" by returning a sum of both numbers bound out from the two |
|
-- | `Right`s, implicitly wrapped in the `Right` data type. |
|
eitherCustomEx5 :: Either CustomError Integer |
|
eitherCustomEx5 = do |
|
thing1 <- Right 21 |
|
thing2 <- Right 21 |
|
return (thing1 + thing2) |
|
|
|
-- | "Fail" by returning a `Left AnError` value. |
|
eitherCustomEx6 :: Either CustomError Integer |
|
eitherCustomEx6 = do |
|
thing1 <- Right 21 |
|
thing2 <- Left AnError |
|
return (thing1 + thing2) |
|
|
|
-- | Short-circuit the computation by returning the first `Left` value. |
|
eitherCustomEx7 :: Either CustomError Integer |
|
eitherCustomEx7 = do |
|
thing1 <- Left (AnErrorWithContext "Tag, you're it!") |
|
thing2 <- Left AnError |
|
return (thing1 + thing2) |
|
|
|
-------------------------------------------------------------------------------- |
|
-- | Drop the `Left` branch of an `Either`, demoting it to a `Maybe`. |
|
hush :: Either err val -> Maybe val |
|
hush eitherErrorOrValue = case eitherErrorOrValue of |
|
Left err -> Nothing |
|
Right val -> Just val |
|
|
|
-- | Annotate the `Nothing` branch of a `Maybe`, raising it to an `Either`. |
|
note :: err -> Maybe val -> Either err val |
|
note err maybeValue = case maybeValue of |
|
Nothing -> Left err |
|
Just val -> Right val |
|
|
|
-- | Tag a `Just 21` value with a `CustomError` message, turning it into |
|
-- | `Right 21`. |
|
maybeToEitherEx1 :: Either CustomError Integer |
|
maybeToEitherEx1 = do |
|
let err = AnErrorWithContext "I couldn't find a number in this Maybe!" |
|
thing1 <- note err (Just 21) |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | Tag a `Nothing` value with a `CustomError` message, turning it into |
|
-- | `Left CustomError`. |
|
maybeToEitherEx2 :: Either CustomError Integer |
|
maybeToEitherEx2 = do |
|
let err = AnErrorWithContext "I couldn't find a number in this Maybe!" |
|
thing1 <- note err Nothing |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | Drop the error message from a `Right 21` value, turning it into `Just 21`. |
|
eitherToMaybeEx1 :: Maybe Integer |
|
eitherToMaybeEx1 = do |
|
thing1 <- hush (Right 21) |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-- | Drop the error message from a `Left AnError` value, turning it into |
|
-- | `Nothing`. |
|
eitherToMaybeEx2 :: Maybe Integer |
|
eitherToMaybeEx2 = do |
|
thing1 <- hush (Left AnError) |
|
let thing2 = 21 |
|
return (thing1 + thing2) |
|
|
|
-------------------------------------------------------------------------------- |
|
-- | Run all of our examples. |
|
main :: IO () |
|
main = do |
|
------------------------------------------------------------------------------ |
|
putStrLn "Some examples with Maybe:" |
|
putStr "maybeEx1: " |
|
print maybeEx1 -- > Just 42 |
|
putStr "maybeEx2: " |
|
print maybeEx2 -- > Nothing |
|
putStr "maybeEx3: " |
|
print (maybeEx3 (Just 21)) -- Just 42 |
|
putStr "maybeEx3: " |
|
print (maybeEx3 Nothing) -- Nothing |
|
|
|
------------------------------------------------------------------------------ |
|
putStrLn "\nSome examples with Either and a String error message:" |
|
putStr "eitherStringEx1: " |
|
print eitherStringEx1 -- > Right 42 |
|
putStr "eitherStringEx2: " |
|
print eitherStringEx2 -- > Left "Oops, no numbers here!" |
|
putStr "eitherStringEx3: " |
|
print (eitherStringEx3 (Right 21)) -- Right 42 |
|
putStr "eitherStringEx3: " |
|
print (eitherStringEx3 (Left "Oops, no number here!")) -- Left "Oops, no numbers here!" |
|
|
|
------------------------------------------------------------------------------ |
|
putStrLn "\nSome examples with Either and a custom datatype error message:" |
|
putStr "eitherCustomEx1: " |
|
print eitherCustomEx1 -- Right 42 |
|
putStr "eitherCustomEx2: " |
|
print eitherCustomEx2 -- Left AnError |
|
putStr "eitherCustomEx3: " |
|
print eitherCustomEx3 -- Left AnotherError |
|
putStr "eitherCustomEx4: " |
|
print eitherCustomEx4 -- Left (AnErrorWithContext "Tag, you're it!") |
|
putStr "eitherCustomEx5: " |
|
print eitherCustomEx5 -- Right 42 |
|
putStr "eitherCustomEx6: " |
|
print eitherCustomEx6 -- Left AnError |
|
putStr "eitherCustomEx7: " |
|
print eitherCustomEx7 -- Left (AnErrorWithContext "Tag, you're it!") |
|
|
|
------------------------------------------------------------------------------ |
|
putStrLn "\nSome examples moving between Either and Maybe:" |
|
putStr "eitherToMaybeEx1: " |
|
print eitherToMaybeEx1 -- Just 42 |
|
putStr "eitherToMaybeEx2: " |
|
print eitherToMaybeEx2 -- Nothing |
|
putStr "maybeToEitherEx1: " |
|
print maybeToEitherEx1 -- Right 42 |
|
putStr "maybeToEitherEx2: " |
|
print maybeToEitherEx2 -- Left (AnErrorWithContext "I couldn't find a number in this Maybe!") |