Using monads for trivial tasks like list manipulation?

一个人想着一个人 提交于 2019-12-04 13:11:19

问题


Whenever I read about Monad example, they always present IO as a case study.

Are there any examples of monads doing list manipulation which somebody could present? I aprpeciate this could be overkill, but I am interested if monads can present advantages over regular list manipulation techniques.


回答1:


The big secret to the list monad in Haskell is that list comprehensions are syntactic sugar for do blocks. Any time you write a list comprehension, you could have written it using a do block instead, which uses the list monad instance.

A simple example

Let's say you want to take two lists, and return their cartesian product (that is, the list of (x,y) for every combination of x from the first list and y from the second list).

You can do that with a list comprehension:

ghci> [(x,y) | x <- [1,2], y <- [3,4]] -- [(1,3),(1,4),(2,3),(2,4)]

The list comprehension is syntactic sugar for this do block:

zs = do x <- [1,2]
        y <- [3,4]
        return (x,y)

which in turn is syntactic sugar for

zs = [1,2] >>= \x -> [3,4] >>= \y -> return (x,y)

A more complicated example

That example doesn't really demonstrate the power of monads, though, because you could easily write it without relying on the fact that lists have a Monad instance. For example, if we only use the Applicative instance:

ghci> import Control.Applicative
ghci> (,) <$> [1,2] <*> [3,4] -- [(1,3),(1,4),(2,3),(2,4)]

Now let's say you take every element of a list of positive integers, and replicate it as many times as itself (so e.g. f [1,2,3] = [1,2,2,3,3,3] for example). Who knows why you'd want to do that, but it is easy:

ghci> let f xs = [ y | x <- xs, y <- replicate x x ]
ghci> f [1,2,3] -- [1,2,2,3,3,3]

That's just syntactic sugar for this:

f xs = do x <- xs
          y <- replicate x x
          return y

which in turn is syntactic sugar for

f xs = xs >>= \x -> replicate x x >>= \y -> return y

This time we can't write that just using the applicative instance. The key difference is that we took the output from the first bind (x) and made the rest of the block depend on it (y <- replicate x x).




回答2:


A classic example of using the list monad to cleverly write a "simple" list utility function is this:

import Control.Monad (filterM)

-- | Compute all subsets of the elements of a list.
powerSet :: [a] -> [[a]]
powerSet = filterM (\x -> [True, False])

The type of filterM is Monad m => (a -> m Bool) -> [a] -> m [a]. In the context of the list monad, this is a nondeterministic list filter: a filter operation that takes a nondeterministic predicate that returns a list of alternative answers. The result of filterM is in turn a list of alternative possible results.

Or in simpler language, filterM (\x -> [True, False]) means: for each element of the list, I want both to keep it and throw it away. filterM figures out all possible combinations of doing this for each list element.




回答3:


Here is a very stupid way to find pairs of divisors for a given integer:

divisors:: Int -> [(Int,Int)]
divisors n = do
  x <- [1 .. n]
  y <- [1 .. n]
  if x*y == n then return (x, y) else []

The meaning of this fragment ought to be fairly straight-forward. Obviously, this is a stupid way to solve this specific problem. (There are far more efficient methods possible in this case.) But now imagine some more complicated problem, where this search is very complex. This kind of idiom for searching can be quite useful.




回答4:


A nice example for list manipulation is querying an HTML/XML document, a la jQuery. jQuery is a monad. Let's examine an example:

$(doc).find('> body')
      .find('> *')
      .is('table')
      .is('.bar')
      .find('> caption')
      .text()

