Using monads for trivial tasks like list manipulation?

前端 未结 6 1399
栀梦
栀梦 2021-02-09 05:13

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 apr

相关标签:
6条回答
  • 2021-02-09 05:57

    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.

    0 讨论(0)
  • 2021-02-09 06:00

    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.

    0 讨论(0)
  • 2021-02-09 06:04

    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.

    0 讨论(0)
  • 2021-02-09 06:04

    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.

    0 讨论(0)
  • 2021-02-09 06:10

    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.

    0 讨论(0)
  • 2021-02-09 06:15

    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).

    0 讨论(0)
提交回复
热议问题