IO monad可以防止嵌入式mapM的短路?

时间:2016-05-31 06:40:39

标签: haskell lazy-evaluation traversal io-monad strictness

以下代码有些神秘。在非玩具版本的问题中,我尝试在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

2 个答案:

答案 0 :(得分:10)

不错的测试用例。以下是发生的事情:

    {li>

    mapM compute我们像往常一样在工作中看到懒惰。这里不足为奇。

  • mapM compute2我们在IO monad中工作,其mapM定义将要求整个列表:与Result不同,Failure会尽快跳过列表的尾部IO找到1}},compute2 x = traceShow x $ return $ Result x 将始终扫描整个列表。请注意代码:

    mapM compute3

    因此,一旦访问IO动作列表的每个元素,上面将打印调试消息。一切都是,所以我们打印一切。

  • 我们现在使用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

    使用Id1 x时,在运行时没有涉及装箱/取消装箱(AKA解除),因此强制x值会导致data被强制执行。对于Id2 undefined类型,这不会发生:值包含在一个框中(例如undefined不等于Identity)。

    在您的示例中,您添加了newtype Identity构造函数,但这来自return $ traceShow x $ Result x !!所以,在致电

    return

    traceShow此处不包含任何内容,mapM会在info(String)运行后立即触发。

答案 1 :(得分:1)

您的Result类型似乎与Maybe几乎完全相同,但

Result <-> Just
Failure <-> Nothing

为了我的大脑,我会在这个答案的其余部分坚持使用Maybe术语。

chi解释了为什么IO (Maybe a)不会像你期望的那样短路。但 是一种可以用于此类事情的类型!事实上,它实际上是相同的类型,但具有不同的Monad实例。您可以在Control.Monad.Trans.Maybe中找到它。它看起来像这样:

newtype MaybeT m a = MaybeT
  { runMaybeT :: m (Maybe a) }

如您所见,这只是围绕newtype的{​​{1}}包装器。但它的m (Maybe a)实例非常不同:

Monad

也就是说,instance Monad m => Monad (MaybeT m) where return a = MaybeT $ return (Just a) m >>= f = MaybeT $ do mres <- runMaybeT m case mres of Nothing -> return Nothing Just a -> runMaybeT (f a) 在底层monad中运行m >>= f计算,得到m某些东西。如果它获得Maybe,它就会停止,返回Nothing。如果它得到了什么,它会将其传递给Nothing并运行结果。您还可以将任何f操作转换为&#34;成功&#34;使用m中的MaybeT m {/ 1}}行动{/ 1}}

lift

你也可以使用这个类定义在Control.Monad.Trans.Class之类的地方,它通常更清晰,更方便:

class MonadTrans t where
  lift :: Monad m => m a -> t m a

instance MonadTrans MaybeT where
  lift m = MaybeT $ Just <$> m