代码之家  ›  专栏  ›  技术社区  ›  Javran

在IO内使用读取器“-->r”的意外行为?

  •  1
  • Javran  · 技术社区  · 7 年前

    我决定使用 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 回复  |  直到 7 年前
        1
  •  3
  •   amalloy    7 年前

    >> 或类似的)并最终成为 main (或给予GHCI的行动)。

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

    这仅仅是IO操作如何组合的结果,也就是说,您必须显式地组合它们:仅仅计算它们然后丢弃它们是不够的。您所要求的类似于要求将您在读卡器单子中计算的两个数字隐式相加:

    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()