我决定使用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
会有所帮助,但我认为最终可能会使代码更复杂,所以我没有尝试过。
答案 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 ()
值隐式组合也是不正确的。