【问题标题】:Reader monad, how bind readers works?读者单子,绑定读者如何工作?
【发布时间】:2019-02-09 15:58:53
【问题描述】:

我是 Haskell 的初学者。我学习了如何创建阅读器和如何查询共享变量。我在 Hugs98 中查看了 Reader.hs 的源代码

instance Monad (Reader r) where
return a = Reader $ \_ -> a

m >>= k  = Reader $ \r -> 
                  runReader (k (runReader m r)) r

在这里我可以看到 (return a) 创建了一个 Reader 包装了一个接受值并返回 a 的函数

m >>= k 是我无法理解的。首先如何应用?也许两个读者绑定的例子可以帮助?

其次,实施对我来说有些模糊 我不明白将 k 应用于 (runReader m r) 的结果有什么意义?

谢谢

【问题讨论】:

  • 既然 Reader 和 runReader 只是在两个同构类型之间穿梭,就假装它们不存在,这样就更容易理解了。
  • 当 monad 是函数时,我仍然难以理解这种特定行为。在所有的 monad 辅导中似乎都缺少一个解释——monad 并不完全相同,例如当 k 应用于 >>= 的 Maybe monad 时,绑定 k 的结尾被喂入并给出 m b 这只是为了a Reader 与给出 m b 相同的 k 现在需要解包和包装 - 但从类型签名可以看出,当 k 与 a a 一起输入时会产生 Reader r b - 这是我们寻找的 Reader 并且应该结束绑定,如Maybe monad...

标签: haskell monads


【解决方案1】:

Reader 定义为:

newtype Reader r a = Reader { runReader :: r -> a }

所以它实际上只是一个带有一些额外封装的r -> a 类型的函数。这是有道理的,因为 Reader 实际上只是为 monad 中的所有操作提供了额外的输入。

如果我们剥离封装只使用r -> a函数,那么一元函数的类型是:

return :: a -> (r -> a) -- or: a -> r -> a
(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b) -- or: (r -> a) -> (a -> r -> b) -> r -> b

看到这一点,更容易看出我们需要什么。如果您查看a -> (r -> a) 类型并看到这相当于a -> r -> a,您可以看到您可以通过两种方式查看此函数。一种是您接受a 的参数并返回r -> a 类型的函数,另一种是将其视为接受ar 并返回a 的函数。您可以使用以下任一视图实现 return:

return a = \r -> a -- or: return a r = a

绑定比较棘手,但同样的逻辑也适用。在我给出的第一个类型签名中,并不能立即看出类型中的第三个r 实际上也是一个输入,而第二个类型签名使得这很容易看到。那么让我们从实现第二种类型签名开始:

(>>=) rToA aAndRToB r = ...

所以我们有一个r 类型的值、一个r -> a 类型的函数和一个a -> r -> b 类型的函数,我们的目标是从中生成一个b 类型的值。我们输入中唯一的ba -> r -> b 函数中,所以我们需要使用它,但是我们没有a 来提供它,所以我们需要一个。 r -> a 函数可以提供一个,如果我们有一个 r 。我们确实有一个r,这是我们的第三个输入。所以我们可以简单地应用这些函数,直到我们得到我们的b

(>>=) rToA aAndRToB r = b where
  a = rToA r
  b = aAndRToB a r

在这里您可以看到我们为每个操作提供了r-value(这是 Reader monad 的目标),同时还将 a-value 从一个操作链接到下一个操作(这是(>>=))。你也可以用模仿第一个类型签名的方式来编写它,如下所示:

(>>=) rToA aToRToB = \r -> (aToRToB (rToA r)) r

如果你重命名变量看起来与Reader的绑定定义非常相似,但没有使用ReaderrunReader

m >>= k = /r -> k (m r) r

