欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

haskell - Monads - the list monad

程序员文章站 2024-01-10 18:56:34
...

in this post, we will going to examine the list a monad, first let's see what is the definition of the list monad, here is the definition of the 

 

instance Monad [] where  
    return x = [x]  
    xs >>= f = concat (map f xs)  
    fail _ = []  

 

 

since list are also  monad, and given the definition of the >>=, here is what you can get from the list monad. 

 

 

ghci> [3,4,5] >>= \x -> [x,-x]  
[3,-3,4,-4,5,-5]  

 the [] is now the Nothing, as can be told by this: 

 

 

ghci> [] >>= \x -> ["bad","mad","rad"]  
[]  
ghci> [1,2,3] >>= \x -> []  
[]  

 

 

 we can chain the [] as we do the Maybe type. 

 

 

ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)  
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]  

 this is like the list comprehension, if we write the code in the do notation. 

 

 

 

listOfTuples :: [(Int,Char)]  
listOfTuples = do  
    n <- [1,2]  
    ch <- ['a','b']  
    return (n,ch)  

 

 

and we can as well use the special syntax which has a name called "list comprehension".

 

 

ghci> [ (n,ch) | n <- [1,2], ch <- ['a','b'] ]  
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]  

 

 

but the list comprehension allow us to filter the output ,an example is as follow. 

 

 

ghci> [ x | x <- [1..50], '7' `elem` show x ]  
[7,17,27,37,47]  

 

 

how does that translate to the list monad? we will need to see the guard method and the MonadPlus type class, first let's see the MonadPlus definition. 

 

 

class Monad m => MonadPlus m where  
    mzero :: m a  
    mplus :: m a -> m a -> m a  

 Monad Plus can also act as monoid. (not mean that it is a monoid?)

 

which is shown in the following MonadPlus [] instance definition. 

 

instance MonadPlus [] where  
    mzero = []  
    mplus = (++)  

 

 

so, how is the guard method implememented? 

 

 

guard :: (MonadPlus m) => Bool -> m ()  
guard True = return ()  
guard False = mzero  

 so we can have this: 

 

 

ghci> guard (5 > 2) :: Maybe ()  
Just ()  
ghci> guard (1 > 2) :: Maybe ()  
Nothing  
ghci> guard (5 > 2) :: [()]  
[()]  
ghci> guard (1 > 2) :: [()]  
[]  

 so, we can implement  filter with monad something like this: 

 

 

ghci> [1..50] >>= (\x -> guard ('7' `elem` show x) >> return x)  
[7,17,27,37,47]  

 so combined with the >> method of the Monad, here is what it looks like: 

 

 

ghci> guard (5 > 2) >> return "cool" :: [String]  
["cool"]  
ghci> guard (1 > 2) >> return "cool" :: [String]  
[]  

 Here's is the previous example rewritten in do notation. 

 

 

sevensOnly :: [Int]  
sevensOnly = do  
    x <- [1..50]  
    guard ('7' `elem` show x)  
    return x  

 remember we need to have the ending "return x". otherwise we will get the empty tuple. 

 

 

let's me try to explain how that works? because guard work on bool and return a monoid, and List are monoid, so [1..50] are decomposed into [1] :[2] :[3] : [4],..,[50]. and each will be applied with the guard(),and that will return return(),  or mzero based on the bool value. 

 

As with other typeclass, there are some laws that guide over the monad typeclass. 

 

  • left identity 

    return x>>=f is the same as the f x

 

an example is as 

 

ghci> return 3 >>= (\x -> Just (x+100000))  
Just 100003  
ghci> (\x -> Just (x+100000)) 3  
Just 100003  

 

 

  • right identity

the second law states that if we have a monadic value and we can >>= to feed it to return , the results is our original monadic value, formally: 

 

    m >>= return is no different than just m

 

example is as follow. 

 

we know that the list .>= is like this :

 

 

xs >>= f = concat (map f xs)  

So when we feed [1,2,3,4] to return, first return gets mapped over [1,2,3,4], resulting in [[1],[2],[3],[4]]and then this gets concatenated and we have our original list.

 

 

 

Left identity and right identity are basically laws that describe how return should behave. It's an important function for making normal values into monadic ones and it wouldn't be good if the monadic value that it produced did a lot of other stuff.

 

  • Associativity

The final monad law says that when we have a chain of monadic function applications with >>=, it shouldn't matter how they're nested. Formally written:

    Doing (m >>= f) >>= g is just like doing m >>= (\x -> f x >>= g)

we can make a chain of calll like the following. 

 

ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2  
Just (2,4)  

 

and if we parenthesize this, we 'd get .

 

ghci> ((return (0,0) >>= landRight 2) >>= landLeft 2) >>= landRight 2  
Just (2,4)  

 but we can also write the routine like this: 

 

return (0,0) >>= (\x -> 
landRight 2 x >>= (\y -> 
landLeft 2 y >>= (\z -> 
landRight 2 z)))  

 so, look at this rule like this:  

 

it doesn't matter how you nest feeding values to monadic functions, what matters is their meaning. Here's another way to look at this law

 

consider the two function, f, and g, composing the two is like this: 

(.) :: (b -> c) -> (a -> b) -> (a -> c)  
f . g = (\x -> f (g x))  

 but the value we are dealing with for Monadic value ? we cannot chain them together, but fortunately >>= to make that happen. so by using >>= , we can compose two monadic functions. 

 

(<=<) :: (Monad m) => (b -> m c) -> (a -> m b) -> (a -> m c)  
f <=< g = (\x -> g x >>= f)  

 so that we can compose the monadic values. 

ghci> let f x = [x,-x]  
ghci> let g x = [x*3,x*2]  
ghci> let h = f <=< g  
ghci> h 3  
[9,-9,6,-6]  

  it states that f <=< (g <=< h) should be the same as (f <=< g) <=< h. This is just another way of saying that for monads, the nesting of operations shouldn't matter

 

If we translate the first two laws to use <=<, then the left identity law states that for every monadic function f,f <=< return is the same as writing just f and the right identity law says that return <=< f is also no different fromf.

This is very similar to how if f is a normal function, (f . g) . h is the same as f . (g . h)f . id is always the same as f and id . f is also just f.

相关标签: haskell