This gives you the captions of all the top-level tables having a CSS class bar. This is an unusual way to use jQuery (usually you'd simply do body > table.bar > caption), but that's because I wanted to show the steps.

In xml-conduit you do it similarly:

child cursor >>= element "body" >>= child
        >>= element "table" >=> attributeIs "class" "bar"
        >>= child >>= element "caption"
        >>= descendants >>= content

What the list monad does is combine the steps. The monad in itself knows nothing about HTML/XML.

In HXT you do it in almost the same way. Newer HXT versions use what are called arrows instead of monads, but the underlying principle is the same.




回答5:


Another example is constructing all the divisors of a number from its prime factorization (though out of order):

divs n = map product 
           . mapM (\(p,n)-> map (p^) [0..n]) 
           . primeFactorization $ n

-- mapM f = sequence . map f

-- primeFactorization 12  ==>  [(2,2),(3,1)]  -- 12 == 2^2 * 3^1

The function f in mapM f == sequence . map f produces a list of powers of a factor, for each entry in the input list; then sequence forms all paths through the lists picking one number from each at a time; then this list of all possible combinations is fed to map product which calculates the divisors:

12                                      -- primeFactorization:
[(2,2),(3,1)]                           -- map (\(p,n)-> ...):
[ [1,2,4], [1,3] ]                      -- sequence:
[[1,1],[1,3],[2,1],[2,3],[4,1],[4,3]]   -- map product:
[1,3,2,6,4,12]                          --   the divisors of 12

The great description given in Luis Casillas's answer applies here too:

In the context of the list monad, mapM :: (Monad m) => (a -> m b) -> [a] -> m [b] is a nondeterministic list map: a mapping operation that maps a nondeterministic function - such that produces a list of alternative results - over a list, creating all the alternative possible lists of results.




回答6:


Monads and list manipulation is even more fun when combined with guard of MonadPlus and perhaps another monad. As an example, let's solve the classic verbal arithmetic puzzle: SEND + MORE = MONEY. Each letter must represent a unique digit and the leading digits can't be zero.

For this, we'll construct a monad stack from StateT and []:

import Control.Monad
import Control.Monad.List
import Control.Monad.State

-- Verbal Arithmetic monad
type VA a = StateT [Int] [] a

The list monads allows us to branch computations to search all possible ways, and the state is the list of digits that are available for choosing. We can run this monad by giving it all possible digits as:

runVA :: VA a -> [a]
runVA k = evalStateT k [0..9]

We'll need one auxiliary function that branches a computation by picking all available digits, and updating the state with what is left:

pick :: VA Int
pick = do
    -- Get available digits:
    digits <- get
    -- Pick one and update the state with what's left.
    (d, ds) <- lift $ split digits
    put ds
    -- Return the picked one.
    return d
  where
    -- List all possible ways how to remove an element from a list.
    split :: [a] -> [(a, [a])]
    split = split' []
    split' _ []      = []
    split' ls (x:rs) = (x, ls ++ rs) : split' (x : ls) rs

Also we'll often need to pick a non-zero digit:

pickNZ :: VA Int
pickNZ = do
    d <- pick
    guard (d /= 0)
    return d

Solving the puzzle is now easy: We can simply implement the addition digit by digit and check using guard that the digits of the result are equal to the sum:

--   SEND
-- + MORE
-- ------
--  MONEY
money :: [(Int,Int,Int)]
money = runVA $ do
    d <- pick
    e <- pick
    let (d1, r1) = (d + e) `divMod` 10
    y <- pick
    guard $ y == r1
    n <- pick
    r <- pick
    let (d2, r2) = (n + r + d1) `divMod` 10
    guard $ e == r2
    o <- pick
    let (d3, r3) = (e + o + d2) `divMod` 10
    guard $ n == r3
    s <- pickNZ
    m <- pickNZ -- Actually M must be 1, but let's pretend we don't know it.
    let (d4, r4) = (s + m + d3) `divMod` 10
    guard $ r4 == o
    guard $ d4 == m
    return (ds [s,e,n,d], ds [m,o,r,e], ds [m,o,n,e,y])
  where
    -- Convert a list of digits into a number.
    ds = foldl (\x y -> x * 10 + y) 0

The puzzle has exactly one result: (9567,1085,10652). (Of course the code can be further optimized, but I wanted it to be simple.)

More puzzles can be found here: http://www.primepuzzle.com/leeslatest/alphameticpuzzles.html.



来源:https://stackoverflow.com/questions/12706640/using-monads-for-trivial-tasks-like-list-manipulation

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!