Using monads for trivial tasks like list manipulation?

前端 未结 6 1419
栀梦
栀梦 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条回答
  •  梦毁少年i
    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.

提交回复
热议问题