每当我读到关于Monad的例子时,他们总是将IO作为案例研究。
是否有任何monad进行列表操作的例子,有人可以提出?我认为这可能是矫枉过正,但我感兴趣的是monad可以提供优于常规列表操作技术的优势。
答案 0 :(得分:9)
Haskell中列表monad的最大秘密是列表推导是do blocks 的语法糖。无论何时编写列表推导,您都可以使用do块来编写它,它使用list monad实例。
假设您想要获取两个列表,并返回其笛卡尔积(即(x,y)
的列表,其中x
的每个组合来自第一个列表,y
来自第二个清单)。
你可以用列表理解来做到这一点:
ghci> [(x,y) | x <- [1,2], y <- [3,4]] -- [(1,3),(1,4),(2,3),(2,4)]
列表理解是这个块的语法糖:
zs = do x <- [1,2]
y <- [3,4]
return (x,y)
反过来又是
的语法糖zs = [1,2] >>= \x -> [3,4] >>= \y -> return (x,y)
这个例子并没有真正展示monad的强大功能,因为你可以轻松地编写它而不依赖于列表具有Monad实例的事实。例如,如果我们只使用Applicative实例:
ghci> import Control.Applicative
ghci> (,) <$> [1,2] <*> [3,4] -- [(1,3),(1,4),(2,3),(2,4)]
现在让我们假设您获取正整数列表中的每个元素,并将其复制多次(例如f [1,2,3] = [1,2,2,3,3,3]
)。谁知道你为什么要那样做,但很容易:
ghci> let f xs = [ y | x <- xs, y <- replicate x x ]
ghci> f [1,2,3] -- [1,2,2,3,3,3]
这只是语法糖:
f xs = do x <- xs
y <- replicate x x
return y
反过来又是
的语法糖f xs = xs >>= \x -> replicate x x >>= \y -> return y
这次我们不能只使用应用实例来编写。关键的区别在于我们从第一个绑定(x
)获取输出,并使块的其余部分依赖于它(y <- replicate x x
)。
答案 1 :(得分:9)
使用list monad巧妙地编写“简单”列表实用程序函数的典型示例是:
import Control.Monad (filterM)
-- | Compute all subsets of the elements of a list.
powerSet :: [a] -> [[a]]
powerSet = filterM (\x -> [True, False])
filterM
的类型为Monad m => (a -> m Bool) -> [a] -> m [a]
。在列表monad的上下文中,这是一个非确定性列表过滤器:一个过滤操作,它采用非确定性谓词,返回替代列表答案。 filterM
的结果反过来列出了替代可能的结果。
或者用更简单的语言,filterM (\x -> [True, False])
表示:对于列表中的每个元素,我希望保留它并将其丢弃。 filterM
计算出为每个列表元素执行此操作的所有可能组合。
答案 2 :(得分:4)
这是一种非常愚蠢的方法来查找给定整数的除数对:
divisors:: Int -> [(Int,Int)]
divisors n = do
x <- [1 .. n]
y <- [1 .. n]
if x*y == n then return (x, y) else []
这个片段的意义应该是相当直截了当的。显然,这是解决这个特定问题的一种愚蠢方法。 (在这种情况下,有更多有效的方法可行。)但现在想象一些更复杂的问题,这种搜索非常复杂。这种用于搜索的习惯用法非常有用。
答案 3 :(得分:3)
另一个例子是从其素数因子分解中构造一个数的所有除数(虽然不按顺序):
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
f
中的函数mapM f == sequence . map f
为输入列表中的每个条目生成一个因子的幂列表;然后sequence
形成列表中的所有路径,一次从每个路径中选择一个号码;然后将所有可能组合的列表输入map product
,计算除数:
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
Luis Casillas's answer中给出的精彩描述也适用于此:
在列表monad的上下文中,
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
是非确定性列表映射:映射非确定性函数的映射操作 - 这样生成一个列表替代结果 - 在列表上,创建所有可能的替代结果列表。
答案 4 :(得分:3)
列表操作的一个很好的例子是查询HTML / XML文档, a la jQuery。 jQuery是monad。我们来看一个例子:
$(doc).find('> body')
.find('> *')
.is('table')
.is('.bar')
.find('> caption')
.text()
这为您提供了具有CSS类bar
的所有顶级表的标题。这是使用jQuery的一种不寻常的方式(通常你只是做body > table.bar > caption
),但这是因为我想显示步骤。
在xml-conduit
中,您可以同样地执行此操作:
child cursor >>= element "body" >>= child
>>= element "table" >=> attributeIs "class" "bar"
>>= child >>= element "caption"
>>= descendants >>= content
monad所做的是组合这些步骤。 monad本身对HTML / XML一无所知。
在HXT
中,您几乎以同样的方式执行此操作。较新的HXT
版本使用所谓的箭头而不是monad,但基本原理是相同的。
答案 5 :(得分:2)
Monad和列表操作与MonadPlus
的{{1}}和另一个monad结合使用时更加有趣。举个例子,让我们解决经典的guard
难题:SEND + MORE = MONEY
。每个字母必须代表一个唯一的数字,前导数字不能为零。
为此,我们将从StateT
和[]
构建一个monad堆栈:
import Control.Monad
import Control.Monad.List
import Control.Monad.State
-- Verbal Arithmetic monad
type VA a = StateT [Int] [] a
列表monads允许我们分支计算以搜索所有可能的方式,状态是可供选择的数字列表。我们可以通过给出所有可能的数字来运行这个monad:
runVA :: VA a -> [a]
runVA k = evalStateT k [0..9]
我们需要一个辅助功能,通过选择所有可用数字来分支计算,并用剩下的内容更新状态:
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
此外,我们经常需要选择一个非零数字:
pickNZ :: VA Int
pickNZ = do
d <- pick
guard (d /= 0)
return d
解决这个难题现在很简单:我们可以简单地逐个数字地实现添加,并使用guard
检查结果的数字是否等于总和:
-- 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
这个谜题只有一个结果:(9567,1085,10652)
。 (当然代码可以进一步优化,但我希望它很简单。)
可以在这里找到更多谜题:verbal arithmetic 。