iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
💯

"I Completely Understand Haskell Monads" Quiz

に公開

I've studied Haskell, but I'm not confident I can say I truly understand monads...

\ I've created examination questions for people like that! /

If you get 8 out of 10 questions correct on the following exam, you completely understand Haskell's monads. I guarantee it!

Let's get started right away~~

Question 1

First, let's relax.

Haskell's Monad is a ○○○○

Which of the following options fits into the ○○○○?

  1. Type
  2. Function
  3. Type class
  4. Type synonym
Answer

3. Type class

This is easy!

Haskell's monad is simply a type class.

Question 2

class A t => B t where
    ...

Let's represent this kind of type class constraint relationship with a graph like this:

In this case (in GHC 9.0.1):

What is the type class that fits into XXXX? In other words, what is the type class constraint of Monad?

  1. Pointed
  2. Applicative
  3. Alternative
  4. Traversable
Answer

2. Applicative

As a side note, in the Haskell 2010 Language Report, Monad actually had no type class constraints. The current Applicative constraint was implemented starting from GHC 7.10 based on the Functor-Applicative-Monad Proposal.

Question 3

In the following type class definitions, what is the type of the bind operator represented by ????

class Functor f where
    fmap :: (a -> b) -> (f a -> f b)

class Functor f => Applicative f where
    pure  :: a -> f a
    (<*>) :: f (a -> b) -> (f a -> f b)

class Applicative m => Monad m where
    (>>=) :: ???
  1. m a -> m a -> m a
  2. (a -> m a) -> m a
  3. (a -> m b) -> f a -> m (f b)
  4. m a -> (a -> m b) -> m b
Answer

4. m a -> (a -> m b) -> m b

1 is the type for MonadPlus, 2 is for MonadFix, and 3 is the type for a method of Traversable.

Question 4

Monads are type classes, but their implementations are implicitly required to satisfy relationships called monad laws.

Along with the following two monad laws:

1. pure x >>= f    === f x
2.      m >>= pure === m

What is the remaining one? (Note that === indicates that the values on the left and right are equal.)

  1. (m >>= f) >>= g === m >>= (\x -> f x >>= g))
  2. pure id >>= (\f -> m >>= (\x -> f x)) === m
  3. pure (g . f) >>= (\h -> m >>= (\x -> h x)) === (pure g >>= (\h -> m >>= (\x -> h x))) . (pure f >>= (\h -> m >>= (\x -> h x)))
  4. u >>= (\f -> pure x >>= (\x -> f x)) === pure ($ x) >>= (\f -> u >>= (\x -> f x))

(This might be a throwaway question...)

Answer

1. (m >>= f) >>= g === m >>= (\x -> f x >>= g))

Having this monad law ensures that we don't need to worry about the order of composition for actions, which is a useful law that guarantees we can separate parts of the logic into functions.

2 and 3 are rewritten versions of the Functor laws:

fmap id === id
fmap (g . f) === fmap g . fmap f

4 is similarly a rewrite of the Applicative law:

u <*> pure y === pure ($ y) <*> u

Question 5

Monads are just type classes, but they are special in Haskell in that a specific syntax called do notation is provided.

However, do notation is just syntactic sugar, so it can be easily converted into code that does not use do notation.

main = do
    putStrLn "What is your name?"
    name <- getLine
    let message = "Hello " ++ name
    putStrLn message

Which of the following is the correct code that exhibits the same behavior as the code above?

1. main = putStrLn "What is your name?" >>=       getLine    (\name -> let message = "Hello " ++ name in        putStrLn message)
2. main = putStrLn "What is your name?" >>=       getLine >>= \name -> let message = "Hello " ++ name in        putStrLn message
3. main = putStrLn "What is your name?" >>= \_ -> getLine >>= \name -> let message = "Hello " ++ name >>= \_ -> putStrLn message
4. main = putStrLn "What is your name?" >>= \_ -> getLine >>= \name -> let message = "Hello " ++ name in        putStrLn message
Answer
4. main = putStrLn "What is your name?" >>= \_ -> getLine >>= \name -> let message = "Hello " ++ name in        putStrLn message

The other options will result in errors and cannot be executed.

Question 6

Within the same do block, you can only use functions belonging to the same monad.
What monads are the two do blocks in the code below respectively?

  1. ① Maybe, ② Either
  2. ① Either, ② IO
  3. ① Either, ② Maybe
  4. ① Identity, ② Maybe
Answer

3. ① Either, ② Maybe

Once you become conscious of which monad the do notation in your code belongs to, monads will increasingly look like "contexts."

Question 7

This is a question about the list monad.

Let's consider a program that lists combinations of four numbers from 0 to 9 that can result in 10 using basic arithmetic operations (+, -, *, /) (similar to the "Make 10" puzzle often played with car license plates when you're bored).

The guard function used in make10 is used within the list monad and executes the subsequent processes only when the boolean value of the first argument is True.

Please implement guard to complete the following program.

guard :: Bool -> [()]
guard = undefined

make10 :: [[Double]]
make10 = do
    x   <- [0..9]
    op1 <- [(+), (*), (-), (/)]
    y   <- [0..9]
    op2 <- [(+), (*), (-), (/)]
    z   <- [0..9]
    op3 <- [(+), (*), (-), (/)]
    w   <- [0..9]
    let result = x `op1` y `op2` z `op3` w

    -- subsequent processes will not be executed if result does not equal 10
    guard $ result == 10

    pure [x, y, z, w]

