你可以修复你的
pf as bs = do
a <- as
b <- bs
if a == b then return () else return (a,b)
通过将其更改为
pf2 :: (Monad m, Eq t) => m t -> m t -> m [(t, t)]
pf2 as bs = do
a <- as
b <- bs
if a == b then return [] else return [(a,b)]
如果
m
是
[]
类型,那么这将得到我们。
pf3 :: (Eq t) => [t] -> [t] -> [ [(t, t)] ]
pf3 as bs = do
a <- as
b <- bs
if a == b then return [] else return [(a,b)]
现在它确实通过了类型检查器,但我们在所需的类型周围创建了一个额外的[]
层:
> pf3 [1,2,3] [1,2,3] :: [ [(Int,Int)] ]
[[],[(1,2)],[(1,3)], [(2,1)],[],[(2,3)], [(3,1)],[(3,2)],[]]
所以这几乎就是我们想要的,除了一些额外的[]
作为保险措施。如果我们能让它们消失该多好啊!
> concat it
[ (1,2), (1,3), (2,1), (2,3), (3,1), (3,2) ]
正如您在上面所看到的,我们通过将pf3
的输出传递给concat
,获得了最终期望的值。但是concat
就像[]
-类型单子的join
:
> :t concat :: [] ([] a) -> [] a
concat :: [] ([] a) -> [] a :: [[a]] -> [a]
> :t join :: [] ([] a) -> [] a
join :: [] ([] a) -> [] a :: [[a]] -> [a]
> :t join
join :: Monad m => m (m a) -> m a
被定义为
join xs = do = do
{ x <- xs { x <- xs
; x ; y <- x
} ; return y
}
因此,我们上面所写的内容融合在一起,形成如下:
pf4 as bs = join $ do
a <- as
b <- bs
if a == b then return [] else return [(a,b)]
= do {
x <- do { a <- as
; b <- bs
; if a == b then return [] else return [(a,b)] }
; x
}
= do { a <- as
; b <- bs
; x <- if a == b then return [] else return [(a,b)]
; x
}
= do { a <- as
; b <- bs
; let { x = if a == b then [] else [(a,b)] }
; x
}
= do { a <- as
; b <- bs
; if a == b then [] else [(a,b)]
}
= do { a <- as
; b <- bs
; if a == b then [] else [()]
; return (a,b)
}
无论您喜欢最后两个中的哪一个。前两个是您通常手写的内容,而最后一个对应于在第三个do
行中使用reject(a==b)
,并定义如下:
reject :: MonadPlus m => Bool -> m ()
reject True = mzero -- mzero :: MonadPlus m => m a
reject False = return () -- () to be ignored
对于[]
类型的单子,mzero = [] :: [] a
(当然,return x = [x] :: [] a
)。
恰好reject b = guard (not b)
,使用内置的guard
函数。
一个旁注:
return ()
是一种无操作,因为
do { a <- as ; b <- bs ; return (a,b) } ==
do { return () ; a <- as ; b <- bs ; return (a,b) } ==
do { a <- as ; return () ; b <- bs ; return (a,b) } ==
do { a <- as ; b <- bs ; return () ; return (a,b) }
你可以将它放在 do
块的任何一行,除了最后一行,它不会改变任何事情。这对于 任何 Monad 都是正确的。
[(a, b) | a <- as, b <- bs, a == b]
。这段代码可以帮你筛选出在列表 as 和列表 bs 中相等的元素对,并将它们以元组的形式存储在列表中。 - daylilyreturn () == [()]
。你读到的可能是特定于IO
单子的典型用法。例如,putStrLn :: String -> IO ()
,它创建了一个IO操作,当执行时,会将一些字符写入标准输出并产生值()
。由于实际上没有人关心执行类型为IO()
的操作所产生的值,因此return ()
在创建的操作中是无操作,它不进行任何输入或输出;它只产生预期和所需的值()
。 - chepnerreturn()
在某种意义上是一个无操作,因为do { a <- as ; b <- bs ; return (a,b) }
==do { return () ; a <- as ; b <- bs ; return (a,b) }
=do { a <- as ; return () ; b <- bs ; return (a,b) }
=do { a <- as ; b <- bs ; return () ; return (a,b) }
。你可以将其放在do
块的任何一行,除了最后一行,它都不会改变任何东西。这对于_任何_单子都是正确的。(cc @chepner) - Will Ness