├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── images │ ├── arrow-down.png │ └── octocat-small.png ├── index.html ├── javascripts │ └── scale.fix.js ├── pages │ ├── ex1-1.html │ ├── ex1-2.html │ ├── ex1-3.html │ ├── ex1-3hint.html │ ├── ex1-4.html │ ├── ex1-5.html │ ├── ex1-6.html │ ├── ex2-1.html │ ├── ex2-2.html │ ├── ex2-3.html │ ├── ex2-4.html │ ├── ex2-5.html │ ├── ex2-6.html │ ├── ex3-1.html │ ├── ex3-2.html │ ├── ex3-3.html │ ├── ex3-4.html │ ├── ex3-5.html │ ├── ex4-1.html │ ├── ex4-2.html │ ├── ex4-3.html │ ├── ex4-4.html │ ├── ex4-5.html │ ├── ex4-6.html │ ├── ex5-1.html │ ├── ex5-2.html │ ├── ex5-3.html │ ├── ex5-4.html │ ├── ex5-5.html │ ├── set1.html │ ├── set2.html │ ├── set3.html │ ├── set4.html │ └── set5.html └── stylesheets │ ├── github-light.css │ └── styles.css └── monad-challenges-code ├── LICENSE ├── default.nix ├── hakyll ├── index.md ├── pages │ ├── ex1-1.md │ ├── ex1-2.md │ ├── ex1-3.md │ ├── ex1-3hint.md │ ├── ex1-4.md │ ├── ex1-5.md │ ├── ex1-6.md │ ├── ex2-1.md │ ├── ex2-2.md │ ├── ex2-3.md │ ├── ex2-4.md │ ├── ex2-5.md │ ├── ex2-6.md │ ├── ex3-1.md │ ├── ex3-2.md │ ├── ex3-3.md │ ├── ex3-4.md │ ├── ex3-5.md │ ├── ex4-1.md │ ├── ex4-2.md │ ├── ex4-3.md │ ├── ex4-4.md │ ├── ex4-5.md │ ├── ex4-6.md │ ├── ex5-1.md │ ├── ex5-2.md │ ├── ex5-3.md │ ├── ex5-4.md │ ├── ex5-5.md │ ├── set1.md │ ├── set2.md │ ├── set3.md │ ├── set4.md │ └── set5.md ├── regen.sh ├── site.hs └── templates │ └── default.html ├── monad-challenges-code.cabal └── src └── MCPrelude.hs /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-newstyle 3 | *.hi 4 | *.o 5 | solutions 6 | *.swp 7 | _site 8 | _cache 9 | hakyll/site 10 | src/Set[1-5].hs 11 | .ghc.environment.* 12 | result 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Doug Beardsley 2 | 3 | All rights reserved. 4 | 5 | Haskell code in this project uses the following license: 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | * Neither the name of Doug Beardsley nor the names of other 19 | contributors may be used to endorse or promote products derived 20 | from this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Monad Challenges 2 | 3 | The goal of this project is to provide a roadmap for hands-on exploration and 4 | experimentation with monads. It is still rough and there is a fair amount 5 | content that could be added. 6 | 7 | ## Contribution Guide 8 | 9 | The site is a static site generated with 10 | [Hakyll](http://hackage.haskell.org/package/hakyll). To make edits to the text 11 | or to add new sets/exercises, you should edit the markdown files in 12 | [monad-challenges-code/hakyll/pages](https://github.com/mightybyte/monad-challenges/tree/master/hakyll/pages). 13 | 14 | To build, run `nix-build` from the `monad-challenges-code` directory. 15 | 16 | To regenerate the HTML, go to the `monad-challenges-code/hakyll` directory and 17 | run `./regen.sh`. 18 | 19 | If you want to contribute, but don't know what to do, check out the [open 20 | issues](https://github.com/mightybyte/monad-challenges/issues) and see if you 21 | can help with anything there. 22 | -------------------------------------------------------------------------------- /docs/images/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mightybyte/monad-challenges/ca9f7f680ad3e11ee2d006be8e055ed2139e0364/docs/images/arrow-down.png -------------------------------------------------------------------------------- /docs/images/octocat-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mightybyte/monad-challenges/ca9f7f680ad3e11ee2d006be8e055ed2139e0364/docs/images/octocat-small.png -------------------------------------------------------------------------------- /docs/javascripts/scale.fix.js: -------------------------------------------------------------------------------- 1 | fixScale = function(doc) { 2 | 3 | var addEvent = 'addEventListener', 4 | type = 'gesturestart', 5 | qsa = 'querySelectorAll', 6 | scales = [1, 1], 7 | meta = qsa in doc ? doc[qsa]('meta[name=viewport]') : []; 8 | 9 | function fix() { 10 | meta.content = 'width=device-width,minimum-scale=' + scales[0] + ',maximum-scale=' + scales[1]; 11 | doc.removeEventListener(type, fix, true); 12 | } 13 | 14 | if ((meta = meta[meta.length - 1]) && addEvent in doc) { 15 | fix(); 16 | scales = [.25, 1.6]; 17 | doc[addEvent](type, fix, true); 18 | } 19 | 20 | }; -------------------------------------------------------------------------------- /docs/pages/ex1-1.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
In MCPrelude we provide a simple random number generation function rand
. Random number generators usually rely on mutable state side effects. They maintain some state somewhere in memory and use that to figure out what “random” number to give you. Before the function returns, it modifies the mutable state so that the next time you call the function you’ll get a different number. Haskell is a pure functional programming language and because our custom Prelude hides Haskell’s mechanisms for dealing with side effects, we can’t build a random number generator that way. Our random number generator has to have everything it needs passed in and it has to return everything it modifies. Therefore, it has this type signature:
rand :: Seed -> (Integer, Seed)
53 | You can construct seeds with the mkSeed
function.
mkSeed :: Integer -> Seed
55 | Make a function that gives you the first five random numbers starting with a seed of (mkSeed 1)
. Call it:
fiveRands :: [Integer]
57 | For now don’t try to do anything fancy. Just implement it in the most straightforward way that comes to mind. To check your answers, the product of these numbers is 8681089573064486461641871805074254223660.
58 | 59 | 60 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now use the rand
function to make your own function for generating random letters of the alphabet. It should only generate lower case letters a-z.
We supply a function in MCPrelude called toLetter
. Use the toLetter
function to write this random letter function:
randLetter :: Seed -> (Char, Seed)
54 | Now use this function to write another function randString3
that generates a random string of three letters using an initial seed of 1.
randString3 :: String
56 | When you use this site to calculate the SHA-256 hash of the output of randString3
, you get 9d475eb78d3e38085220ed6ebde9d8f7d26540bb1c8f9382479c3acd4c8c94a3.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now write a few more functions to generate different subsets of random numbers. But first, we need to generalize a bit. If we look at the type signatures for rand
and randLetter
, what are the commonalities?
rand :: Seed -> (Integer, Seed)
53 | randLetter :: Seed -> (Char, Seed)
54 | This common pattern can be captured in a type synonym. The key point here is that they use two different types, so you will have to introduce a type parameter. Create a type synonym that would allow us to change the type signatures of these two functions to the following:
55 |rand :: Gen Integer
56 | randLetter :: Gen Char
57 | This is a relatively obvious type synonym and it is pretty easy to write, but it enables a big mental leap. Now that we have this type synonym we’ve jumped up a level of abstraction and higher level patterns will become more apparent. First go back and rewrite all your existing type signatures using this Gen
type synonym. Now write three new functions:
randEven :: Gen Integer -- the output of rand * 2
59 | randOdd :: Gen Integer -- the output of rand * 2 + 1
60 | randTen :: Gen Integer -- the output of rand * 10
61 | Where randEven
and randOdd
only generate even and odd numbers respectively, randTen
only generates multiples of 10. Write randEven
in terms of rand
, write randOdd
in terms of randEven
, and write randTen
however you want. There’s a general pattern lurking here. If you implement all these functions the naive way like you did fiveRands
, you’ll be repeating the same pattern over and over. Figure out how to exploit the common pattern and write a function called generalA
that implements this pattern. All three of the above functions will also use generalA
. There are a number of different ways you could abstract this. We realize that you may not pick the right abstraction. Play around with as many different possibilities as you can think of.
SPEND SOME TIME WITH THIS BEFORE READING THE HINT!
63 |Play with a number of abstractions and try to find the most flexible one. After you’ve spent some time with it, look at the hint here.
64 |Pass all three of these functions a seed of 1. They’ll give you three numbers back, the product of which is 189908109902700.
65 | 66 | 67 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
All three of these functions are conceptually a call to rand
followed by applying a function to the Integer it returns. What you ultimately need is a function that you can pass a Gen a
to and will transform the result.
Be careful not to make your function too specific to rand
. rand
is a specific example of a Gen Integer
but you want your function to work for other kinds of Gen a
as well.
If you are still stuck, you can view the expected type signature by hex decoding the following string: 67656E6572616C41203A3A202861202D3E206229202D3E2047656E2061202D3E2047656E2062
54 |You can decode it with this online hex decoder.
55 | 56 | 57 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Use the provided rand
function and your randLetter
function to make a function that generates a random pair of a Char and an Integer.
randPair :: Gen (Char, Integer)
53 | Generate the letter first, then generate the number using the seed acquired from generating the letter. When you give this function (mkSeed 1)
it gives you the random pair ('l',282475249)
.
Here we’re starting to see the Gen
type synonym pay off. Without Gen
, the type would have been:
randPair :: Seed -> ((Char, Integer), Seed)
56 | Still understandable, but the nested tuples are starting to obscure things slightly.
57 |You have just composed two generators. Now generalize the composition to generalPair
, and make sure that the second argument receive the seed generated by the first argument.
generalPair :: Gen a -> Gen b -> Gen (a,b)
59 | This function makes the Gen
type synonym almost essential. Here’s what we would have had to write if we didn’t have Gen
:
generalPair :: (Seed -> (a, Seed)) -> (Seed -> (b, Seed)) -> (Seed -> ((a,b), Seed))
61 | Removing the unnecessary parentheses gets us this type signature, which might help you a little when implementing the function.
62 |generalPair :: (Seed -> (a, Seed)) -> (Seed -> (b, Seed)) -> Seed -> ((a,b), Seed)
63 | Write another version randPair_
using generalPair
, and test it by comparing its output to what you got from randPair
.
This generalPair
function can be generalized even more. Instead of always constructing pairs, you should be able to have a generalization that can construct anything. Your result shouldn’t be fixed to Gen (a,b)
. It should also be able to be Gen String
, Gen Polynomial
, or Gen BlogPost
. All you need to do is pass in a function that does the constructing with two inputs. Call this even more generalized function generalB
. Once you have it implemented, write a new generalPair2
function in terms of generalB
.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
By now you have probably realized that generating multiple random numbers this way is rather painful. You have to thread the output seed from one rand
call to the input of the next call. This is tedious and error prone, so now you will create a function to make this a little easier.
repRandom :: [Gen a] -> Gen [a]
53 | This function lets you give it a list of generators and it automatically handles the state threading for you.
54 |The nice thing about this function is that [Gen a]
is really general, so it composes well with other built-in list functions. For example:
repRandom (replicate 3 randLetter) (mkSeed 1)
56 | This function should generate the same three letters that you got from randString3
in challenge #2.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
In the previous exercise we wrote something that handled the threading of state through a list of generators. A simpler idea is to have a function that does one step of two generators and the necessary state threading. Now write a function called genTwo
that does this. Its first argument will be a generator. Its second argument will be a function that takes the result of the first generator and returns a second generator. The type signature looks like this:
genTwo :: Gen a -> (a -> Gen b) -> Gen b
53 | Implement this function.
54 |Now look at the implementation of your repRandom
function. It probably has one clause handling the empty list case. That case probably looks something like this:
repRandom [] s = ([], s)
56 | repRandom
was expecting a list of generators and it’s supposed to return a generator. In the empty list case it has no incoming generators to work with but it still has to return one. Essentially what’s happening here is that it has to construct a Gen
out of thin air. It turns out that this is a really common pattern. So let’s make a function for it. We’ll call this function mkGen
. It has to return a Gen a
. But it has to get the a from somewhere, so that will have to be the argument.
Implement mkGen
. Try to figure out the type signature yourself, but if you need help here it is hex-encoded: 6D6B47656E203A3A2061202D3E2047656E2061. You can decode it with this online hex decoder.
Congrats! You have finished the first set. There are a few repeating patterns lurking around. Lets see more examples in the following two sets, and we will provide a unified approach in Set 4. Don’t jump ahead! We have not seen all of them!
59 | 60 | 61 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
You may have noticed that MCPrelude doesn’t have the Maybe
type or anything from Data.Maybe. That’s because you’re going to build it all yourself.
IMPORTANT Again, it is imperative that you DO NOT CHEAT. Don’t look at any of the Maybe
stuff from Prelude or Data.Maybe. Don’t do it. Nobody is forcing you to do these exercises, so you should try to get the maximum possible benefit. IMPORTANT
First of all, you need to define the Maybe
type. It should be able to represent any value a
, as well as the case where no value a
exists. This type needs to represent failing values of any type, so it needs a type variable similar to what we saw in the Gen
type synonym. But this can’t be a type synonym because it has two constructors. Write this type yourself and get it to compile. Once you’ve gotten it compiling, check your answer by hex decoding the following:
64617461204D617962652061203D204E6F7468696E67207C204A7573742061
55 | You should use this definition and names going forward. We just wanted you to work on it yourself first.
56 |Then for convenience write a Show
instance for this new type.
instance Show a => Show (Maybe a) where
58 | -- show :: Maybe a -> String
59 | show = undefined
60 | If you’re having trouble with this, here is the hex encoded instance.
61 |202073686F77204E6F7468696E67203D20224E6F7468696E6722DA202073686F7720284A757374206129203D20224A7573742022202B2B2073686F772061
62 | You are also going to need an Eq
instance as well.
instance Eq a => Eq (Maybe a) where
64 | -- (==) :: Maybe a -> Maybe a -> Bool
65 | (==) = undefined
66 | We’re not going to give you the answer to this one. In the worst case scenario you should be able to figure it out from the way the Show
instance was done.
On the other hand, if writing these instances was too difficult, it might be good to go study some more introductory Haskell materials before continuing here.
68 | 69 | 70 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now that you have a Maybe
type, you need to use it. So now use it to build safe versions of several common functions specified by the Prelude.
headMay :: [a] -> Maybe a
53 | tailMay :: [a] -> Maybe [a]
54 | lookupMay :: Eq a => a -> [(a, b)] -> Maybe b
55 | divMay :: (Eq a, Fractional a) => a -> a -> Maybe a
56 | maximumMay :: Ord a => [a] -> Maybe a
57 | minimumMay :: Ord a => [a] -> Maybe a
58 | The functions headMay
and tailMay
are “safe” versions of the well known head
and tail
functions. The former returns the first element of a list or Nothing
if the list is empty. The latter returns a list containing all but the first element of a list, or Nothing
if the list is empty. The lookupMay
function is kind of like the lookup
for a map. Find the first tuple in the list where the first element is the equal to the passed in value and return the second element. If there is no matching a
, then return Nothing
. The divMay
function should return Nothing
if you’re dividing by 0
and the result of the division otherwise. maximumMay
and minimumMay
calculate the maximum and minimum respectively of all the numbers in the list, but if the list is empty they return Nothing
.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
In MCPrelude we have defined the following type synonym for you.
52 |type GreekData = [(String, [Integer])]
53 | We also provide two data structures greekDataA, greekDataB :: GreekData
that have some sample data.
Write a function to query the above data structures. Your function should have the following type signature:
55 |queryGreek :: GreekData -> String -> Maybe Double
56 | Your implementation of this function should use the functions you wrote in the previous exercise to do the following: first query the GreekData
that is passed in, look up the string passed in the second argument, and retrieve the corresponding list of Integers. Call this list xs. Next calculate the maximum of the tail of xs. (Don’t use any pattern matching here. Use case expressions and the maximumMay
and tailMay
functions you wrote in the last exercise.) Take the maximum and divide it by the head of the list (using your headMay
and divMay
functions). If any of these operations along the way return Nothing
, then your function should return Nothing
. But if everything succeeds, then return the final quotient. One hint… you’ll need to use the fromIntegral
function to convert your two Integers to Doubles for the final call to divMay
.
You will probably find this function pretty annoying to implement. Stick with us though… there is a point. The more you feel the pain now, the more the solutions will stick in your head later. Ultimately, we will get rid of the repeating part. But don’t jump ahead! We haven’t seen all patterns.
58 |Your function should generate the following results:
59 |queryGreek greekDataA "alpha" == Just 2.0
60 | queryGreek greekDataA "beta" == Nothing
61 | queryGreek greekDataA "gamma" == Just 3.3333333333333335
62 | queryGreek greekDataA "delta" == Nothing
63 | queryGreek greekDataA "zeta" == Nothing
64 |
65 | queryGreek greekDataB "rho" == Nothing
66 | queryGreek greekDataB "phi" == Just 0.24528301886792453
67 | queryGreek greekDataB "chi" == Just 9.095238095238095
68 | queryGreek greekDataB "psi" == Nothing
69 | queryGreek greekDataB "omega" == Just 24.0
70 | If your function threw any kind of exception on any of those inputs, then your implementation is wrong. Make sure your function always returns a Nothing
or a Just
in every case.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Write a function that calculates the product of the tail of a list. Use your tailMay
function and the product
function defined in the Prelude. This function should return Nothing
when passed an empty list, Just 1
when passed a list of one element (which is what the Prelude’s product
function does), and return the product of the tail for larger lists.
tailProd :: Num a => [a] -> Maybe a
53 | Now write a similar but slightly different function:
54 |tailSum :: Num a => [a] -> Maybe a
55 | These two functions have a lot in common. See if you can abstract out the commonality. To do this, write another function called transMaybe
that is the generalized version of both of these. Spend some time working on this before you look at the next hex encoded hint:
7472616E734D61796265203A3A202861202D3E206229202D3E204D617962652061202D3E204D617962652062
57 | Write a function with that type signature and then go back and implement tailProd
and tailSum
in terms of that function.
Now that you have that finished, use transMaybe
again, with maximumMay
and minimumMay
, to write two more functions tailMax
and tailMin
. These functions will have different type signatures than tailProd
and tailSum
. See if you can figure out what they should be. If you can’t figure it out, here’s a hex encoded hint.
7461696C4D6178203A3A204F72642061203D3E205B615D202D3E204D6179626520284D61796265206129
60 | That type signature is different from the ones above for tailProd
and tailSum
and less convenient to use. To collapse the two levels you’ll need another helper function. See if you can figure out what the type signature of this function should be and how to implement it. Call this function combine
. Here is its hex encoded type signature if you need help.
636F6D62696E65203A3A204D6179626520284D61796265206129202D3E204D617962652061
62 |
63 |
64 | A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Have you ever needed to generate the Cartesian product of some things? Let’s use the term combination to mean an element from the Cartesian product. In imperative languages generating combinations always seemed like a pain to me. We are going to explore how Haskell can make combination generation easier.
52 |First, write this function:
53 |allPairs :: [a] -> [b] -> [(a,b)]
54 | This function should generate all possible pairs of items from each of the two input lists. This means it should NOT have this behavior:
55 |allPairs [1,2,3] [4,5,6] == [(1,4),(2,5),(3,6)]
56 | That’s the zip
function and it’s not what we are looking for here. Instead, your function should generate this:
allPairs [1,2] [3,4] == [(1,3),(1,4),(2,3),(2,4)]
58 | Here is another test case:
59 |allPairs [1..3] [6..8] == [(1,6),(1,7),(1,8),(2,6),(2,7),(2,8),(3,6),(3,7),(3,8)]
60 | Note that because of the way we set up the project template you cannot use list comprehensions to solve this. That’s intentional. You should solve it with explicit recursion.
61 | 62 | 63 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
We can use the allPairs
function to do things like generate poker hands. In MCPrelude we have defined two lists called cardRanks
and cardSuits
. Try calling your allPairs
function on these:
allPairs cardRanks cardSuits == [(2,"H"),(2,"D"),(2,"C"),(2,"S"),(3,"H"),(3,"D"),(3,"C"),(3,"S"),(4,"H"),(4,"D"),(4,"C"),(4,"S"),(5,"H"),(5,"D"),(5,"C"),(5,"S")]
53 | But this isn’t a very nice representation. We want a more concise representation of the card to show our user. If you were writing a real poker-related program, instead of using a tuple you would probably create a data type Card
. Do that now and then write a Show
instance for it that returns the more concise representation "2H"
, "2D"
, etc.
show (Card 2 "h") == "2h"
55 | Now create a new function allCards
that does the same thing as your allPairs
function but uses your new Card
data type instead. It should have the following type signature:
allCards :: [Int] -> [String] -> [Card]
57 | This function should do the same thing as allPairs
, but with more concise output. When you write this function, don’t implement it using your previous allPairs
function. Rewrite it.
show (allCards cardRanks cardSuits) == "[2H,2D,2C,2S,3H,3D,3C,3S,4H,4D,4C,4S,5H,5D,5C,5S]"
59 |
60 |
61 | A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
As we have done before, look at your allPairs
and allCards
functions and find the differences. Then implement a more general function that can be used to implement both allPairs
and allCards
. Call this new function allCombs
.
allCombs :: (a -> b -> c) -> [a] -> [b] -> [c]
53 | Then go back and reimplement allPairs
and allCards
in terms of allCombs
. Verify that they do the same thing as the original functions.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Our allCombs
function is more general but it still only lets us generate combinations of two things. Now implement an allCombs3
function that generates combinations of 3 things. Don’t try to do anything fancy yet. Just use the most straightforward approach you can think of.
allCombs3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
53 | Here is example output to check your function.
54 |allCombs3 (,,) [1,2] [3,4] [5,6] == [(1,3,5),(1,3,6),(1,4,5),(1,4,6),(2,3,5),(2,3,6),(2,4,5),(2,4,6)]
55 |
56 |
57 | A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
You probably noticed that allCombs3
is getting painful to write. Worse still, it does not help us generate combinations of four or more things. We need to take a different approach if we want this to scale easily. This is a difficult step, so we are not going to make you figure it out yourself. All the same, spend some time thinking about how you might do it. Here is a hint though. You probably noticed that allCombs
has ([a] -> [b])
in its type signature and allCombs3
has ([a] -> [b] -> [c])
in its type signature. When you are playing with this, do not try to generalize that pattern to [[a]]
. That is not an adequate generalization because it only allows a
and has no b
or c
anywhere. Spend some time thinking about this before you continue. But don’t be discouraged if you can’t figure it out. This one is hard.
Back? If you figured it out, congratulations. If not, here is the hex encoded type signature for a function called combStep
that is the generalization we need.
636F6D6253746570203A3A205B61202D3E20625D202D3E205B615D202D3E205B625D
54 | Now write that function. Then use the combStep
function to implement allCombs
and allCombs3
and check that the new implementations have the same behavior as before. Once you do this it should be pretty obvious how you would use combStep
to implement allCombs4
and beyond. Also notice how combStep
compares to allCombs
.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now that you’ve spent some time building general abstractions for working with state and failing computations we want to go back through and look for commonalities.
52 |First, go back through all the code you wrote for Set 1, Find all the generalized functions and copy their type signatures (not the code) into a new file. You can skip the functions that solved special case exercises. Then do the same thing for Set 2 and copy those type signatures into the same file.
53 |Now look at these type signatures and look for functions from Set 1 that have a similar type signature pattern to functions from Set 2. The type signatures obviously won’t be the same, but there are similar patterns in both sets. You should come up with two pairs of function equivalences.
54 |Here is the hex encoded answer. But again, spend some effort trying to see this correspondence for yourself.
55 |67656E54776F207E3D206C696E6B2C2067656E6572616C42207E3D20794C696E6B
56 | Now that you have the correspondences, for each pair write a more general type signature that works for both the Gen
version and the Maybe
version. Think back to things we’ve done before. If two signatures are exactly the same except for one difference, replace that difference with a type variable. It doesn’t matter what letter you use for the type variable as long as it is different from all the other type variables that occur in the signature. But just to make your life easier we’ll give you a hint: use the letter m
. Here’s the hex-encoded answer:
67656E54776F2C206C696E6B0A20203A3A206D2061202D3E202861202D3E206D206229202D3E206D20620A0A67656E6572616C422C20794C696E6B0A20203A3A202861202D3E2062202D3E206329202D3E206D2061202D3E206D2062202D3E206D2063
58 | You won’t be able to implement these functions yet. We’re just writing type signatures right now and looking at the patterns involved.
59 | 60 | 61 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
We just found an equivalence between generalB
and yLink
. In Set 2 we implemented yLink
in terms of link
without using any cases. But in Set 1 you might not have implemented generalB
in terms of genTwo
. Go back and look at your generalB
implementation and if you didn’t write it in terms of genTwo
, do that now and call it generalB2
. Doing this should get rid of the state threading between generators.
Re-implement repRandom
in terms of generalA
, genTwo
, and mkGen
. Note that by using generalA
, genTwo
and mkGen
you should not need to have a seed
variable in your code for repRandom
anywhere.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now that we have identified a pattern that we want to abstract, we need to formalize it somehow. When we did an abstraction like this before, we took a repeated type signature expression and created a shorthand name for it with a type synonym and a type variable. The modification only affected type signatures and had nothing to do with the implementations. Now we are trying to find commonalities across two implementations of completely different problems, so a simple type synonym is not going to be enough for what we want to do.
52 |Clearly the random number example and the failing computation example have some similarities, but clearly they also have some differences. We’re trying to find the smallest set of fundamental primitives that have to be different. We have two candidates for those things: genTwo
/link
and generalB2
/yLink
. We just saw that generalB2
/yLink
can be written in terms of genTwo
/link
, so let’s assume that the genTwo
/link
abstraction is part of the fundamental set of primitives.
Whatever this pattern is that is common between random number generation and failing computations, we need to give it a name so we can talk about it more easily. Let’s call it a monad! You know how companies these days have been giving themselves nonsensical names that allow them to completely define their brand without competing with their customers’ preconceived notions of what common words mean? We’re doing the same thing here. (Well, mathematicians did it for us a while back.) Now that we have a name we need to create a type class:
54 |class Monad m where
55 | The generalized type signature for genTwo
/link
that you came up with in challenge #1 is one of the ones we want to put into our type class, and if you used the type variable m
, you should be able to drop it in. All we need is a name. Let’s use the name bind
.
Now that we have part of our type class your task is to create a single unified implementation for generalB2
/yLink
. Most of it should be the same, but you’ll find that there is one part that is different for the two. Make that part into the second function of the type class. Call this function return
. Figure out what the type signature should be. We’ve seen this pattern before in Set 1 and Set 3.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now you have a Monad
type class with two functions: bind
and return
. If you’ve heard anything about monads in the past, this might sound familiar. But don’t go off and look at existing monad code yet. You have a lot more ground to discover yourself.
The next thing you need to do is create instances of your Monad
type class for the three types we’ve been working with: Gen
, Maybe
, and []
. You can do this for Maybe
and []
with no trouble. But Gen
won’t work because it is a type synonym. First you need to replace your type synonym with a newtype
. Don’t go back and modify your old code. We’ll be referring back to that in the future. Do this work in a new file Set4.hs (again using the same header we’ve been using and importing MCPrelude
instead of Haskell’s Prelude). You can import Set2 because that has your Maybe
data type and associated code which can be reused. But don’t import Set1. When you are finished with this challenge you should have a Set4.hs file with a Monad
type class, a Gen
newtype, and three instances.
Along with your Gen
newtype you also might want to write a helper function to make it easier to get your random values out:
evalGen :: Gen a -> Seed -> a
55 | If you did all the exercises properly this should be pretty straightforward. The Monad
instance for Gen
may be a little bit less obvious because this time you’ll have to do some newtype wrangling.
If you need to brush up your knowledge of some of these topics, check out this chapter on newtypes or this chapter on type classes in Learn You a Haskell.
57 |If you’re having trouble with the newtype instance here’s a hex encoded code template to get you started:
58 |6E6577747970652047656E2061203D2047656E207B2072756E47656E203A3A2053656564202D3E2028612C205365656429207DDADA696E7374616E6365204D6F6E61642047656E207768657265DA2020202072657475726E203D202E2E2EDA2020202062696E64203D202E2E2E
59 | Again, if possible try not to use this. We’re still leaving the hard bits out. These things are worth fighting with for awhile.
60 | 61 | 62 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now that we have our monad type class and an understanding of what motivated it, go back and rewrite the generic functions from sets 1, 2, and 3 using your Monad
type class. But since we’re rewriting them generically we need to give them new names. Here are the functions that you should rewrite and the new names that you should use. From set 1:
repRandom
(sequence
)generalB
(liftM2
)From set 2:
57 |chain
(=<<
)yLink
(liftM2
)combine
(join
)From set 3:
63 |allCombs
(liftM2
)allCombs3
(liftM3
)combStep
(ap
)You only need to write liftM2
once. But instead of using Gen
, Maybe
, or []
you use m
in its place with the Monad m
type class constraint. Try to do this set without looking at the code you used before. Just copy all the type signatures and redo the work, but this time do everything in terms of return
and bind
.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now your Set4.hs module has fairly decent set of abstract tools all built on the Monad
interface. Now go back through sets 1, 2, and 3 and redo them all using the library of functions you built up in Set4.hs. Since Gen
is now a newtype you will have to make some changes to the functions that use it. This may seem like a waste of time, but it will develop your familiarity with the names actually used by Haskell’s monad library. This is the core of developing a working knowledge of monads. The first three sets were the motivation. Now we’re getting to the real world use.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Now that you’ve spent some time using the monad abstraction, you may have seen that while it does what we need, it’s a little more verbose than we would like. This is where Haskell’s do notation comes to the rescue. When you write the following do block:
52 |rule1 = do
53 | foo <- calcFoo
54 | bar foo
55 | GHC automatically desugars this to
56 |rule1 = bind calcFoo (\foo -> bar foo)
57 | It’s important to note what this says about the type signatures of everything involved.
58 |rule1 :: m b
59 | calcFoo :: m a
60 | bar :: a -> m b
61 | The key is that whatever m
is, it must be the same for all three of these types. Also, calcFoo
returns an m a
, but bar
takes a plain a
. The bind function is responsible for “unboxing” the m a
and passing the unboxed value to bar
.
One thing that trips a lot of people up is what to do when they don’t have a function that looks like bar
. The version of bar
they want might instead have this type bar :: a -> b
(note that here we’re specifically saying that bar
’s type signature does NOT have an m
). Figure out what to do in this situation.
Note this challenge isn’t concrete. We’re dealing with more abstract types that don’t have type signatures exactly the same as the ones you’ve been working with. We could have written this challenge in a concrete way, but we think it’s important to learn how to think in more abstract terms. So for this go back to the stuff you have done before and find something that you think can be written with the do block that rule1
has. Or better yet, make up a new function that does it using Maybe
or Gen
.
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
In the last exercise, we noted that the following code:
52 |rule1 = do
53 | foo <- calcFoo
54 | bar foo
55 | Is automatically de-sugared to:
56 |rule1 = bind calcFoo (\foo -> bar foo)
57 | In Haskell, the bind
function is conventionally written as the >>=
operator, which can be written infix style like this:
rule1 = calcFoo >>= (\foo -> bar foo)
59 | In order for the do syntax to work correctly, we need to change our Monad
class to have a >>=
operator. Create a class like this:
class Monad m where
61 | (>>=) :: m a -> (a -> m b) -> m b
62 | return :: a -> m a
63 |
64 | fail :: String -> m a
65 | fail = undefined
66 | (Note: for historical reasons, Monad
is required to have a fail
function. We will not be concerning ourselves with failure here, so we just leave this as undefined. When you implement Monad
for your own data types, you should only implement the >>=
and return
functions)
In the next few sections, we will be rewriting the earlier exercises using do syntax.
68 | 69 | 70 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
First, create an instance of Monad
for your Gen
newtype, similar to what you did in Set 4. You will also probably want to create an evalGen :: Gen a -> Seed -> a
function as well.
With your Monad
instance, you should be able to use do syntax. Recall that in Set 1 we had a function called rand :: Seed -> (Integer, Seed)
. Create a new function makeRandom :: Gen Integer
which wraps the rand
function inside your new type.
Next, use do syntax to re-create the following function from Set 1:
54 |fiveRands :: Gen [Integer]
55 | To check that you created this function correctly, recall that the product of the five numbers you generate when passing in a seed of mkSeed 1
is 8681089573064486461641871805074254223660.
Once that has been created correctly, create randLetter :: Gen Char
and use it to create randString3 :: Gen String
, which creates a String
of three random characters. If you have an initial seed of 1, when you use this site to calculate the SHA-256 hash of the output of randString3, you get 9d475eb78d3e38085220ed6ebde9d8f7d26540bb1c8f9382479c3acd4c8c94a3.
Lastly, go ahead and create this general function:
58 |generalPair :: Gen a -> Gen b -> Gen (a, b)
59 |
60 |
61 | A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
As with the previous set, you will want to create a Monad
instance for your Maybe data
type.
Once that is accomplished, rewrite the following functions using do syntax:
53 |queryGreek :: GreekData -> String -> Maybe Double
54 | addSalaries :: [(String, Integer)] -> String -> String -> Maybe Integer
55 | tailProd :: Num a => [a] -> Maybe a
56 | tailSum :: Num a => [a] -> Maybe a
57 | tailMax :: Ord a => [a] -> Maybe a
58 | You should import Set2 so that you can use the helper functions you wrote in that set.
59 |You can test queryGreek
with the following assertions:
queryGreek greekDataA "alpha" == Just 2.0
61 | queryGreek greekDataA "beta" == Nothing
62 | queryGreek greekDataA "gamma" == Just 3.3333333333333335
63 | queryGreek greekDataA "delta" == Nothing
64 | queryGreek greekDataA "zeta" == Nothing
65 |
66 | queryGreek greekDataB "rho" == Nothing
67 | queryGreek greekDataB "phi" == Just 0.24528301886792453
68 | queryGreek greekDataB "chi" == Just 9.095238095238095
69 | queryGreek greekDataB "psi" == Nothing
70 | queryGreek greekDataB "omega" == Just 24.0
71 |
72 |
73 | A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
Again, create a Monad
instance for your []
type. Import your Card
data constructor from Set 3. Using do syntax, implement the following functions:
allPairs :: [a] -> [b] -> [(a,b)]
53 | allCards :: [Int] -> [String] -> [Card]
54 | allCombs3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
55 |
56 |
57 | A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
It may not seem like the problems in this set have much to do with monads at first, but bear with us. We are taking you through the motivations for monads in a step by step structured way. It may be tempting to skip over this stuff. DON’T DO IT. Later problem sets build on things you did here. Stick with us and try to absorb the ideas and patterns.
52 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
This set deals with programming patterns that arise when dealing with computations that might fail. Failing computations come up all the time in the real world and Haskell can provide great tools for dealing with them.
52 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
In this set we are going to investigate lists. As usual, make sure you start with the standard block of header code we’ve been using for these challenges. If you don’t do this, you might inadvertently solve these problems in a way that misses the whole point of the exercise.
52 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
A set of challenges for jump starting your understanding of monads.
20 | 21 |
44 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
45 |
In this set, we are going to rewrite the challenges we completed in earlier sets using do syntax. Do syntax is a common form of syntactic sugar that you will see when writing Haskell, but it is functionally equivalent to previous functions we’ve already created.
52 |A set of challenges for jump starting your understanding of monads.
20 | 21 |
36 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
37 |