Appetite
How do I suppose to understand this short code?
sequ2 :: [String]
sequ2 = do
x <- ["World", "Lady"]
y <- list4 "Hello" x
list5 "Good Morning" y
This tutorial will guide you step by step, from Functor <$>, Applicative <*>, Bind »=, until do notation.
Explaining Monad
This tutorial/ guidance/ article is one of some parts.
-
Overview: Summary.
-
References: About Monad.
-
Examining Bind: Bind »= operator. Hello World Example.
-
Examining Bind: <*> and <$> operators. Personal Notes. Example using Number.
-
Monadic Operator: Fish >=> operator.
The first one is overview, then some references. The last three parts is all about Example Code.
Preface
I’m not pretending that I know Monad. I’m staying away from dark arts. Instead I’m examining behaviour of this almost magic bind »= operator. Gentle, step by step. Reference about bind »= operator can be found here.
Dotfiles
I’m using GHCI to show each step of this tutorial. I put all the function in one module. This module should be loaded in GHCI.
In module:
module MyFunc
( unboxStr
, putUnbox
, say1, say2, say3, say4
, list1, list3, list4, list5
) where
...
...
In GHCI:
GHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for help
Prelude> :l MyFunc
[1 of 1] Compiling MyFunc ( MyFunc.hs, interpreted )
Ok, modules loaded: MyFunc.
*MyFunc>
Wrapping and unwrapping
Functor, Applicative and Monad, can be seen as a concept of unwrapping and wrapping data. The box that wrap the data could be IO, list or the simple Maybe context.
These three are standard spells from the book of white wood witch.
This tutorial use String in Maybe context. In order to print, we need to unwrap the context first.
In module:
unboxStr :: Maybe String -> String
unboxStr (Just text) = text
unboxStr Nothing = ""
In GHCI:
> putStrLn $ unboxStr (Just "Hello World")
Hello World
Consider a more useful function
In module:
putUnbox :: Maybe String -> IO ()
putUnbox (Just text) = putStrLn text
putUnbox Nothing = return ()
In GHCI:
> putUnbox (Just "Hello World")
Hello World
You don’t need this function in GHCI. But you are going to need this when you do compile in command line.
Simple Data Type: One Argument
Consider a String -> String function. Start with simple.
In module:
say1 :: String -> String
say1 str = "Hello " ++ str
In GHCI:
> say1 "World"
"Hello World"
In GHCI: with Functor <$>
Functor takes any function that can be mapped. Whether Maybe context or a list can be mapped.
Here the data (Just “World”) unwrapped into "World". And the result wrapped back.
> say1 <$> (Just "World")
Just "Hello World"
We can see that from another perspective. Functor operate inside the list.
> say1 <$> ["World", "Lady"]
["Hello World","Hello Lady"]
In GHCI: with Applicative <*>
ApplicativeFunctor also wrapped the function. So both function and data are wrapped. And the result also wrapped. Here we have two wrapper example, Maybe context as wrapper, and list as wrapper.
> (Just say1) <*> (Just "World")
Just "Hello World"
> [say1] <*> ["World", "Lady"]
["Hello World","Hello Lady"]
I love girl. Treat a girl like a lady.
Simple Data Type with Two Argument
We need this two argument to show how to combine Functor and ApplicativeFunctor.
In module:
say2 :: String -> String -> String
say2 str1 str2 = str1 ++ " " ++ str2
In GHCI:
We can greet plainly.
> say2 "Hello" "World"
"Hello World"
Or Curry the function.
> (say2 "Hello") "World"
"Hello World"
Or even using function application
> (say2 "Hello") $ "World"
"Hello World"
In GHCI: using functor <$>
And get the wrapped result.
> (say2 "Hello") <$> (Just "World")
Just "Hello World"
> (say2 "Hello") <$> ["World", "Lady"]
["Hello World","Hello Lady"]
fmap is just another name for infix <*> operator.
> fmap (say2 "Hello") (Just "World")
Just "Hello World"
> fmap (say2 "Hello") ["World", "Lady"]
["Hello World","Hello Lady"]
In GHCI: with Applicative <*>
Get wrapped, both function and input argument.
> Just (say2 "Hello") <*> (Just "World")
Just "Hello World"
> [say2 "Hello"] <*> ["World", "Lady"]
["Hello World","Hello Lady"]
In GHCI: with Functor <$> for both arguments
Now the function have a way to operate inside each wrapped argument.
> say2 <$> (Just "Hello") <*> (Just "World")
Just "Hello World"
> say2 <$> ["Hello"] <*> ["World", "Lady"]
["Hello World","Hello Lady"]
Summary of Functor and Applicative
I hope that you see these patterns clearly. However, these above are just an example of wrapping and unwrapping. There is a more complex pattern, that is bind »= operator.
Source Code can be found here.
Bind Introduction
Consider our last simple function.
In module:
say1 :: String -> String
say1 str = "Hello " ++ str
We can add a failsafe feature using Maybe context as an output.
say3 :: String -> Maybe String
say3 str =
if (length str) /= 0
then Just ("Hello " ++ str)
else Nothing
In GHCI:
Now the output already wrapped. Even with plain call.
> say3 "World"
Just "Hello World"
Before thinking about chaining, we can rewrite as below.
> (Just "World") >>= say3
Just "Hello World"
You can imagine how the bind operator works. Just like ApplicativeFunctor, bind does unwrapping and wrapping operation.
Chaining
Here comes the most interesting part. Monad get it feed from output of sequence. This is what applicative and functor cannot do.
Consider rewrite the function with two arguments, and with Maybe context feature as an output.
In module:
say4 :: String -> String -> Maybe String
say4 text greet =
if ((length text) /= 0) && ((length greet) /= 0)
then Just (greet ++ text)
else Nothing
In GHCI:
Here we can chain each function using bind »= operator.
> Just "Hello " >>= say4 "world," >>= say4 " How are you ?"
Just "Hello world, How are you ?"
Or using the flip version, reverse bind =« operator for convenience.
say4 " How are you ?" =<< say4 "world," =<< Just "Hello "
How does it works ?
The Just “Hello" unwrapped into plain String "Hello". And the rest follow. We have already make the output wrapped in box.
Binding List
In module:
Now consider the Functor version that we already have.
say1 :: String -> String
say1 str = "Hello " ++ str
Please pay attention to the function declaration.
say1 :: String -> String
In GHCI:
> ["World"] >>= say1
"Hello World"
> say1 =<< ["Lady"]
"Hello Lady"
> ["World, ", "Lady"] >>= say1
"Hello World, Hello Lady"
> ["World"] >>= say2 "Hello"
"Hello World"
This is not a good example, since it does not apply to numeric. We should move on to the next example.
Bind Example Using Simple List
Now you can have the idea on how the list binded with the Monad operator. By examining the data type in function declaration.
Consider this simple function example.
In module:
list1 :: [String] -> [String]
list1 str = map ("Hello " ++) str
In GHCI:
We can map each element in this list easily.
> list1 ["World", "Lady"]
["Hello World","Hello Lady"]
Please pay attention to the function declaration.
list1 :: [String] -> [String]
Now consider a list function made for bind. We can rewrite it without map function.
In module:
list3 :: String -> [String]
list3 str =
if (length str) /= 0
then ["Hello " ++ str]
else []
Please pay attention to the function declaration.
list3 :: String -> [String]
In GHCI:
We can map each element in this list easily.
> ["World", "Lady"] >>= list3
["Hello World","Hello Lady"]
How about empty list ?
> [] >>= list3
[]
The if then else for empty list looks pretty useless in this function. We are going to have the need for it in the next function. Or you can just skip it.
List with Two Argument
Consider these two functions with list output.
In module:
list4 :: String -> String -> [String]
list4 greet text =
if ((length text) /= 0) && ((length greet) /= 0)
then [greet ++ " " ++ text]
else []
list5 :: String -> String -> [String]
list5 greet text =
if ((length text) /= 0) && ((length greet) /= 0)
then [text ++ " " ++ greet]
else []
Please pay attention to the function declaration.
list4 :: String -> String -> [String]
list5 :: String -> String -> [String]
In GHCI:
We can bind different function together.
> ["World", "Lady"] >>= list4 "Hello" >>= list5 "Good Morning"
["Hello World Good Morning","Hello Lady Good Morning"]
Or using flip version operator, as needed, for conveninence, with about the same result.
> (list4 "Hello" =<< ["World", "Lady"]) >>= list5 "Good Morning"
["Hello World Good Morning","Hello Lady Good Morning"]
Summary for Bind
We can examine how bind »= can chain different functions together in such order. The real world may have more complex case, not just different functions, but also functions with different input and output.
Source Code can be found here.
Sequencing
By chaining different function together in such order, we have already make a sequence. One step closer to have sequence of command.
Consider our last chain, using vanilla monadic code.
> ["World", "Lady"] >>= list4 "Hello" >>= list5 "Good Morning"
We can rewrite in a function, still with vanilla monadic code as below
sequ1 :: [String]
sequ1 =
["World", "Lady"]
>>= list4 "Hello"
>>= list5 "Good Morning"
Run this in command line, we will have the same result.
main = print $ sequ2
Now we can convert it, for use with do notation.
sequ2 :: [String]
sequ2 = do
x <- ["World", "Lady"]
y <- list4 "Hello" x
list5 "Good Morning" y
In every command inside do notation, each must have the same data type with the function output. It is the list, [String].
Summary for Do notation
do notation, make sequence of command possible. Well, I’m still not sure that I have understand Monad. But I’m sure that many times I greet the World and Lady.
I like the World. I like a Girl.
Source Code can be found here.
Thank you for Reading.