IO内部使用Reader`( - > r)`的意外行为?

时间:2017-09-27 18:20:48

标签: haskell lazy-evaluation

我决定使用Reader实例(->) r来清理我的一些代码,以避免不断传递某些值。

在玩了一下之后,我发现了以下行为上的差异:

let p x y = print (x,y)
    f = ($ (1 :: Int))

f $ do
    p <$> (+ 2) <*> (+ 5)
    p <$> (* 6) <*> (* 2)
-- prints (6,2) but not (3,6)

do
  f $ p <$> (+ 2) <*> (+ 5)
  f $ p <$> (* 6) <*> (* 2)
-- prints both (6,2) and (3,6)

手动去除这个并输入一些类型的注释后:

let p x y = print (x,y)
    f = ($ (1 :: Int))
f $ ((>>) :: forall m a. m ~ (Int -> a) => m -> m -> m)
    (p <$> (+ 2) <*> (+ 5) :: Int -> IO ())
    (p <$> (* 6) <*> (* 2))

((>>) :: forall m. m ~ IO () => m -> m -> m)
  (f (p <$> (+ 2) <*> (+ 5) :: Int -> IO ()))
  (f (p <$> (* 6) <*> (* 2)))

现在我怀疑问题是,虽然IO实例的>>确实按照我的意愿行事,但>> (-> r)却懒得查看其左侧。

所以我的问题是,有没有办法(也许是LHS的>>严格的变体?),做这样的事情而不会遇到这种懒惰问题?

在这种情况下,不确定ReaderT会有所帮助,但我认为最终可能会使代码更复杂,所以我没有尝试过。

1 个答案:

答案 0 :(得分:3)

对LHS严格要求根本不重要:只看IO动作就不会执行它。它必须与另一个IO动作(通过>>等)结合起来,并最终作为main的一部分(或给予GHCI的动作)。

也就是说,正如您已经意识到的那样,在第一种情况下,您有两个(Int -> IO ())值,您使用来自Reader monad的(>>)组合,这导致仅返回第二个IO操作。在第二种情况下,您有两个IO ()个动作,您可以使用IO monad中的(>>)进行组合。当然,这将两者结合为一个动作。

这仅仅是IO动作如何组合的结果,即你必须明确地将它们结合起来:仅仅计算它们然后扔掉它们是不够的。您要求的类似于将您在Reader monad中计算的两个数字隐式添加到一起:

f :: Int -> Int -> Int
f x = (* x)

foo :: Int -> Int
foo = do
  f 3
  f 5

bar :: Int
bar = foo 4

首先你计算4 * 3,当然是12,然后你把它扔掉并计算4 * 5,当然是20,因此值栏是20.因为你明确地扔掉了12说不要对它做任何事情:将它们加在一起并让bar = 32,或者将它们相乘并让bar = 240,或者任何这样的事情都是错误的。同样,读者计算中的IO ()值隐式组合也是不正确的。