【讨论】:

    【解决方案2】:

    >>= 又名绑定具有签名:m a -> (a -> m b) -> m b。如果我们尝试使这个签名特定于 Reader,即用 Reader r 替换 m,我们会发现它变成:

    (Reader r) a -> (a -> (Reader r) b) -> (Reader r) b

    等同于:

    Reader r a -> (a -> Reader r b) -> Reader r b

    现在我们需要编写这样一个函数:

    (>>=) m k = ... 其中mReader r ak(a -> Reader r b),我们需要返回Reader r b

    您如何创建Reader r b,因为这是我们必须返回的东西?好吧,k 是一个允许您创建 Reader r b 的函数,但 k 需要一些 a 类型的值才能返回 Reader r b

    我们如何获得a 类型的值(以便我们可以使用函数k)?看起来m 类型为Reader r a 的参数可以帮助我们获取a 类型的值。

    我们如何从Reader r a 获得a 的值? runReader 有一个Reader r a -> r -> a 类型,所以如果我们在m 上调用runReader,我们将得到(r -> a),但我们正在寻找a 类型的值,而我们得到的是(r -> a),看来我们没有任何r 值来获取a。似乎我们被卡住了,因为我们没有任何其他参数可供查找。

    假设我们以某种方式拥有一些r 值(称为r_val)以便我们可以这样做:

    let a_val = runReader m r_val 为我们提供a 类型的值。

    a 我们需要使用 k 获取 Reader r b

    let reader_r_b_val = k a_val 给了我们Reader r b 类型的值,就是这样,我们得到了我们需要返回的值,让我们结合以上 2 个让我们:

    k (runReader m r_val)Reader r b 但我们还没有完成,我们需要做一些r_val 的事情,这只是一个占位符。假设我们将r_val 作为参数

    \r_val -> k (runReader m r_val)r -> Reader r b 类型的 ...嗯,但我们只需要返回 Reader r b .. 我们可以以某种方式将 r -> Reader r b 包装成 Reader r b 吗?

    Reader $ (\r_val -> k (runReader m r_val)) 有一个类型 Reader r (Reader r b) .. 看起来我们快到了,我们只需要将 Reader r (Reader r b) 转换为 Reader r b 即我们需要将内部 Reader r b 转换为 b 并为此我们可以使用runReader 所以:

    Reader $ (\r_val -> runReader (k (runReader m r_val)) r_val)

    【讨论】:

      【解决方案3】:

      好的,让我们看看m >>= k。这里m 是一个阅读器,k 是一个产生阅读器的函数。那么这有什么作用呢?

      runReader m r
      

      好的,所以这是运行mr 作为要读取的输入。

      k (runReader m r)
      

      这将运行m 的输出并将其传递给k。这使得k 返回另一个 读者。

      runReader (k (runReader m r)) r
      

      这需要k 返回的阅读器并运行它(使用相同的输入r 进行阅读)。

      你都遵守了吗?

      【讨论】:

      • 感谢您的帮助,是的,我的问题就在这里k (runReader m r) 我不知道如何使用?如何绑定多个阅读器,一个供另一个阅读器?你能提供一个简单的例子吗?再次感谢
      • 如图所示,k (runReader m r) 返回一个阅读器。然后我们将它传递给最外层的runReader 来运行它(同样,使用相同的输入r)。
      【解决方案4】:

      首先,阅读器的用途。

      假设你有一个纯函数 f x y 和一个纯函数 g y。现在你发现 g 需要在内部使用 f ,但它只有一个参数可以提供 f !典型的解决方案是以下之一:

      1. 修改 API,让 g 现在有两个参数,x 和 y(调用者现在必须计算它,即使 g 不调用 f);

      2. 创建一个全局变量,g 将读取该变量以向 f 提供该参数(g 不再是纯变量);

      3. 创建一个 f 将读取的全局变量(f 不再是纯变量)。

      熟悉吗?后两种解决方案可能是最常见的,但它们很难看。第一个解决方案需要调用者与 g 交互的统一方式,这就是困难。通过将 g 包装在 Reader monad 中,我们提供了这样的接口:调用者 h :: a -> b 要么知道如何计算 x 并提供它(runReader (g y) x),要么调用者也可以将自己包装到 Reader 中,并将 x 的计算委托给它的调用者(变成 h :: a -> Reader x b)。

      本质上,解决方案1,在函数g中引入额外的参数,意味着它的签名是g :: y -> x -> z,它是一个函数g :: y -> (x -> z)。 Reader monad 允许抽象掉 (x -> z) 部分,所以你有 g :: y -> Reader x z。抽象允许绑定需要 x 的函数或将 x 传递给其他函数以统一的方式

      没有 Reader monad:

      h :: x -> z
      h = \x -> g y x -- caller doesn't know how to compute x
        where y = .... -- some computation that h knows how to do
      
      g y = \x -> f x y
      

      (h 可以更简洁地写成 h x = ...,但我特意将其表示为 lambda,因此与下面比较会更容易):

      这与:

      h :: Reader x z
      h = Reader $ \x -> g y x
        where y = ...
      
      g :: y -> x -> z
      g y = \x -> f x y
      

      使用 Reader monad:

      h :: Reader x z
      h = g y
        where y = ...
      
      g :: y -> Reader x z
      g y = Reader $ \x -> f x y
      

      整理一下:

      h :: Reader x z
      h = g y
        where y = ...
      
      g :: y -> Reader x z
      g y = do
              x <- ask
              return $ f x y
      

      现在到 (>>=)。

      (Reader f) >>= g = Reader $ \x -> -- this is the x we are given, 
                                        -- so need to pass it to f and g y
                           case g (f x) of -- g y is Reader x z,
                                           -- so need to call the wrapped x -> z 
                               Reader g' -> g' x
      

      上面的模式匹配同:

      m >>= g = Reader $ \x -> runReader (g (runReader m x)) x
      

      【讨论】:

        【解决方案5】:

        让我们推导出Reader&gt;&gt;=的具体类型方案,然后稍微简化一下。

        -- General monad
        (>>=) :: m a -> (a -> m b) -> m b
        
        -- Reader monad
        (>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b
        
        -- Let me put it in a (syntactically incorrect, but) more illustrative form
        (>>=) :: (Reader r -> a) -> (a -> (Reader r -> b)) -> (Reader r -> b)
        
        -- A reader is a function of type (r -> a), packed into a Reader context.
        -- If we want to access the wrapped function, we can easily do it with runReader.
        
        -- With this in mind, let's see how it would be without the extra context.
        (>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b)
        


        我们可以将&gt;&gt;= 视为一个接受两个参数的函数,m(一个单子值)和f(一个函数),并返回result(另一个单子值)。

        现在让我们为这个简化的类型结构编写一个实现。

        -- Takes m and f, returns result
        m >>= f = result
        
        -- The types
        m      :: r -> a
        f      :: a -> (r -> b)
        result :: r -> b
        
        -- Implementation
        m >>= f = \x -> (f (m x)) x
        
        -- A quick How-We-Got-Here
        mResult = m x  -- :: a
        fResult = f mResult  -- :: r -> b
        result  = \x -> fResult x
                = \x -> (f mResult) x
                = \x -> (f (m x)) x
        


        是时候将Reader 带回游戏中了。 f &gt;&gt;= m = result 的形式仍然存在,但类型发生了一些变化,实现也会发生一些变化。

        -- The types
        m      :: Reader (r -> a)
        f      :: a -> Reader (r -> b)
        result :: Reader (r -> b)
        
        -- Functions we easily used before, are now in a "Reader".
        -- But we can easily unwrap and access them with "runReader".
        
        -- Now "result" is not just a function, but one in a "Reader".
        -- But we can easily wrap it with "Reader".
        
        -- Apply these tools on our How-We-Got-Here analogy from before.
        mResult = (runReader m) x  -- :: a
        fResult = f mResult  -- :: Reader (r -> b)
        result  = Reader $ \x -> (runReader fResult) x
                = Reader $ \x -> (runReader (f mResult)) x
                = Reader $ \x -> (runReader (f ((runReader m) x))) x
        


        毕竟实际的实现。

        m >>= f = Reader $ \x -> (runReader (f ((runReader m) x))) x
        
        -- Remove the unnecessary parens
        m >>= f = Reader $ \x -> runReader (f (runReader m x)) x
        
        -- Different letters
        m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-06-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-11-14
          • 2010-11-28
          相关资源
          最近更新 更多