Haskell 读者monad,bind读者是如何工作的?

Haskell 读者monad,bind读者是如何工作的?,haskell,monads,Haskell,Monads,我是哈斯克尔的初学者。我学习了如何创建阅读器以及如何查询共享变量。我查看了Hugs98中Reader.hs的源代码 instance Monad (Reader r) where return a = Reader $ \_ -> a m >>= k = Reader $ \r -> runReader (k (runReader m r)) r 这里我可以看到(returna)创建了一个读取器,它包装了一个函数,该函数接受一个

我是哈斯克尔的初学者。我学习了如何创建阅读器以及如何查询共享变量。我查看了Hugs98中Reader.hs的源代码

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

m >>= k  = Reader $ \r -> 
                  runReader (k (runReader m r)) r
这里我可以看到(returna)创建了一个读取器,它包装了一个函数,该函数接受一个值并返回一个值

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

其次,对我来说,执行是多么的模糊 我不明白将k应用于(runReader m r)的结果有什么意义


谢谢

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

runReader m r
好的,这是运行
m
,将
r
作为要读取的输入

k (runReader m r)
这将获取运行
m
的输出,并将其传递给
k
。这使得
k
返回另一个读卡器

这将获取由
k
返回的读取器并运行该读取器(使用相同的输入
r
进行读取)


你明白了吗?

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

runReader m r
好的,这是运行
m
,将
r
作为要读取的输入

k (runReader m r)
这将获取运行
m
的输出,并将其传递给
k
。这使得
k
返回另一个读卡器

这将获取由
k
返回的读取器并运行该读取器(使用相同的输入
r
进行读取)

你明白了吗?

读卡器的定义如下:

newtype Reader r a = Reader { runReader :: r -> a }
所以它实际上只是一个
r->a
类型的函数,带有一些额外的封装。这是有道理的,因为读者实际上只是为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
类型的函数,另一个是将is视为一个函数,它取一个
a
和一个
r
并返回一个
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
函数,那么
r->a
函数可以提供一个。我们有一个
r
,这是我们的第三个输入。因此,我们可以简单地应用函数,直到得到
b

(>>=) rToA aAndRToB r = b where
  a = rToA r
  b = aAndRToB a r
在这里,您可以看到,我们为每个动作提供
r
-值(这是读卡器monad的目标),同时还将
a
-值从一个动作链接到下一个动作(这是
(>>=)
)的目标)。您也可以模仿第一个类型签名的方式编写此文件,如下所示:

(>>=) rToA aToRToB = \r -> (aToRToB (rToA r)) r
如果重命名变量,看起来与读卡器绑定的定义非常相似,但不使用
Reader
runReader

m >>= k = /r -> k (m r) r
读卡器的定义如下:

newtype Reader r a = Reader { runReader :: r -> a }
所以它实际上只是一个
r->a
类型的函数,带有一些额外的封装。这是有道理的,因为读者实际上只是为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
类型的函数,另一个是将is视为一个函数,它取一个
a
和一个
r
并返回一个
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
函数,那么
r->a
函数可以提供一个。我们有一个
r
,这是我们的第三个输入。因此,我们可以简单地应用函数,直到得到
b

(>>=) rToA aAndRToB r = b where
  a = rToA r
  b = aAndRToB a r
在这里,您可以看到,我们为每个动作提供
r
-值(这是读卡器monad的目标),同时还将
a
-值从一个动作链接到下一个动作(这是
(>>=)
)的目标)。您也可以用模仿第一个类型签名的方式编写它
-- 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)
-- 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
-- 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