From 3cdb45b6bbe5a44e364ac5b55a9b9f0a7f29c53c Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 1 Jan 2017 15:07:36 +0200 Subject: [PATCH 1/6] Raw import from SoH --- posts.yaml | 3 + posts/functors-applicatives-and-monads.md | 655 ++++++++++++++++++++++ 2 files changed, 658 insertions(+) create mode 100644 posts/functors-applicatives-and-monads.md diff --git a/posts.yaml b/posts.yaml index a54c907..be1748f 100644 --- a/posts.yaml +++ b/posts.yaml @@ -1,3 +1,6 @@ +- file: posts/functors-applicatives-and-monads.md + title: Functors, Applicatives, and Monads + day: 2017-01-04 - file: posts/beware-of-readfile.md title: Beware of readFile day: 2016-12-22 diff --git a/posts/functors-applicatives-and-monads.md b/posts/functors-applicatives-and-monads.md new file mode 100644 index 0000000..37fc66a --- /dev/null +++ b/posts/functors-applicatives-and-monads.md @@ -0,0 +1,655 @@ +Let's start off with a very simple problem. We want to let a user input his/her +birth year, and tell him/her his/her age in the year 2020. +Using the function `read`, this is really simple: + +```active haskell +main = do + putStrLn "Please enter your birth year" + year <- getLine + putStrLn $ "In 2020, you will be: " ++ show (2020 - read year) +``` + +If you run that program and type in a valid year, you'll get the right result. +However, what happens when you enter something invalid? + +``` +Please enter your birth year +hello +main.hs: Prelude.read: no parse +``` + +The problem is that the user input is coming in as a `String`, and `read` is +trying to parse it into an `Integer`. But not all `String`s are valid +`Integer`s. `read` is what we call a __partial function__, meaning that under +some circumstances it will return an error instead of a valid result. + +A more resilient way to write our code is to use the `readMay` function, which +will return a `Maybe Integer` value. This makes it clear with the types +themselves that the parse may succeed or fail. To test this out, try running +the following code: + +```active haskell +import Safe (readMay) + +main = do + -- We use explicit types to tell the compiler how to try and parse the + -- string. + print (readMay "1980" :: Maybe Integer) + print (readMay "hello" :: Maybe Integer) + print (readMay "2000" :: Maybe Integer) + print (readMay "two-thousand" :: Maybe Integer) +``` + +So how can we use this to solve our original problem? We need to now determine +if the result of `readMay` was successful (as `Just`) or failed (a `Nothing`). +One way to do this is with pattern matching: + +```active haskell +import Safe (readMay) + +main = do + putStrLn "Please enter your birth year" + yearString <- getLine + case readMay yearString of + Nothing -> putStrLn "You provided an invalid year" + Just year -> putStrLn $ "In 2020, you will be: " ++ show (2020 - year) +``` + +## Decoupling code + +This code is a bit coupled; let's split it up to have a separate function for +displaying the output to the user, and another separate function for +calculating the age. + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided an invalid year" + Just age -> putStrLn $ "In 2020, you will be: " ++ show age + +yearToAge year = 2020 - year + +main = do + putStrLn "Please enter your birth year" + yearString <- getLine + let maybeAge = + case readMay yearString of + Nothing -> Nothing + Just year -> Just (yearToAge year) + displayAge maybeAge +``` + +This code does exactly the same thing as our previous version. But the +definition of `maybeAge` in the `main` function looks pretty repetitive to me. +We check if the parse year is `Nothing`. If it's `Nothing`, we return +`Nothing`. If it's `Just`, we return `Just`, after applying the function +`yearToAge`. That seems like a lot of line noise to do something simple. All we +want is to conditionally apply `yearToAge`. + +## Functors + +Fortunately, we have a helper function to do just that. `fmap`, or __functor +mapping__, will apply some function over the value contained by a __functor__. +`Maybe` is one example of a functor, another common one is a list. In the case +of `Maybe`, `fmap` does precisely what we described above. So we can replace +our code with: + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided an invalid year" + Just age -> putStrLn $ "In 2020, you will be: " ++ show age + +yearToAge year = 2020 - year + +main = do + putStrLn "Please enter your birth year" + yearString <- getLine + let maybeAge = fmap yearToAge (readMay yearString) + displayAge maybeAge +``` + +Our code definitely got shorter, and hopefully a bit clearer as well. Now it's +obvious that all we're doing is applying the `yearToAge` function over the +contents of the `Maybe` value. + +So what *is* a functor? It's some kind of container of values. In `Maybe`, our +container holds zero or one values. With lists, we have a container for zero or +more values. Some containers are even more exotic; the `IO` functor is actually +providing an action to perform in order to retrieve a value. The only thing +functors share is that they provide some `fmap` function which lets you modify +their contents. + +## do-notation + +We have another option as well: we can use do-notation. This is the same way +we've been writing our `main` function in so far. That's because- as we +mentioned in the previous paragraph- `IO` is a functor as well. Let's see how +we can change our code to not use `fmap`: + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided an invalid year" + Just age -> putStrLn $ "In 2020, you will be: " ++ show age + +yearToAge year = 2020 - year + +main = do + putStrLn "Please enter your birth year" + yearString <- getLine + let maybeAge = do + yearInteger <- readMay yearString + return $ yearToAge yearInteger + displayAge maybeAge +``` + +Inside the `do-`block, we have the __slurp operator__ <-. This +operator is special for do-notation, and is used to pull a value out of its +wrapper (in this case, `Maybe`). Once we've extracted the value, we can +manipulate it with normal functions, like `yearToAge`. When we complete our +do-block, we have to return a value wrapped up in that container again. That's +what the `return` function does. + +do-notation isn't available for all `Functor`s; it's a special feature reserved +only for `Monad`s. `Monad`s are an extension of `Functor`s that provide a +little extra power. We're not really taking advantage of any of that extra +power here; we'll need to make our program more complicated to demonstrate +it. + +## Dealing with two variables + +It's kind of limiting that we have a hard-coded year to compare against. Let's +fix that by allowing the user to specify the "future year." We'll start off +with a simple implementation using pattern matching and then move back to do +notation. + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + +main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = + case readMay birthYearString of + Nothing -> Nothing + Just birthYear -> + case readMay futureYearString of + Nothing -> Nothing + Just futureYear -> Just (futureYear - birthYear) + displayAge maybeAge +``` + +OK, it gets the job done... but it's very tedious. Fortunately, do-notation makes this kind of code really simple: + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + +yearDiff futureYear birthYear = futureYear - birthYear + +main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + birthYear <- readMay birthYearString + futureYear <- readMay futureYearString + return $ yearDiff futureYear birthYear + displayAge maybeAge +``` + +This is very convenient: we've now slurped our two values in our do-notation. +If either parse returns `Nothing`, then the entire do-block will return +`Nothing`. This demonstrates an important property about `Maybe`: it provides +__short circuiting__. + +Without resorting to other helper functions or pattern matching, there's no way +to write this code using just `fmap`. So we've found an example of code that +requires more power than `Functor`s provide, and `Monad`s provide that power. + +## Partial application + +But maybe there's something else that provides enough power to write our +two-variable code without the full power of `Monad`. To see what this might be, +let's look more carefully at our types. + +We're working with two values: `readMay birthYearString` and `readMay +futureYearString`. Both of these values have the type `Maybe Integer`. And we +want to apply the function `yearDiff`, which has the type `Integer -> Integer +-> Integer`. + +If we go back to trying to use `fmap`, we'll seemingly run into a bit of a +problem. The type of `fmap`- specialized for `Maybe` and `Integer`- is +`(Integer -> a) -> Maybe Integer -> Maybe a`. In other words, it takes a +function that takes a single argument (an `Integer`) and returns a value of +some type `a`, takes a second argument of a `Maybe Integer`, and gives back a +value of type `Maybe a`. But our function- `yearDiff`- actually takes two +arguments, not one. So `fmap` can't be used at all, right? + +Not true actually. This is where one of Haskell's very powerful features comes +into play. Any time we have a function of two arguments, we can also look at is +as a function of one argument which returns a __function__. We can make this +more clear with parentheses: + +```haskell +yearDiff :: Integer -> Integer -> Integer +yearDiff :: Integer -> (Integer -> Integer) +``` + +So how does that help us? We can look at the `fmap` function as: + +```haskell +fmap :: (Integer -> (Integer -> Integer)) + -> Maybe Integer -> Maybe (Integer -> Integer) +``` + +Then when we apply `fmap` to `yearDiff`, we end up with: + +```haskell +fmap yearDiff :: Maybe Integer -> Maybe (Integer -> Integer) +``` + +That's pretty cool. We can apply *this* to our `readMay futureYearString` and +end up with: + +```haskell +fmap yearDiff (readMay futureYearString) :: Maybe (Integer -> Integer) +``` + +That's certainly very interesting, but it doesn't help us. We need to somehow +apply this value of type `Maybe (Integer -> Integer)` to our `readMay +birthYearString` of type `Maybe Integer`. We can do this with do-notation: + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + +yearDiff futureYear birthYear = futureYear - birthYear + +main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + yearToAge <- fmap yearDiff (readMay futureYearString) + birthYear <- readMay birthYearString + return $ yearToAge birthYear + displayAge maybeAge +``` + +We can even use `fmap` twice and avoid the second slurp: + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + +yearDiff futureYear birthYear = futureYear - birthYear + +main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + yearToAge <- fmap yearDiff (readMay futureYearString) + fmap yearToAge (readMay birthYearString) + displayAge maybeAge +``` + +But we don't have a way to apply our `Maybe (Integer -> Integer)` function to +our `Maybe Integer` directly. + +## Applicative functors + +And now we get to our final concept: applicative functors. The idea is simple: +we want to be able to apply a function which is *inside* a functor to a value +inside a functor. The magic operator for this is <*>. Let's +see how it works in our example: + +```active haskell +import Safe (readMay) +import Control.Applicative ((<*>)) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + +yearDiff futureYear birthYear = futureYear - birthYear + +main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = + fmap yearDiff (readMay futureYearString) + <*> readMay birthYearString + displayAge maybeAge +``` + +In fact, the combination of `fmap` and `<*>` is so common that we have a +special operator, `<$>`, which is a synonym for `fmap`. That means we can make +our code just a little prettier: + +```active haskell +import Safe (readMay) +import Control.Applicative ((<$>), (<*>)) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + +yearDiff futureYear birthYear = futureYear - birthYear + +main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine +-- show + let maybeAge = yearDiff + <$> readMay futureYearString + <*> readMay birthYearString +-- /show + displayAge maybeAge +``` + +Notice the distinction between `<$>` and `<*>`. The former uses a function +which is *not* wrapped in a functor, while the latter applies a function which +is wrapped up. + +## So we don't need Monads? + +So if we can do such great stuff with functors and applicative functors, why do +we need monads at all? The terse answer is __context sensitivity__: with a +monad, you can make decisions on which processing path to follow based on +previous results. With applicative functors, you have to always apply the same +functions. + +Let's give a contrived example: if the future year is less than the birth year, +we'll assume that the user just got confused and entered the values in reverse, +so we'll automatically fix it by reversing the arguments to `yearDiff`. With +do-notation and an if statement, it's easy: + +```active haskell +import Safe (readMay) + +displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + +yearDiff futureYear birthYear = futureYear - birthYear + +main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + futureYear <- readMay futureYearString + birthYear <- readMay birthYearString + return $ + if futureYear < birthYear + then yearDiff birthYear futureYear + else yearDiff futureYear birthYear + displayAge maybeAge +``` + +## Exercises + +1. Implement `fmap` using `<*>` and `return`. + + ```active haskell + import Control.Applicative ((<*>), Applicative) + import Prelude (return, Monad) + import qualified Prelude + + fmap :: (Applicative m, Monad m) => (a -> b) -> (m a -> m b) + -- show + fmap ... ... = FIXME + -- /show + + main = + case fmap (Prelude.+ 1) (Prelude.Just 2) of + Prelude.Just 3 -> Prelude.putStrLn "Good job!" + _ -> Prelude.putStrLn "Try again" + ``` + + @@@SHOW SOLUTION + + ```active haskell + import Control.Applicative ((<*>)) + + -- show + myFmap function wrappedValue = return function <*> wrappedValue + + main = print $ myFmap (+ 1) $ Just 5 + -- /show + ``` + + @@@ + +2. How is `return` implemented for the `Maybe` monad? Try replacing `return` + with its implementation in the code above. + + ```haskell active + -- show + returnMaybe = FIXME + -- /show + + main + | returnMaybe "Hello" == Just "Hello" = putStrLn "Correct!" + | otherwise = putStrLn "Incorrect, please try again" + ``` + + @@@SHOW SOLUTION + + `return` is simply the `Just` constructor. This gets defined as: + + ```haskell + instance Monad Maybe where + return = Just + ``` + + @@@ + +3. `yearDiff` is really just subtraction. Try to replace the calls to + `yearDiff` with explicit usage of the `-` operator. + + ```haskell active + import Safe (readMay) + + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + + main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + futureYear <- readMay futureYearString + birthYear <- readMay birthYearString + return $ + -- show + if futureYear < birthYear + then yearDiff birthYear futureYear + else yearDiff futureYear birthYear + -- /show + displayAge maybeAge + ``` + + @@@SHOW SOLUTION + + ```haskell + import Safe (readMay) + + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + + main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + futureYear <- readMay futureYearString + birthYear <- readMay birthYearString + return $ + -- show + if futureYear < birthYear + then birthYear - futureYear + else futureYear - birthYear + -- /show + displayAge maybeAge + ``` + + @@@ + +4. It's possible to write an applicative functor version of the + auto-reverse-arguments code by modifying the `yearDiff` function. Try to do + so. + + ```active haskell + import Safe (readMay) + import Control.Applicative ((<$>), (<*>)) + + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + + -- show + yearDiff futureYear birthYear = FIXME + -- /show + + main + | yearDiff 5 6 == 1 = putStrLn "Correct!" + | otherwise = putStrLn "Please try again" + ``` + + + @@@ SHOW SOLUTION + + ```active haskell + import Safe (readMay) + + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + + -- show + yearDiff futureYear birthYear + | futureYear > birthYear = futureYear - birthYear + | otherwise = birthYear - futureYear + -- /show + + main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + futureYear <- readMay futureYearString + birthYear <- readMay birthYearString + return $ + if futureYear < birthYear + then yearDiff birthYear futureYear + else yearDiff futureYear birthYear + displayAge maybeAge + ``` + + @@@ + + * Now try to do it without modifying `yearDiff` directly, but by using a + helper function which is applied to `yearDiff`. + + ```active haskell + import Safe (readMay) + import Control.Applicative ((<$>), (<*>)) + + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + + yearDiff futureYear birthYear = futureYear - birthYear + -- show + yourHelperFunction f ... + -- /show + + main + | yourHelperFunction yearDiff 5 6 == 1 = putStrLn "Correct!" + | otherwise = putStrLn "Please try again" + ``` + + @@@ SHOW SOLUTION + + ```active haskell + import Safe (readMay) + + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age + + yearDiff futureYear birthYear = futureYear - birthYear + + main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + futureYear <- readMay futureYearString + birthYear <- readMay birthYearString + return $ + if futureYear < birthYear + then yourHelperFunction yearDiff birthYear futureYear + else yourHelperFunction yearDiff futureYear birthYear + displayAge maybeAge + + -- show + yourHelperFunction f x y + | x > y = f x y + | otherwise = f y x + -- /show + ``` + + @@@ From 7c4b3b973dacf2e966c84f9b77a3c41e72794f46 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 1 Jan 2017 15:17:49 +0200 Subject: [PATCH 2/6] readMay=>readMaybe, active=>stack runghc --- posts/functors-applicatives-and-monads.md | 206 +++++++++++++--------- 1 file changed, 126 insertions(+), 80 deletions(-) diff --git a/posts/functors-applicatives-and-monads.md b/posts/functors-applicatives-and-monads.md index 37fc66a..fd80e90 100644 --- a/posts/functors-applicatives-and-monads.md +++ b/posts/functors-applicatives-and-monads.md @@ -1,8 +1,13 @@ +__NOTE__ This content originally appeared on +[School of Haskell](https://www.schoolofhaskell.com/user/snoyberg/general-haskell/basics/functors-applicative-functors-and-monads). + Let's start off with a very simple problem. We want to let a user input his/her birth year, and tell him/her his/her age in the year 2020. Using the function `read`, this is really simple: -```active haskell +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc main = do putStrLn "Please enter your birth year" year <- getLine @@ -23,34 +28,38 @@ trying to parse it into an `Integer`. But not all `String`s are valid `Integer`s. `read` is what we call a __partial function__, meaning that under some circumstances it will return an error instead of a valid result. -A more resilient way to write our code is to use the `readMay` function, which +A more resilient way to write our code is to use the `readMaybe` function, which will return a `Maybe Integer` value. This makes it clear with the types themselves that the parse may succeed or fail. To test this out, try running the following code: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) main = do -- We use explicit types to tell the compiler how to try and parse the -- string. - print (readMay "1980" :: Maybe Integer) - print (readMay "hello" :: Maybe Integer) - print (readMay "2000" :: Maybe Integer) - print (readMay "two-thousand" :: Maybe Integer) + print (readMaybe "1980" :: Maybe Integer) + print (readMaybe "hello" :: Maybe Integer) + print (readMaybe "2000" :: Maybe Integer) + print (readMaybe "two-thousand" :: Maybe Integer) ``` So how can we use this to solve our original problem? We need to now determine -if the result of `readMay` was successful (as `Just`) or failed (a `Nothing`). +if the result of `readMaybe` was successful (as `Just`) or failed (a `Nothing`). One way to do this is with pattern matching: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) main = do putStrLn "Please enter your birth year" yearString <- getLine - case readMay yearString of + case readMaybe yearString of Nothing -> putStrLn "You provided an invalid year" Just year -> putStrLn $ "In 2020, you will be: " ++ show (2020 - year) ``` @@ -61,8 +70,10 @@ This code is a bit coupled; let's split it up to have a separate function for displaying the output to the user, and another separate function for calculating the age. -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -75,7 +86,7 @@ main = do putStrLn "Please enter your birth year" yearString <- getLine let maybeAge = - case readMay yearString of + case readMaybe yearString of Nothing -> Nothing Just year -> Just (yearToAge year) displayAge maybeAge @@ -96,8 +107,10 @@ mapping__, will apply some function over the value contained by a __functor__. of `Maybe`, `fmap` does precisely what we described above. So we can replace our code with: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -109,7 +122,7 @@ yearToAge year = 2020 - year main = do putStrLn "Please enter your birth year" yearString <- getLine - let maybeAge = fmap yearToAge (readMay yearString) + let maybeAge = fmap yearToAge (readMaybe yearString) displayAge maybeAge ``` @@ -131,8 +144,10 @@ we've been writing our `main` function in so far. That's because- as we mentioned in the previous paragraph- `IO` is a functor as well. Let's see how we can change our code to not use `fmap`: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -145,7 +160,7 @@ main = do putStrLn "Please enter your birth year" yearString <- getLine let maybeAge = do - yearInteger <- readMay yearString + yearInteger <- readMaybe yearString return $ yearToAge yearInteger displayAge maybeAge ``` @@ -170,8 +185,10 @@ fix that by allowing the user to specify the "future year." We'll start off with a simple implementation using pattern matching and then move back to do notation. -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -184,10 +201,10 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = - case readMay birthYearString of + case readMaybe birthYearString of Nothing -> Nothing Just birthYear -> - case readMay futureYearString of + case readMaybe futureYearString of Nothing -> Nothing Just futureYear -> Just (futureYear - birthYear) displayAge maybeAge @@ -195,8 +212,10 @@ main = do OK, it gets the job done... but it's very tedious. Fortunately, do-notation makes this kind of code really simple: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -211,8 +230,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - birthYear <- readMay birthYearString - futureYear <- readMay futureYearString + birthYear <- readMaybe birthYearString + futureYear <- readMaybe futureYearString return $ yearDiff futureYear birthYear displayAge maybeAge ``` @@ -232,7 +251,7 @@ But maybe there's something else that provides enough power to write our two-variable code without the full power of `Monad`. To see what this might be, let's look more carefully at our types. -We're working with two values: `readMay birthYearString` and `readMay +We're working with two values: `readMaybe birthYearString` and `readMaybe futureYearString`. Both of these values have the type `Maybe Integer`. And we want to apply the function `yearDiff`, which has the type `Integer -> Integer -> Integer`. @@ -268,19 +287,21 @@ Then when we apply `fmap` to `yearDiff`, we end up with: fmap yearDiff :: Maybe Integer -> Maybe (Integer -> Integer) ``` -That's pretty cool. We can apply *this* to our `readMay futureYearString` and +That's pretty cool. We can apply *this* to our `readMaybe futureYearString` and end up with: ```haskell -fmap yearDiff (readMay futureYearString) :: Maybe (Integer -> Integer) +fmap yearDiff (readMaybe futureYearString) :: Maybe (Integer -> Integer) ``` That's certainly very interesting, but it doesn't help us. We need to somehow -apply this value of type `Maybe (Integer -> Integer)` to our `readMay +apply this value of type `Maybe (Integer -> Integer)` to our `readMaybe birthYearString` of type `Maybe Integer`. We can do this with do-notation: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -295,16 +316,18 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - yearToAge <- fmap yearDiff (readMay futureYearString) - birthYear <- readMay birthYearString + yearToAge <- fmap yearDiff (readMaybe futureYearString) + birthYear <- readMaybe birthYearString return $ yearToAge birthYear displayAge maybeAge ``` We can even use `fmap` twice and avoid the second slurp: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -319,8 +342,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - yearToAge <- fmap yearDiff (readMay futureYearString) - fmap yearToAge (readMay birthYearString) + yearToAge <- fmap yearDiff (readMaybe futureYearString) + fmap yearToAge (readMaybe birthYearString) displayAge maybeAge ``` @@ -334,8 +357,10 @@ we want to be able to apply a function which is *inside* a functor to a value inside a functor. The magic operator for this is <*>. Let's see how it works in our example: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) import Control.Applicative ((<*>)) displayAge maybeAge = @@ -351,8 +376,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = - fmap yearDiff (readMay futureYearString) - <*> readMay birthYearString + fmap yearDiff (readMaybe futureYearString) + <*> readMaybe birthYearString displayAge maybeAge ``` @@ -360,8 +385,10 @@ In fact, the combination of `fmap` and `<*>` is so common that we have a special operator, `<$>`, which is a synonym for `fmap`. That means we can make our code just a little prettier: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) import Control.Applicative ((<$>), (<*>)) displayAge maybeAge = @@ -378,8 +405,8 @@ main = do futureYearString <- getLine -- show let maybeAge = yearDiff - <$> readMay futureYearString - <*> readMay birthYearString + <$> readMaybe futureYearString + <*> readMaybe birthYearString -- /show displayAge maybeAge ``` @@ -401,8 +428,10 @@ we'll assume that the user just got confused and entered the values in reverse, so we'll automatically fix it by reversing the arguments to `yearDiff`. With do-notation and an if statement, it's easy: -```active haskell -import Safe (readMay) +```haskell +#!/usr/bin/env stack +-- stack --resolver lts-7.14 runghc +import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -417,8 +446,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - futureYear <- readMay futureYearString - birthYear <- readMay birthYearString + futureYear <- readMaybe futureYearString + birthYear <- readMaybe birthYearString return $ if futureYear < birthYear then yearDiff birthYear futureYear @@ -430,7 +459,9 @@ main = do 1. Implement `fmap` using `<*>` and `return`. - ```active haskell + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc import Control.Applicative ((<*>), Applicative) import Prelude (return, Monad) import qualified Prelude @@ -448,7 +479,9 @@ main = do @@@SHOW SOLUTION - ```active haskell + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc import Control.Applicative ((<*>)) -- show @@ -463,7 +496,9 @@ main = do 2. How is `return` implemented for the `Maybe` monad? Try replacing `return` with its implementation in the code above. - ```haskell active + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc -- show returnMaybe = FIXME -- /show @@ -472,9 +507,9 @@ main = do | returnMaybe "Hello" == Just "Hello" = putStrLn "Correct!" | otherwise = putStrLn "Incorrect, please try again" ``` - + @@@SHOW SOLUTION - + `return` is simply the `Just` constructor. This gets defined as: ```haskell @@ -487,8 +522,10 @@ main = do 3. `yearDiff` is really just subtraction. Try to replace the calls to `yearDiff` with explicit usage of the `-` operator. - ```haskell active - import Safe (readMay) + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -501,8 +538,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - futureYear <- readMay futureYearString - birthYear <- readMay birthYearString + futureYear <- readMaybe futureYearString + birthYear <- readMaybe birthYearString return $ -- show if futureYear < birthYear @@ -515,7 +552,9 @@ main = do @@@SHOW SOLUTION ```haskell - import Safe (readMay) + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -528,8 +567,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - futureYear <- readMay futureYearString - birthYear <- readMay birthYearString + futureYear <- readMaybe futureYearString + birthYear <- readMaybe birthYearString return $ -- show if futureYear < birthYear @@ -544,9 +583,11 @@ main = do 4. It's possible to write an applicative functor version of the auto-reverse-arguments code by modifying the `yearDiff` function. Try to do so. - - ```active haskell - import Safe (readMay) + + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) import Control.Applicative ((<$>), (<*>)) displayAge maybeAge = @@ -562,12 +603,13 @@ main = do | yearDiff 5 6 == 1 = putStrLn "Correct!" | otherwise = putStrLn "Please try again" ``` - @@@ SHOW SOLUTION - ```active haskell - import Safe (readMay) + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -586,8 +628,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - futureYear <- readMay futureYearString - birthYear <- readMay birthYearString + futureYear <- readMaybe futureYearString + birthYear <- readMaybe birthYearString return $ if futureYear < birthYear then yearDiff birthYear futureYear @@ -599,9 +641,11 @@ main = do * Now try to do it without modifying `yearDiff` directly, but by using a helper function which is applied to `yearDiff`. - - ```active haskell - import Safe (readMay) + + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) import Control.Applicative ((<$>), (<*>)) displayAge maybeAge = @@ -621,8 +665,10 @@ main = do @@@ SHOW SOLUTION - ```active haskell - import Safe (readMay) + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) displayAge maybeAge = case maybeAge of @@ -637,8 +683,8 @@ main = do putStrLn "Please enter some year in the future" futureYearString <- getLine let maybeAge = do - futureYear <- readMay futureYearString - birthYear <- readMay birthYearString + futureYear <- readMaybe futureYearString + birthYear <- readMaybe birthYearString return $ if futureYear < birthYear then yourHelperFunction yearDiff birthYear futureYear From daad8d3bdc9ed71020ed9b97775f9b21978ba5b3 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 1 Jan 2017 15:25:43 +0200 Subject: [PATCH 3/6] SHOW SOLUTION via Bootstrap --- posts/functors-applicatives-and-monads.md | 164 ++++++++++++++-------- 1 file changed, 107 insertions(+), 57 deletions(-) diff --git a/posts/functors-applicatives-and-monads.md b/posts/functors-applicatives-and-monads.md index fd80e90..b9081a8 100644 --- a/posts/functors-applicatives-and-monads.md +++ b/posts/functors-applicatives-and-monads.md @@ -477,7 +477,17 @@ main = do _ -> Prelude.putStrLn "Try again" ``` - @@@SHOW SOLUTION +
+
+ +
+
```haskell #!/usr/bin/env stack @@ -491,7 +501,7 @@ main = do -- /show ``` - @@@ +
2. How is `return` implemented for the `Maybe` monad? Try replacing `return` with its implementation in the code above. @@ -508,7 +518,17 @@ main = do | otherwise = putStrLn "Incorrect, please try again" ``` - @@@SHOW SOLUTION +
+
+ +
+
`return` is simply the `Just` constructor. This gets defined as: @@ -517,7 +537,7 @@ main = do return = Just ``` - @@@ +
3. `yearDiff` is really just subtraction. Try to replace the calls to `yearDiff` with explicit usage of the `-` operator. @@ -549,7 +569,17 @@ main = do displayAge maybeAge ``` - @@@SHOW SOLUTION +
+
+ +
+
```haskell #!/usr/bin/env stack @@ -578,7 +608,7 @@ main = do displayAge maybeAge ``` - @@@ +
4. It's possible to write an applicative functor version of the auto-reverse-arguments code by modifying the `yearDiff` function. Try to do @@ -604,7 +634,17 @@ main = do | otherwise = putStrLn "Please try again" ``` - @@@ SHOW SOLUTION +
+
+ +
+
```haskell #!/usr/bin/env stack @@ -637,65 +677,75 @@ main = do displayAge maybeAge ``` - @@@ +
- * Now try to do it without modifying `yearDiff` directly, but by using a - helper function which is applied to `yearDiff`. +5. Now try to do it without modifying `yearDiff` directly, but by + using a helper function which is applied to `yearDiff`. - ```haskell - #!/usr/bin/env stack - -- stack --resolver lts-7.14 runghc - import Text.Read (readMaybe) - import Control.Applicative ((<$>), (<*>)) + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) + import Control.Applicative ((<$>), (<*>)) - displayAge maybeAge = - case maybeAge of - Nothing -> putStrLn "You provided invalid input" - Just age -> putStrLn $ "In that year, you will be: " ++ show age + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age - yearDiff futureYear birthYear = futureYear - birthYear - -- show - yourHelperFunction f ... - -- /show + yearDiff futureYear birthYear = futureYear - birthYear + -- show + yourHelperFunction f ... + -- /show - main - | yourHelperFunction yearDiff 5 6 == 1 = putStrLn "Correct!" - | otherwise = putStrLn "Please try again" - ``` + main + | yourHelperFunction yearDiff 5 6 == 1 = putStrLn "Correct!" + | otherwise = putStrLn "Please try again" + ``` - @@@ SHOW SOLUTION +
+
+ +
+
- ```haskell - #!/usr/bin/env stack - -- stack --resolver lts-7.14 runghc - import Text.Read (readMaybe) + ```haskell + #!/usr/bin/env stack + -- stack --resolver lts-7.14 runghc + import Text.Read (readMaybe) - displayAge maybeAge = - case maybeAge of - Nothing -> putStrLn "You provided invalid input" - Just age -> putStrLn $ "In that year, you will be: " ++ show age + displayAge maybeAge = + case maybeAge of + Nothing -> putStrLn "You provided invalid input" + Just age -> putStrLn $ "In that year, you will be: " ++ show age - yearDiff futureYear birthYear = futureYear - birthYear + yearDiff futureYear birthYear = futureYear - birthYear - main = do - putStrLn "Please enter your birth year" - birthYearString <- getLine - putStrLn "Please enter some year in the future" - futureYearString <- getLine - let maybeAge = do - futureYear <- readMaybe futureYearString - birthYear <- readMaybe birthYearString - return $ - if futureYear < birthYear - then yourHelperFunction yearDiff birthYear futureYear - else yourHelperFunction yearDiff futureYear birthYear - displayAge maybeAge + main = do + putStrLn "Please enter your birth year" + birthYearString <- getLine + putStrLn "Please enter some year in the future" + futureYearString <- getLine + let maybeAge = do + futureYear <- readMaybe futureYearString + birthYear <- readMaybe birthYearString + return $ + if futureYear < birthYear + then yourHelperFunction yearDiff birthYear futureYear + else yourHelperFunction yearDiff futureYear birthYear + displayAge maybeAge - -- show - yourHelperFunction f x y - | x > y = f x y - | otherwise = f y x - -- /show - ``` + -- show + yourHelperFunction f x y + | x > y = f x y + | otherwise = f y x + -- /show + ``` - @@@ +
From 6879a6119e6f27e487bb29abd2190b02462210d7 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 1 Jan 2017 15:28:26 +0200 Subject: [PATCH 4/6] Remove usage of --show comments --- posts/functors-applicatives-and-monads.md | 111 +--------------------- 1 file changed, 2 insertions(+), 109 deletions(-) diff --git a/posts/functors-applicatives-and-monads.md b/posts/functors-applicatives-and-monads.md index b9081a8..7b2b7df 100644 --- a/posts/functors-applicatives-and-monads.md +++ b/posts/functors-applicatives-and-monads.md @@ -386,29 +386,9 @@ special operator, `<$>`, which is a synonym for `fmap`. That means we can make our code just a little prettier: ```haskell -#!/usr/bin/env stack --- stack --resolver lts-7.14 runghc -import Text.Read (readMaybe) -import Control.Applicative ((<$>), (<*>)) - -displayAge maybeAge = - case maybeAge of - Nothing -> putStrLn "You provided invalid input" - Just age -> putStrLn $ "In that year, you will be: " ++ show age - -yearDiff futureYear birthYear = futureYear - birthYear - -main = do - putStrLn "Please enter your birth year" - birthYearString <- getLine - putStrLn "Please enter some year in the future" - futureYearString <- getLine --- show let maybeAge = yearDiff <$> readMaybe futureYearString <*> readMaybe birthYearString --- /show - displayAge maybeAge ``` Notice the distinction between `<$>` and `<*>`. The former uses a function @@ -467,9 +447,7 @@ main = do import qualified Prelude fmap :: (Applicative m, Monad m) => (a -> b) -> (m a -> m b) - -- show fmap ... ... = FIXME - -- /show main = case fmap (Prelude.+ 1) (Prelude.Just 2) of @@ -490,15 +468,9 @@ main = do
```haskell - #!/usr/bin/env stack - -- stack --resolver lts-7.14 runghc - import Control.Applicative ((<*>)) - - -- show myFmap function wrappedValue = return function <*> wrappedValue main = print $ myFmap (+ 1) $ Just 5 - -- /show ```
@@ -509,9 +481,7 @@ main = do ```haskell #!/usr/bin/env stack -- stack --resolver lts-7.14 runghc - -- show returnMaybe = FIXME - -- /show main | returnMaybe "Hello" == Just "Hello" = putStrLn "Correct!" @@ -561,11 +531,11 @@ main = do futureYear <- readMaybe futureYearString birthYear <- readMaybe birthYearString return $ - -- show + -- BEGIN CODE TO MODIFY if futureYear < birthYear then yearDiff birthYear futureYear else yearDiff futureYear birthYear - -- /show + -- END CODE TO MODIFY displayAge maybeAge ``` @@ -582,30 +552,9 @@ main = do
```haskell - #!/usr/bin/env stack - -- stack --resolver lts-7.14 runghc - import Text.Read (readMaybe) - - displayAge maybeAge = - case maybeAge of - Nothing -> putStrLn "You provided invalid input" - Just age -> putStrLn $ "In that year, you will be: " ++ show age - - main = do - putStrLn "Please enter your birth year" - birthYearString <- getLine - putStrLn "Please enter some year in the future" - futureYearString <- getLine - let maybeAge = do - futureYear <- readMaybe futureYearString - birthYear <- readMaybe birthYearString - return $ - -- show if futureYear < birthYear then birthYear - futureYear else futureYear - birthYear - -- /show - displayAge maybeAge ```
@@ -625,9 +574,7 @@ main = do Nothing -> putStrLn "You provided invalid input" Just age -> putStrLn $ "In that year, you will be: " ++ show age - -- show yearDiff futureYear birthYear = FIXME - -- /show main | yearDiff 5 6 == 1 = putStrLn "Correct!" @@ -647,34 +594,9 @@ main = do
```haskell - #!/usr/bin/env stack - -- stack --resolver lts-7.14 runghc - import Text.Read (readMaybe) - - displayAge maybeAge = - case maybeAge of - Nothing -> putStrLn "You provided invalid input" - Just age -> putStrLn $ "In that year, you will be: " ++ show age - - -- show yearDiff futureYear birthYear | futureYear > birthYear = futureYear - birthYear | otherwise = birthYear - futureYear - -- /show - - main = do - putStrLn "Please enter your birth year" - birthYearString <- getLine - putStrLn "Please enter some year in the future" - futureYearString <- getLine - let maybeAge = do - futureYear <- readMaybe futureYearString - birthYear <- readMaybe birthYearString - return $ - if futureYear < birthYear - then yearDiff birthYear futureYear - else yearDiff futureYear birthYear - displayAge maybeAge ```
@@ -694,9 +616,7 @@ main = do Just age -> putStrLn $ "In that year, you will be: " ++ show age yearDiff futureYear birthYear = futureYear - birthYear - -- show yourHelperFunction f ... - -- /show main | yourHelperFunction yearDiff 5 6 == 1 = putStrLn "Correct!" @@ -716,36 +636,9 @@ main = do
```haskell - #!/usr/bin/env stack - -- stack --resolver lts-7.14 runghc - import Text.Read (readMaybe) - - displayAge maybeAge = - case maybeAge of - Nothing -> putStrLn "You provided invalid input" - Just age -> putStrLn $ "In that year, you will be: " ++ show age - - yearDiff futureYear birthYear = futureYear - birthYear - - main = do - putStrLn "Please enter your birth year" - birthYearString <- getLine - putStrLn "Please enter some year in the future" - futureYearString <- getLine - let maybeAge = do - futureYear <- readMaybe futureYearString - birthYear <- readMaybe birthYearString - return $ - if futureYear < birthYear - then yourHelperFunction yearDiff birthYear futureYear - else yourHelperFunction yearDiff futureYear birthYear - displayAge maybeAge - - -- show yourHelperFunction f x y | x > y = f x y | otherwise = f y x - -- /show ```
From f386421fcab519fd2fe2cf7f36a344609cabfe2c Mon Sep 17 00:00:00 2001 From: Julie Moronuki Date: Sun, 1 Jan 2017 15:27:30 -0600 Subject: [PATCH 5/6] suggests minor changes - remove Control.Applicative import because <*> is in Prelude - minor punctuation changes - remove use of function after `main` --- posts/functors-applicatives-and-monads.md | 32 +++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/posts/functors-applicatives-and-monads.md b/posts/functors-applicatives-and-monads.md index 7b2b7df..8e41174 100644 --- a/posts/functors-applicatives-and-monads.md +++ b/posts/functors-applicatives-and-monads.md @@ -67,7 +67,7 @@ main = do ## Decoupling code This code is a bit coupled; let's split it up to have a separate function for -displaying the output to the user, and another separate function for +displaying the output to the user and another separate function for calculating the age. ```haskell @@ -93,7 +93,7 @@ main = do ``` This code does exactly the same thing as our previous version. But the -definition of `maybeAge` in the `main` function looks pretty repetitive to me. +definition of `maybeAge` in `main` looks pretty repetitive to me. We check if the parse year is `Nothing`. If it's `Nothing`, we return `Nothing`. If it's `Just`, we return `Just`, after applying the function `yearToAge`. That seems like a lot of line noise to do something simple. All we @@ -103,7 +103,7 @@ want is to conditionally apply `yearToAge`. Fortunately, we have a helper function to do just that. `fmap`, or __functor mapping__, will apply some function over the value contained by a __functor__. -`Maybe` is one example of a functor, another common one is a list. In the case +`Maybe` is one example of a functor; another common one is a list. In the case of `Maybe`, `fmap` does precisely what we described above. So we can replace our code with: @@ -139,8 +139,8 @@ their contents. ## do-notation -We have another option as well: we can use do-notation. This is the same way -we've been writing our `main` function in so far. That's because- as we +We have another option as well: we can use `do`-notation. This is the same way +we've been writing `main` so far. That's because- as we mentioned in the previous paragraph- `IO` is a functor as well. Let's see how we can change our code to not use `fmap`: @@ -166,13 +166,13 @@ main = do ``` Inside the `do-`block, we have the __slurp operator__ <-. This -operator is special for do-notation, and is used to pull a value out of its +operator is special for `do`-notation and is used to pull a value out of its wrapper (in this case, `Maybe`). Once we've extracted the value, we can manipulate it with normal functions, like `yearToAge`. When we complete our -do-block, we have to return a value wrapped up in that container again. That's +`do`-block, we have to return a value wrapped up in that container again. That's what the `return` function does. -do-notation isn't available for all `Functor`s; it's a special feature reserved +`do`-notation isn't available for all `Functor`s; it's a special feature reserved only for `Monad`s. `Monad`s are an extension of `Functor`s that provide a little extra power. We're not really taking advantage of any of that extra power here; we'll need to make our program more complicated to demonstrate @@ -182,8 +182,7 @@ it. It's kind of limiting that we have a hard-coded year to compare against. Let's fix that by allowing the user to specify the "future year." We'll start off -with a simple implementation using pattern matching and then move back to do -notation. +with a simple implementation using pattern matching and then move back to `do`-notation. ```haskell #!/usr/bin/env stack @@ -210,7 +209,7 @@ main = do displayAge maybeAge ``` -OK, it gets the job done... but it's very tedious. Fortunately, do-notation makes this kind of code really simple: +OK, it gets the job done... but it's very tedious. Fortunately, `do`-notation makes this kind of code really simple: ```haskell #!/usr/bin/env stack @@ -236,8 +235,8 @@ main = do displayAge maybeAge ``` -This is very convenient: we've now slurped our two values in our do-notation. -If either parse returns `Nothing`, then the entire do-block will return +This is very convenient: we've now slurped our two values in our `do`-notation. +If either parse returns `Nothing`, then the entire `do`-block will return `Nothing`. This demonstrates an important property about `Maybe`: it provides __short circuiting__. @@ -264,7 +263,7 @@ some type `a`, takes a second argument of a `Maybe Integer`, and gives back a value of type `Maybe a`. But our function- `yearDiff`- actually takes two arguments, not one. So `fmap` can't be used at all, right? -Not true actually. This is where one of Haskell's very powerful features comes +Not true. This is where one of Haskell's very powerful features comes into play. Any time we have a function of two arguments, we can also look at is as a function of one argument which returns a __function__. We can make this more clear with parentheses: @@ -296,7 +295,7 @@ fmap yearDiff (readMaybe futureYearString) :: Maybe (Integer -> Integer) That's certainly very interesting, but it doesn't help us. We need to somehow apply this value of type `Maybe (Integer -> Integer)` to our `readMaybe -birthYearString` of type `Maybe Integer`. We can do this with do-notation: +birthYearString` of type `Maybe Integer`. We can do this with `do`-notation: ```haskell #!/usr/bin/env stack @@ -361,7 +360,6 @@ see how it works in our example: #!/usr/bin/env stack -- stack --resolver lts-7.14 runghc import Text.Read (readMaybe) -import Control.Applicative ((<*>)) displayAge maybeAge = case maybeAge of @@ -406,7 +404,7 @@ functions. Let's give a contrived example: if the future year is less than the birth year, we'll assume that the user just got confused and entered the values in reverse, so we'll automatically fix it by reversing the arguments to `yearDiff`. With -do-notation and an if statement, it's easy: +`do`-notation and an if statement, it's easy: ```haskell #!/usr/bin/env stack From dfb5451e494134fffbb146b33d28168a1051aa90 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Mon, 2 Jan 2017 07:02:32 +0200 Subject: [PATCH 6/6] Update publish date; thank Julie --- posts.yaml | 2 +- posts/functors-applicatives-and-monads.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/posts.yaml b/posts.yaml index be1748f..4be27c5 100644 --- a/posts.yaml +++ b/posts.yaml @@ -1,6 +1,6 @@ - file: posts/functors-applicatives-and-monads.md title: Functors, Applicatives, and Monads - day: 2017-01-04 + day: 2017-01-03 - file: posts/beware-of-readfile.md title: Beware of readFile day: 2016-12-22 diff --git a/posts/functors-applicatives-and-monads.md b/posts/functors-applicatives-and-monads.md index 8e41174..bd25143 100644 --- a/posts/functors-applicatives-and-monads.md +++ b/posts/functors-applicatives-and-monads.md @@ -1,5 +1,8 @@ __NOTE__ This content originally appeared on -[School of Haskell](https://www.schoolofhaskell.com/user/snoyberg/general-haskell/basics/functors-applicative-functors-and-monads). +[School of +Haskell](https://www.schoolofhaskell.com/user/snoyberg/general-haskell/basics/functors-applicative-functors-and-monads). +Thanks for Julie Moronuki for encouraging me to update/republish, and for all +of the edits/improvements. Let's start off with a very simple problem. We want to let a user input his/her birth year, and tell him/her his/her age in the year 2020.