-
-
Save fedesilva/3cd3fecbd05dc21b2dc784f4c6c02db7 to your computer and use it in GitHub Desktop.
Just Do the Right Thing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 e a -> Maybe a | |
hush = either (const Nothing) Just | |
-- | Annotate the `Nothing` branch of a `Maybe`, raising it to an `Either`. | |
note :: e -> Maybe a -> Either e a | |
note e = maybe (Left e) Right | |
-- | 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!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment