读者monad,读者如何运作?

时间:2013-08-28 07:11:02

标签: haskell monads

我是Haskell的初学者。我学会了如何创建一个Reader以及如何查询共享变量。我查看了Hugs98中Reader.hs的源代码

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

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

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

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

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

谢谢

5 个答案:

答案 0 :(得分:3)

Reader定义为:

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

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

如果我们剥离封装并仅使用r -> a函数,则monadic函数的类型为:

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 a = \r -> a -- or: return a r = a

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

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

所以我们有r类型的值,类型为r -> a的函数和类型为a -> r -> b的函数,我们的目标是创建类型为b的值出于那个。我们输入中的唯一b位于a -> r -> b函数中,因此我们需要使用它,但我们没有a来提供它,所以我们需要得到一个。 r -> a函数可以提供一个,如果我们有r的话。我们有一个r,这是我们的第三个输入。所以我们可以简单地应用这些函数,直到得到b

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

在这里你可以看到我们为每个动作提供了r - 值(这是Reader monad的目标),同时还将a - 值从一个动作链接到下一个动作(其中是(>>=))的目标。你也可以用模仿第一种类型签名的方式写这个:

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

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

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

答案 1 :(得分:2)

>>= aka bind具有签名:m a -> (a -> m b) -> m b。如果我们尝试将此签名设为特定于Reader,即用m替换Reader r,我们会发现它变为:

(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的{​​{1}}参数看起来可以帮助我们获取Reader r a类型的值。

我们如何从a获得a的价值? Reader r a的类型为runReader,因此,如果我们在Reader r a -> r -> a上致电runReader,我们会m,但我们正在寻找(r -> a)类型的值我们得到的是a,似乎我们没有任何(r -> a)值来获取r。我们似乎陷入困境,因为我们没有任何其他参数可供查找。

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

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

来自a我们需要使用k

获取a

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

Reader r b这是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),即我们需要转换内部{{1}只需Reader r b,为此我们可以使用Reader r b所以:

b

答案 2 :(得分:1)

好的,让我们看一下m >>= k。此处m是读者,k函数,可生成阅读器。这是做什么的?

runReader m r

好的,所以这是m正在运行r作为要读取的输入。

k (runReader m r)

这会从运行m获取输出并将其传递给k。这使k返回另一个读者。

runReader (k (runReader m r)) r

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

你遵循这一切吗?

答案 3 :(得分:1)

首先,读者的目的是什么。

考虑你有一个纯函数f x y和一个纯函数g y。现在你弄清楚g需要在里面使用f,但它只有一个可以提供f的参数!典型的解决方案是以下之一:

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

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

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

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

    本质上,解决方案1,在函数g中引入额外的参数,意味着它的签名是g :: y - > x - > z,这是函数g :: y - > (x - > z)。读者monad允许抽象掉(x - > z)部分,所以你有g :: y - >读者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
    

    现在到(&gt;&gt; =)。

    (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
    

答案 4 :(得分:0)

让我们为Reader的{​​{1}}推导出特定的类型方案,然后稍微简化一下。

>>=


我们可以将-- 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) 视为一个函数,它接受两个参数>>=(一个monadic值)和m(一个函数),并返回f(另一个monadic值) 。

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

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 >>= 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