% Just Haskell or Nothing
% John Chee
% @chee1bot on Twitter | @cheecheeo on Github
Some libraries that we'll be using for this presentation
-
\begin{code}
module JustHaskellOrNothing where
import Control.Applicative
import Data.Maybe
import Data.Monoid
\end{code}
![Donald Knuth](./dek-14May10-2-resize.jpeg)
Outline
-
- Maybe in action
- Maybe itself
- Definition & usage
- Examples
- Some functions that construct Maybes
- Parsing North American phone numbers
- Destructing or eliminating values
- More tools to deal with Maybes
- Here, have a default
- Another way to think of Maybes
- Dealing with collections of Maybes
- Opening up the Typeclassopedia
- fmappable things AKA Functor instances
- Applicative instance
- liftAn
- Further Study
Maybe in action
=
Maybe can be used to represent nulled values
-
In Javascript:
< > 20 * 2 + 2 + null
< 42
In SQL:
< mysql> select 20 * 2 + 2 + null;
< +-------------------+
< | 20 * 2 + 2 + null |
< +-------------------+
< | NULL |
< +-------------------+
< 1 row in set (0.01 sec)
In Haskell:
< λ: fmap (20 * 2 + 2 +) Nothing
< Nothing
Maybe can be used to represent possibly-failed computations
-
Rather than:
< λ: head []
< *** Exception: Prelude.head: empty list
< λ: safeHead []
< Nothing
< λ: safeHead [1,2,3,4]
< Just 1
< λ: safeHead [42]
< Just 42
Or:
< λ: 12 `div` 0
< *** Exception: divide by zero
< λ: 12 `safeDiv` 0
< Nothing
< λ: 42 `safeDiv` 2
< Just 21
Maybe itself
=
Definition & usage
-
< data Maybe a = Nothing | Just a
- When you have a type `Maybe Foo` you know you have values like:
- Nothing
- Just f where f is a value with type `Foo`
Examples
-
- `Integer`s
- `Maybe Integer`s
- `Bool`s
- `Maybe Bool`s
. . .
< λ: [0..5]
< [0,1,2,3,4,5]
< λ: [Nothing] ++ map Just [0..5]
< [Nothing,Just 0,Just 1,Just 2,Just 3,Just 4,Just 5]
< λ: [True, False]
< [True,False]
< λ: [Nothing] ++ map Just [True, False]
< [Nothing,Just True,Just False]
Some functions that construct `Maybe`s
-
- Possibly-failed computations:
\begin{code}
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x : _) = Just x
\end{code}
\begin{code}
safeDiv :: Integer -> Integer -> Maybe Integer
safeDiv n d =
if d == 0
then Nothing
else Just (n `div` d)
\end{code}
Parsing North American phone numbers
-
In this case a phone number that isn't valid maps to Nothing.
\begin{code}
data PhoneNumber = PhoneNumber Integer Integer Integer
deriving (Show)
parsePhoneNumber :: Integer -> Maybe PhoneNumber
parsePhoneNumber n =
if n >= 2002000000 && n < 10000000000 -- the correct number of digits
then let (areaCode, m) = n `divMod` 10000000
(centralOfficeCode, subscriberNumber) = m `divMod` 10000
in if areaCode >= 200 && centralOfficeCode >= 200 && centralOfficeCode `mod` 100 /= 11
then Just (PhoneNumber areaCode centralOfficeCode subscriberNumber)
else Nothing
else Nothing
\end{code}
. . .
Examples:
< λ: parsePhoneNumber 1234567890
< Nothing
< λ: parsePhoneNumber 4155551234
< Just (PhoneNumber 415 555 1234)
< λ: parsePhoneNumber 4159112277
< Nothing
< λ: parsePhoneNumber 4159128347
< Just (PhoneNumber 415 912 8347)
Destructing or eliminating values
-
If we have a `Maybe` and we're ready to handle both the `Nothing` case and
the `Just` case we can:
pattern match:
\begin{code}
showMaybeInteger :: Maybe Integer -> String
showMaybeInteger m =
case m of
Nothing -> "There's nothing here."
Just x -> "I have something: " <> show x
\end{code}
. . .
< λ: showMaybeInteger Nothing
< "There's nothing here."
< λ: showMaybeInteger (Just 42)
< "I have something: 42"
or eliminate:
< maybe :: b -> (a -> b) -> Maybe a -> b
\begin{code}
showMaybeInteger' :: Maybe Integer -> String
showMaybeInteger' m =
maybe
"There's nothing here." -- Nothing
(\x -> "I have something: " <> show x) -- Just x
m
\end{code}
- I can think of some good reasons to prefer the `maybe` function can you? (audience involvement)
Reasons to prefer `maybe`
-
< maybe :: b -> (a -> b) -> Maybe a -> b
- always cover all cases (without the compiler's help!)
- information hiding
- rename `Just` to `Some` or `Nothing` to `None`
- This won't actually happen
![Eliminated](./bigstock-Eliminated-Red-Square-Grungy-S-66335674-583x388.jpg)
More tools to deal with `Maybe`s
=
Here, have a default
-
![Grexit](./IMFGoHome_3254366b.jpg)
- If you're concerned about having to construct and destruct `Maybe` values all over the place
- Don't worry
< fromMaybe :: a -> Maybe a -> a
If we want to do the Javascript thing and make `Nothing` be `0`
< λ: let (x, y) = (Nothing, Just 30000)
< λ: let jsNumbers = fromMaybe 0
< λ: 20 * 2 + 2 + jsNumbers x
< 42
< λ: jsNumbers y + 1337
< 31337
Another way to think of `Maybe`s
-
- A list with at most one element
< listToMaybe :: [a] -> Maybe a
< maybeToList :: Maybe a -> [a]
\begin{code}
safeHead' = listToMaybe
\end{code}
< λ: maybeToList Nothing
< []
< λ: maybeToList (Just "hello")
< ["hello"]
< λ: listToMaybe [1..10]
< Just 1
< λ: listToMaybe []
< Nothing
Dealing with collections of `Maybe`s
-
< catMaybes :: [Maybe a] -> [a]
< mapMaybe :: (a -> Maybe b) -> [a] -> [b]
< λ: catMaybes [Nothing,Just 1,Just 2,Nothing,Just 4,Just 5]
< [1,2,4,5]
< λ: length . mapMaybe parsePhoneNumber $ [4150000000..4159999999]
< 7920000
- With just these functions and `maybe` you can write a large number of useful programs
Opening up the Typeclassopedia
=
`fmap`pable things AKA `Functor` instances
-
- Just like we can `map` (or equivalently `fmap`) across lists
- We can `fmap` across `Maybe`s
< fmap :: (a -> b) -> Maybe a -> Maybe b
We can write our function without being concerned about `Maybe`:
\begin{code}
call :: PhoneNumber -> String
call (PhoneNumber areaCode centralOfficeCode subscriberNumber) =
"I called (" <> show areaCode <> ") "
<> show centralOfficeCode <> " " <> show subscriberNumber
<> " and had a great conversation!"
\end{code}
and we'll perform computations if we have a `Just` and otherwise get `Nothing`
< λ: fmap call (parsePhoneNumber 4155551234)
< Just "I called (415) 555 1234 and had a great conversation!"
< λ: fmap call (parsePhoneNumber 1234567890)
< Nothing
We can `text` too
-
- Let's make another type
\begin{code}
data Phone = Android | IPhone | Hipster
\end{code}
\begin{code}
text :: Phone -> String
text p =
case p of
Android -> "I love swiping when I text."
IPhone -> "I love to type character by character."
Hipster -> "Texting is so 2014."
\end{code}
We can use `Maybe Phone` to represent those rare times when you forget your cell phone.
< λ: fmap text (Just Android)
< Just "I love swiping when I text."
< λ: fmap text (Just Hipster)
< Just "Texting is so 2014."
< λ: fmap text Nothing
< Nothing
`Applicative` instance
-
- Let's make another function that uses both `Phone` and `PhoneNumber`
\begin{code}
callWithPhone :: Phone -> PhoneNumber -> String
callWithPhone phone (PhoneNumber areaCode centralOfficeCode subscriberNumber) =
let prettyPhoneNumber = "(" <> show areaCode <> ") " <> show centralOfficeCode <> " " <> show subscriberNumber
in case phone of
Android -> "Google knows I just called " <> prettyPhoneNumber <> "."
IPhone -> "I called " <> prettyPhoneNumber <> " on the best phone!"
Hipster -> "I called " <> prettyPhoneNumber <> " and asked for their Snapchat ID."
\end{code}
. . .
- `Maybe` is also an `Applicative` instance
- This means we can embed expressions in `Maybe`s using `Just`
- And combine `Maybe` computations using (<*>)
. . .
- We want to call someone but what if we have a `Maybe Phone` and a `Maybe PhoneNumber`?
- Don't be afraid of the syntax
< λ: (pure callWithPhone) <*> (pure Android) <*> (parsePhoneNumber 4155551234)
< Just "Google knows I just called (415) 555 1234."
< λ: (pure callWithPhone) <*> (pure IPhone) <*> (parsePhoneNumber 1234567890)
< Nothing
< λ: (pure callWithPhone) <*> (pure Hipster) <*> (parsePhoneNumber 5035551234)
< Just "I called (503) 555 1234 and asked for their Snapchat ID."
< λ: (pure callWithPhone) <*> Nothing <*> (parsePhoneNumber 1234567890)
< Nothing
Pop quiz
-
What about? (audience participation)
< λ: Nothing <*> Nothing <*> Nothing
. . .
![Mind blown](./mind_blown-resize.gif)
You don't even need a function for `Maybe`s `Applicative` instance
-
< λ: Nothing <*> Nothing <*> Nothing
< Nothing
![A show about nothing](./seinfeld-cast-resize.jpg)
`liftAn`
-
- If you can't get over the syntax (liftA, liftA2 and liftA3 are helpful)
- `liftA` is really just `fmap`
- `liftA2` can be thought of as `fmap` but for functions that take two arguments
< λ: liftA text (Just Android)
< Just "I love swiping when I text."
< λ: liftA2 callWithPhone (Just Android) (parsePhoneNumber 4155551234)
< Just "Google knows I just called (415) 555 1234."
< λ: liftA2 callWithPhone (Just IPhone) (parsePhoneNumber 1234567890)
< Nothing
< λ: liftA2 callWithPhone (Just Hipster) (parsePhoneNumber 5035551234)
< Just "I called (503) 555 1234 and asked for their Snapchat ID."
< λ: liftA2 callWithPhone Nothing (parsePhoneNumber 1234567890)
< Nothing
Further Study
-
- [Data.Maybe](http://hackage.haskell.org/package/base-4.8.1.0/docs/Data-Maybe.html) in `base`
- `Maybe` is an instance of `Monoid`, `Functor`, `Applicative`, `Monad`, `Foldable`, `Traversable`, `Alternative` and others.
- [witherable](http://hackage.haskell.org/package/witherable-0.1.3/docs/Data-Witherable.html#v:mapMaybe)
- `mapMaybe` and `catMaybes` can have more generic types that allow them to work with `Traversable` instances rather than just lists.
- [MaybeT](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-Trans-Maybe.html#t:MaybeT)
- Enrich any `Monad` with `Maybe` semantics
- [maybeT](http://hackage.haskell.org/package/errors-2.0.0/docs/Control-Error-Util.html#v:maybeT)
- provides an eliminator for `MaybeT` values
- [Either](http://hackage.haskell.org/package/base-4.8.1.0/docs/Data-Either.html#t:Either)
- Like `Maybe` but allows you to hold data where you would have `Nothing`
Questions or comments?
-
- Get the latest version of these slides: [https://cheecheeo.github.io/just_haskell_or_nothing.html](https://cheecheeo.github.io/just_haskell_or_nothing.html)
- Download the source code for these slides: [just_haskell_or_nothing.markdown.lhs](https://raw.githubusercontent.com/cheecheeo/presentations/master/just_haskell_or_nothing.markdown.lhs)