now, we will see how we make monads,.
the guideline of making hte monad is not to make a monad for the purpose of monad. normally, we usually make a type that whose purpose is to model an aspect of some problem and then later on if we see that the type represents a value with a context and can act like a monad, we give it a Monad instance.
as agin, we will use some real examples to show the idea.
suppose that we have a list of [3,5,9], and when we feed that to the >>= operator, we will just all the possible choice of taking an element from teh list adn applying the function to it and then presents the rsult in a list as well.
there is no information telling the possiblity of each of those number's occuring. suppose that we want to have 50% percent for the appearance of 3, and 25% each of 5, and 9.. so we might get the following structure.
[(3,0.5),(5,0.25),(9,0.25)]
Haskell offers us a type for rational types, it is "Rational" and lives in "Data.Ratio", we write it as if it were a fraction, the numerator and the denominator are separated by a %, here are a few examples.
ghci> 1%4 1 % 4 ghci> 1%2 + 1%2 1 % 1 ghci> 1%3 + 5%4 19 % 12
so the number wil be represented as below.
ghci> [(3,1%2),(5,1%4),(9,1%4)] [(3,1 % 2),(5,1 % 4),(9,1 % 4)]
to better represent that we will create a new type, and the type is called Prob which takes a parameter, a,
here it is.
import Data.Ratio newtype Prob a = Prob { getProb :: [(a,Rational)] } deriving Show
next is to consider whether or not to make it a functor, the list is a functor, so probably it is good to make it part of functor, but we are only interested in the number itself, and we will leave the possiblity as they are.
instance Functor Prob where fmap f (Prob xs) = Prob $ map (\(x,p) -> (f x,p)) xs
Now the big question, is this a monad?return x is supposed to make a monadic value that always presents x as its result, so it doesn't make sense for the probability to be 0. If it always has to present it as its result, the probability should be 1!
What about >>=? so let's make use of the fact that m >>= f always equals join (fmap f m) for monads and think about how we would flatten a probability list of probability lists.
Here's this situation expressed as a probability list:
thisSituation :: Prob (Prob Char) thisSituation = Prob [( Prob [('a',1%2),('b',1%2)] , 1%4 ) ,( Prob [('c',1%2),('d',1%2)] , 3%4) ]
and we must invent some way to calculate the possiblity of individuals under a possiblity group, and here is it.
flatten :: Prob (Prob a) -> Prob a flatten (Prob xs) = Prob $ concat $ map multAll xs where multAll (Prob innerxs,p) = map (\(x,r) -> (x,p*r)) innerxs
what it does for the multAll is to take a possibility p and applies that to each of the possiblty of the inner possibility group with p, return a list of items and new possiblity.
now, we have all that, now let's make a monad instance out of it.
instance Monad Prob where return x = Prob [(x,1%1)] m >>= f = flatten (fmap f m) fail _ = Prob []
the reason to return 1 as the possibility is because when a item happens, you know for sure that the possibility is 1. we uses flattern because we already have a jon method named, and the flattern method has the ability to calculate conditional possibility . and when it fails, it shall return a empty list.
there is a very important point of writing haskell monad, you might want to verify that monad that you write conforms to the monad rules. the rules that we shall verify include: 1. return and then fmap , the possibility should be 1 %1 multply the possiblity returned by f; 2. f <=< (g <=h ) should be the same as (f <=< g) <=< h, this holds as well, because it holds for the list monads which forms the basis of hte possiblity monad.
Now, we have the monad, what can we do with it?
Say we have two normal coins and one loaded coin that gets tails an astounding nine times out of ten and heads only one time out of ten. If we throw all the coins at once, what are the odds of all of them landing tails? First, let's make probability values for a normal coin flip and for a loaded one:
data Coin = Heads | Tails deriving (Show, Eq) coin :: Prob Coin coin = Prob [(Heads,1%2),(Tails,1%2)] loadedCoin :: Prob Coin loadedCoin = Prob [(Heads,1%10),(Tails,9%10)]
and finally, the coin throwing actions.
import Data.List (all) flipThree :: Prob Bool flipThree = do a <- coin b <- coin c <- loadedCoin return (all (==Tails) [a,b,c])
Giving a go, we see te odds of all three landing tails are not that good, despites cheating with our laoded coin:
ghci> getProb flipThree [(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40), (False,1 % 40),(False,9 % 40),(False,1 % 40),(True,9 % 40)]
we know that even with the loade coin, we are not that lucky to get a wiin.
In this post, we went from having a question (what if lists also carried information about probability?) to making a type, recognizing a monad and finally making an instance and doing something with it. I think that's quite fetching! By now, we should have a pretty good grasp on monads and what they're about.