main :: IO ()
main = mapM_ print (take 5 make10)

Running it will behave like this:

> runhaskell Main.hs
[0.0,0.0,1.0,9.0]
[0.0,0.0,2.0,8.0]
[0.0,0.0,2.0,5.0]
[0.0,0.0,3.0,7.0]
[0.0,0.0,4.0,6.0]

When checking if your implementation is correct, using Replit is the quickest way, so please take advantage of it:
https://replit.com/new/haskell

Answer
guard :: Bool -> [()]
guard False = []
guard True  = [()]

By the way, guard can be generalized as a function related not only to lists but also to the Alternative type class.

guard :: Alternative f => Bool -> f ()

Question 8

Implement the State monad, which falls into the more difficult category among monads, without looking at anything!

Please fill in the undefined parts in the code below.

newtype State s a = State { runState :: s -> (a, s) }

instance Functor (State s) where
    fmap f (State g) = State $ \s ->
        let (  a, s') = g s
         in (f a, s')

instance Applicative (State s) where
    pure a = State $ \s -> (a, s)
    (State f) <*> (State g) = State $ \s ->
        let (  h, s' ) = f s
            (  a, s'') = g s'
         in (h a, s'')

instance Monad (State s) where
    (State f) >>= m = undefined

If you get stuck, try writing the type of (>>=) for the State monad.

Answer
instance Monad (State s) where
    (State f) >>= m = State $ \s ->
        let (a, s') = f s
         in runState (m a) s'

Compared to the Applicative implementation, this one is quite simple!

Question 9

Even if an entirely unknown type appears, if that type is an instance of Monad, you can understand how to use it to some extent.

Below, let's consider a database (Key-Value store) called Redis. Redis is a database where you can save values associated with specific keys, much like a dictionary (Map) data structure.

  • Function to save a value to Redis
  • Function to get a value from Redis

Assume the following functions are provided:

-- | Function to save a value to Redis
set :: String   -- Key for the value
    -> String   -- Value to save
    -> Redis ()

-- | Function to get a value from Redis
get :: String                -- Key for the value
    -> Redis (Maybe String)  -- Obtained value (returns Nothing if the key does not exist)

And suppose you have confirmed from the documentation that the Redis type is an instance of Monad as follows.

Now, using the functions above, implement a function that behaves as follows:

  • Retrieve the value for the key "foo" from Redis.
  • If the value exists, save that value to the key "bar". If it doesn't exist, save the value "hoge" to the key "bar".
example :: Redis ()
example = do
   ...
Answer
example :: Redis ()
example = do
   mvalue <- get "foo"
   case mvalue of
     Just value -> set "bar" value
     Nothing    -> set "bar" "hoge"

If you know it is an instance of Monad, you can figure out how to compose values and functions of that type without knowing the details of the type itself.

This question was created based on an actual library called hedis.

In fact, the example function created this way can be converted to IO and executed using the following function:

runRedis :: Connection -> Redis a -> IO a

Connection is a data type that includes connection information and so on.

Question 10

The Control.Monad module in base provides functions like the following:

join :: Monad m => m (m a) -> m a

This function can be implemented using (>>=) as follows:

join m = m >>= id

Conversely, if join were given first, you could use join to implement (>>=), and thus the Monad instance.

Please try implementing (>>=) using join. You may use Functor or Applicative methods.

instance Monad m where
    m >>= k = undefined
        where
            join :: m (m a) -> m a
            join = {- Assume this implementation is known -}
Answer
instance Monad m where
    m >>= k = join (fmap k m)
        where
            join :: m (m a) -> m a
            join = {- Assume this implementation is known -}

In this way, if one of (>>=) or join is defined, the implementation for the other is automatically derived.

In principle, you could adopt join as the Monad method instead of (>>=).

A monad is just a monoid in the category of endofunctors. What's the problem?

As the saying goes, join is a function deeply involved with this "monoid object" concept.


The exam is now over! How did you do?

If you got 8 out of 10 questions correct, you completely understand Haskell's monads!
You shouldn't have any trouble using Monad in your daily life. 👀

If you unfortunately couldn't solve 8 questions, don't worry—nothing bad will happen! 😅
This article is meant to be a guide to gauge whether you understand Haskell's monads, which are often called difficult, so if you feel you need more study, please review and try again. There are countless monad tutorials in the world, so you won't run out of study material. If you don't know where to start, please refer to this link collection.

Congratulations to those who got 8 or more correct!
Keep working towards reaching the "I have no idea. I understand a little bit" stage that lies beyond "completely understood." (Needless to say, this wording refers to the "Dunning-Kruger effect".)

Don't forget to tweet how many questions you solved!


\ Thank you for reading! /

This article was written during the "87th Haskell-jp Mokumoku-kai @ Online."

Haskell-jp will also host an online event called Haskell Day 2021 on November 7, 2021, so please check it out!

If you enjoyed this article, I'd appreciate it if you could give it a Like ♡ ☺️
Sending a badge would also be great encouragement for me to write the next article! 🙌

Discussion