在 IO 中使用 Reader `(-> r)` 的意外行为?
Unexpected behavior using Reader `(-> r)` inside IO?
我决定使用 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)))
现在我怀疑问题是,虽然 >>
for IO 实例确实做了我想要的,但 >>
for (-> r)
懒得看它的左侧。
所以我的问题是,有没有办法(可能是 >>
对 LHS 严格的变体?),在不 运行 解决这个懒惰问题的情况下做这样的事情?
不确定 ReaderT
在这种情况下是否有帮助,但我认为这最终可能会使代码变得更加复杂,所以我还没有尝试过。
严格控制 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,因此值 bar 是 20。12 被扔掉是因为您明确表示不要对它做任何事情:将它们加在一起并让 bar=32,或将它们相乘并让 bar=240,或任何类似的事情都是错误的。同样,隐式组合 reader 计算中的 IO ()
值也是不正确的。
我决定使用 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)))
现在我怀疑问题是,虽然 >>
for IO 实例确实做了我想要的,但 >>
for (-> r)
懒得看它的左侧。
所以我的问题是,有没有办法(可能是 >>
对 LHS 严格的变体?),在不 运行 解决这个懒惰问题的情况下做这样的事情?
不确定 ReaderT
在这种情况下是否有帮助,但我认为这最终可能会使代码变得更加复杂,所以我还没有尝试过。
严格控制 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,因此值 bar 是 20。12 被扔掉是因为您明确表示不要对它做任何事情:将它们加在一起并让 bar=32,或将它们相乘并让 bar=240,或任何类似的事情都是错误的。同样,隐式组合 reader 计算中的 IO ()
值也是不正确的。