1

我是 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 包装了一个函数,该函数接受一个值并返回一个

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

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

谢谢

4

5 回答 5

3

读者定义为:

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)并看到 this 等价于a -> r -> a,您可以看到您可以通过两种方式查看此函数。一个是你接受一个参数a并返回一个类型的函数r -> a,另一个是把 is 看成一个接受一个a和一个r并返回一个的函数a。您可以使用以下任一视图实现 return:

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每个动作提供了 -value(这是 Reader monad 的目标),同时还将a-value 从一个动作链接到下一个动作(这是 的目标(>>=))。你也可以用模仿第一个类型签名的方式编写它,如下所示:

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

如果您重命名变量,它看起来与 Reader 绑定的定义非常相似,但不使用Readerand runReader

m >>= k = /r -> k (m r) r
于 2013-08-28T08:27:24.073 回答
2

>>=又名 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 = ...在哪里,在哪里,我们m需要返回Reader r ak(a -> Reader r b)Reader r b

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

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

我们如何获得afrom的价值Reader r arunReader有一个类型Reader r a -> r -> a,所以如果我们调用我们会得到runReader,但我们正在寻找类型的值,我们得到的是,似乎我们没有任何价值得到。似乎我们被卡住了,因为我们没有任何其他参数要寻找。m(r -> a)a(r -> a)ra

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

let a_val = runReader m r_val给我们 type 的值a

a我们需要得到Reader r b使用 k

let reader_r_b_val = k a_val给了我们类型的值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成 aReader r b吗?

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

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

于 2013-08-28T09:07:23.790 回答
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的读取输入)。

你都遵守了吗?

于 2013-08-28T07:56:14.043 回答
1

首先,Reader 的用途。

假设您有一个纯函数 fxy 和一个纯函数 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 (gy) x),要么调用者也可以将自己包装到 Reader 中,并将 x 的计算委托给它的调用者(变成 h :: a -> Reader xb)。

本质上,解决方案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 可以更简洁地写成 hx = ...,但我故意将其表示为 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
于 2013-08-28T12:34:30.003 回答
0

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

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


我们可以将其>>=视为一个函数,它接受两个参数,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 >>= 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
于 2014-08-05T11:35:22.820 回答