下面的代码有点神秘。在问题的非玩具版本中,我试图在 monad Result 中进行 monadic 计算,其值只能从 IO 中构造。似乎 IO 背后的魔力使此类计算变得严格,但我无法弄清楚这是如何发生的。
编码:
data Result a = Result a | Failure deriving (Show)
instance Functor Result where
fmap f (Result a) = Result (f a)
fmap f Failure = Failure
instance Applicative Result where
pure = return
(<*>) = ap
instance Monad Result where
return = Result
Result a >>= f = f a
Failure >>= _ = Failure
compute :: Int -> Result Int
compute 3 = Failure
compute x = traceShow x $ Result x
compute2 :: Monad m => Int -> m (Result Int)
compute2 3 = return Failure
compute2 x = traceShow x $ return $ Result x
compute3 :: Monad m => Int -> m (Result Int)
compute3 = return . compute
main :: IO ()
main = do
let results = mapM compute [1..5]
print $ results
results2 <- mapM compute2 [1..5]
print $ sequence results2
results3 <- mapM compute3 [1..5]
print $ sequence results3
let results2' = runIdentity $ mapM compute2 [1..5]
print $ sequence results2'
输出:
1
2
Failure
1
2
4
5
Failure
1
2
Failure
1
2
Failure
最佳答案
不错的测试用例。这是正在发生的事情:
mapM compute
像往常一样,我们看到工作中的懒惰。这里没有惊喜。 mapM compute2
我们在 IO monad 内部工作,它的 mapM
定义将需要整个列表:不像 Result
一旦 Failure
跳过列表的尾部已找到,IO
将始终扫描整个列表。注意代码:compute2 x = traceShow x $ return $ Result x
因此,只要访问了 IO 操作列表的每个元素,上述内容就会打印调试消息。都是,所以我们打印所有内容。
mapM compute3
我们现在大致使用:compute3 x = return $ traceShow x $ Result x
现在,自从
return
在 IO 是惰性的,它不会触发 traceShow
返回 IO 操作时。所以,当 mapM compute3
正在运行,没有看到任何消息。相反,我们仅在 sequence results3
时才看到消息。运行,这会强制 Result
- 不是所有的,但只有需要的。 Identity
例子也很棘手。请注意:> newtype Id1 a = Id1 a
> data Id2 a = Id2 a
> Id1 (trace "hey!" True) `seq` 42
hey!
42
> Id2 (trace "hey!" True) `seq` 42
42
使用
newtype
时,在运行时不涉及装箱/拆箱(AKA 提升),因此强制 Id1 x
值(value)导致x
被迫。与 data
类型这不会发生:值被包装在一个盒子里(例如 Id2 undefined
不等于 undefined
)。在您的示例中,您添加了
Identity
构造函数,但来自 newtype Identity
!!所以,打电话的时候return $ traceShow x $ Result x
return
这里不包装任何东西,traceShow
立即触发 mapM
正在运行。 关于haskell - IO monad防止嵌入式mapM短路?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37538532/