以下代码有些神秘。在非玩具版本的问题中,我尝试在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
答案 0 :(得分:10)
不错的测试用例。以下是发生的事情:
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