Skip to content

Instantly share code, notes, and snippets.

@jkachmar
Last active March 17, 2020 15:55
Show Gist options
  • Save jkachmar/8db239bdc634b12f2d4e693c0cbafd3c to your computer and use it in GitHub Desktop.
Save jkachmar/8db239bdc634b12f2d4e693c0cbafd3c to your computer and use it in GitHub Desktop.
Just Do the Right Thing

Just do the Right Thing

Maybe

Definition

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 Nothings are encountered.

If, however, the function attempts to bind a Nothing to a variable name, it will short-circuit and immediately return Nothing.

Maybe Example 1

The following function "succeeds" by returning the sum of the value bound out from the Just and 21, using the return function to wrap the result in Just.

maybeEx1 :: Maybe Integer
maybeEx1 = do
  thing1 <- Just 21
  let thing2 = 21
  return (thing1 + thing2)

Maybe Example 2

The following function "fails" by attempting to bind out the Nothing, thereby short-circuiting and returning a Nothing.

maybeEx2 :: Maybe Integer
maybeEx2 = do
  thing1 <- Nothing
  let thing2 = 21
  return (thing1 + thing2)

Maybe Example 3

The following function attempts to bind out the value contained within an argument with the type Maybe Integer, and add it to 21.

This function will return a Nothing if the argument is a Nothing or, if the argument is a Just number, it will return the sum of the number added to 21.

Note that, since we take Maybe Integer as an argument, we can't tell if this function will "succeed" (i.e. return a Just number) or fail (i.e. return a Nothing).

It's impossible to know what happens until we "unwrap" the Maybe and see what's inside of it.

maybeEx3 :: Maybe Integer -> Maybe Integer
maybeEx3 maybeNumber = do
  thing1 <- maybeNumber
  let thing2 = 21
  return (thing1 + thing2)

Either

Definition

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 Rights and values indicating "failure" wrapped in Lefts.

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 Lefts 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.

Either Example 1

The following function "succeeds" by returning the sum of the value bound out from the Right 21 and 21, using the return function to wrap the result in Right.

eitherStringEx1 :: Either String Integer
eitherStringEx1 = do
  thing1 <- Right 21
  let thing2 = 21
  return (thing1 + thing2)

Either Example 2

The following function "fails" by attempting to bind out the Left, thereby short-circuiting and returning the first Left that we attempted to bind.

eitherStringEx2 :: Either String Integer
eitherStringEx2 = do
  thing1 <- Left "Oops, no numbers here!"
  let thing2 = 21
  return (thing1 + thing2)

Either Example 3

The following function attempts to bind out the value contained within an argument with the type Either String Integer, and add it to 21.

This function will return a Left String if the argument is a Left that contains an error message of type String or, if the argument is a Right number, it will return the sum of the number and 21.

Note that, as before with Maybe Integer, since we take Either String Integer as an argument, we can't tell if this function will "succeed" (i.e. return a Right number) or fail (i.e. return a Left error).

It is, again, impossible to know what will happen until we "unwrap" the Either and see what's inside of it.

eitherStringEx3 :: Either String Integer -> Either String Integer
eitherStringEx3 eitherNumber = do
  thing1 <- eitherNumber
  let thing2 = 21
  return (thing1 + thing2)
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!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment