列表单子变换器

14

我需要使用一个列表单子变换器。我已经阅读过关于 Control.Monad.List 中的 ListT IO 可能存在问题,因为 IO 不是可交换的,所以我正在查看 正确使用的ListT。但我得到了一些意外的行为。

考虑这个简单的测试:

test = runListT $ do
  x <- liftList [1..3]
  liftIO $ print x
  y <- liftList [6..8]
  liftIO $ print (x,y)

使用 Control.Monad.List:

Main> test
1
(1,6)
(1,7)
(1,8)
2
(2,6)
(2,7)
(2,8)
3
(3,6)
(3,7)
(3,8)
[(),(),(),(),(),(),(),(),()]

使用“正确使用ListT”:

Main> test
1
(1,6)

这是“ListT done right”存在问题,还是我使用方法不正确?有没有更好的替代方案?

谢谢!

1个回答

8
这可能是作者有意为之,因为他们说:

它让列表的每个元素都拥有自己的副作用,只有在实际检查这个列表元素时才会被执行。

虽然我不确定。无论如何,您可以使用此函数对整个列表进行排序:
runAll_ :: (Monad m) => ListT m a -> m ()
runAll_ (ListT m) = runAll_' m where
    runAll_' m = do
        mm <- m
        case mm of
             MNil          -> return ()
             _ `MCons` mxs -> runAll_' mxs

同时构建一个返回列表的类似runAll方法将会很容易。

main = runAll_ $ do
    x <- liftList [1..3]
    liftIO $ print x
    y <- liftList [6..8]
    liftIO $ print (x,y)

1
(1,6)
(1,7)
(1,8)
2
(2,6)
(2,7)
(2,8)
3
(3,6)
(3,7)
(3,8)

嗯,好的,这很有道理,你的runAll_想法非常好!我原本期望的是类似于命令式语言中带有打印语句的嵌套for循环的行为。但是如果“正确使用ListT”是惰性的,为什么它仍然会对列表头执行副作用呢? - Chad Scherrer
它假设您始终希望至少有第一个元素,因此将“整个列表”包装在m中,并且还将cdr包装在m中;car未被包装。如果您对“整个列表”进行序列化,则仅公开car。 - Owen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接