我是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)的结果?
谢谢
答案 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
的函数,另一个是查看作为一个函数,它采用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 -> 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的绑定定义,但没有使用Reader
和runReader
:
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 = ...
其中m
为Reader r a
且k
为(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的参数!典型的解决方案是以下之一:
修改API,使g现在有两个参数,x和y(调用者现在必须计算它,即使g不调用f);
创建一个全局变量,g将读取以提供带有该参数的f(g不再是纯粹的);
创建一个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)。读者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