为什么要将Reader的构造函数参数定义为一个函数呢?
Why to define the constructor parameter of Reader as a function?
在学习Reader Monad时,我发现它被定义为:
newtype Reader r a = Reader { runReader :: r -> a }
instance Monad (Reader r) where
return a = Reader $ \_ -> a
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
我想知道为什么使用函数作为构造函数参数而不是元组等其他东西:
newtype Reader r a = Reader { runReader :: (r, a) }
instance Monad (Reader r) where
-- Here I cannot get r when defining return function,
-- so does that's the reason that must using a function whose input is an "r"?
return a = Reader (r_unknown, a)
m >>= k = Reader (fst $ runReader m) (f (snd $ runReader m))
根据Reader的定义,我们需要一个"environment",我们可以用它来生成一个"value"。我认为 Reader 类型应该包含 "environment" 和 "value" 的信息,所以元组看起来很完美。
首先请注意,您的绑定函数是错误的,无法编译。
如果 Reader
是按照您描述的元组定义的,则会出现问题:
将违反单子法则,例如左标识,其中指出:
return a >>= f == f a
或正确的身份:
m >>= return == m
会被破坏,这取决于 >>=
的实现,因为 >>=
会 忘记 第一个参数或第二个参数的第一个元组元素,即如果实现是:
(Reader (mr, mv)) >>= f =
let (Reader (fr, fv)) = f mv
in Reader (mr, fv)
那么我们总是会丢失来自 f
(又名 fr
)的 reader 值,否则如果 >>=
将是
(Reader (mr, mv)) >>= f =
let (Reader (fr, fv)) = f mv
in Reader (fr, fv)
-- ^^^ tiny difference here ;)
我们总是输mr
。
- A
Reader
是一些动作,可能 ask
是一个常量值,不能被另一个单子动作改变,即 read-only.
但是当用元组定义时,我们可以 super-easy 覆盖 reader 值,例如具有此功能:
tell :: x -> BadReader x ()
tell x = BadReader (x, ())
如果 reader 是用函数定义的,这是不可能的(试一试)
- 此外,enviroment 实际上不需要 before 将
Reader
转换为纯值(又名 运行ning Reader),因此仅从这一点来看,使用函数而不是元组是有意义的。
使用元组时,我们甚至必须在实际 运行 操作之前提供 Reader
值。
您可以看到,在您的 return
定义中,您甚至指出了 r_unknown
来自哪里的问题...
为了获得更好的直觉,让我们假设一个 Reader
动作 returns Person
与 Addressbook
中的某个 age
:
data Person = MkPerson {name :: String, age :: Int}
type Addressbook = [Person]
personsWithThisAge :: Int -> Reader Addressbook [Person]
personsWithThisAge a = do
addressbook <- ask
return (filter (\p -> age p == a) addressbook)
这个 personsWithAge
函数 returns 一个 Reader
动作,因为它只有 ask
s 用于 Addressbook
,它就像一个函数接受一个地址簿并返回一个 [Person]
列表,
所以很自然地定义一个 reader 就是这样,一个从一些输入到结果的函数。
我们可以将此 Reader
操作重写为 Addressbook
的函数,如下所示:
personsWithThisAgeFun :: Int -> Addressbook -> [Person]
personsWithThisAgeFun a addressbook =
filter (\p -> age p == a) addressbook
但是为什么发明Reader
??
Reader
的实际价值在组合多个函数时显示,例如personsWithThisAge
,这一切都取决于(相同的)一个常数 Addressbook
。
使用 Reader
我们不必显式传递一些 Addressbook
,单个 Reader
操作甚至根本没有任何方法可以修改 Addressbook
- Reader
向我们保证,每个动作都使用 相同的、未修改的 Addressbook
,并且所有 Reader
动作都可以与 环境是ask
。
具有这些保证的唯一实现方法是使用函数。
此外,如果您查看标准库中包含的 monad 实例,您会发现 (r ->)
是一个 monad;实际上,除了一些技术差异外,它与 Reader
monad 相同。
现在你用元组描述的结构实际上非常接近 Writer
monad,什么是 write-only ,但这超出了范围。
您没有在问题中提及它,但我猜您特别想到使用一对来定义 Reader
,因为将其视为提供固定环境的一种方式也很有意义。假设我们在 Reader
monad 中有一个较早的结果:
return 2 :: Reader Integer Integer
我们可以使用此结果对固定环境进行进一步计算(Monad
方法保证它在整个 (>>=)
链中保持固定):
GHCi> runReader (return 2 >>= \x -> Reader (\r -> x + r)) 3
5
(如果你在上面的表达式中替换return
、(>>=)
和runReader
的定义并简化它,你会看到它是如何减少到2 + 3
.)
现在,让我们按照您的建议定义:
newtype Env r a = Env { runEnv :: (r, a) }
如果我们有一个类型为 r
的环境和一个类型为 a
的先前结果,我们可以从中创建一个 Env r a
...
Env (3, 2) :: Env Integer Integer
...我们也可以从中得到一个新的结果:
GHCi> (\(r, x) -> x + r) . runEnv $ Env (3, 2)
5
那么问题是我们是否可以通过 Monad
接口捕获此模式。答案是不。虽然是一个Monad
对实例,但它做了一些完全不同的事情:
newtype Writer r a = Writer { Writer :: (r, a) }
instance Monoid r => Monad (Writer r) where
return x = (mempty, x)
m >>= f = Writer
. (\(r, x) -> (\(s, y) -> (mappend r s, y)) $ f x)
$ runWriter m
需要 Monoid
约束,以便我们可以使用 mempty
(这解决了您注意到的必须凭空创建 r_unknown
的问题)和 mappend
(这使得以不违反 monad 法则的方式组合 pair 的第一个元素成为可能)。但是,这个 Monad
实例与 Reader
实例做的事情非常不同。该对的第一个元素不是固定的(它可能会发生变化,因为我们 mappend
其他生成的值)并且我们不使用它来计算该对的第二个元素(在上面的定义中, y
既不依赖于 r
也不依赖于 s
)。 Writer
是记录器; r
这里的值是输出,不是输入。
然而,有一种方法可以证明您的直觉:我们不能使用一对来制作 reader-like monad,但我们可以制作 reader-like co单子。说的很笼统,Comonad
就是把Monad
界面倒过来得到的:
-- This is slightly different than what you'll find in Control.Comonad,
-- but it boils down to the same thing.
class Comonad w where
extract :: w a -> a -- compare with return
(=>>) :: w a -> (w a -> b) -> w b -- compare with (>>=)
我们可以给我们已经放弃的 Env
一个 Comonad
实例:
newtype Env r a = Env { runEnv :: (r, a) }
instance Comonad (Env r) where
extract (Env (_, x)) = x
w@(Env (r, _)) =>> f = Env (r, f w)
这让我们可以根据 (=>>)
:
从头开始编写 2 + 3
示例
GHCi> runEnv $ Env (3, 2) =>> ((\(r, x) -> x + r) . runEnv)
(3,5)
了解此方法为何有效的一种方法是注意 a -> Reader r b
函数(即您赋予 Reader
的 (>>=)
的内容)与 Env r a -> b
一个(即你给 Env
的 (=>>)
的东西):
a -> Reader r b
a -> (r -> b) -- Unwrap the Reader result
r -> (a -> b) -- Flip the function
(r, a) -> b -- Uncurry the function
Env r a -> b -- Wrap the argument pair
作为进一步的证据,这里有一个函数可以将一个变成另一个:
GHCi> :t \f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
\f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
:: (t -> Reader r a) -> Env r t -> a
GHCi> -- Or, equivalently:
GHCi> :t \f -> uncurry (flip (runReader . f)) . runEnv
\f -> uncurry (flip (runReader . f)) . runEnv
:: (a -> Reader r c) -> Env r a -> c
总结一下,这里有一个稍长的示例,有 Reader
和 Env
版本 side-by-side:
GHCi> :{
GHCi| flip runReader 3 $
GHCi| return 2 >>= \x ->
GHCi| Reader (\r -> x ^ r) >>= \y ->
GHCi| Reader (\r -> y - r)
GHCi| :}
5
GHCi> :{
GHCi| extract $
GHCi| Env (3, 2) =>> (\w ->
GHCi| (\(r, x) -> x ^ r) $ runEnv w) =>> (\z ->
GHCi| (\(r, x) -> x - r) $ runEnv z)
GHCi| :}
5
在学习Reader Monad时,我发现它被定义为:
newtype Reader r a = Reader { runReader :: r -> a }
instance Monad (Reader r) where
return a = Reader $ \_ -> a
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
我想知道为什么使用函数作为构造函数参数而不是元组等其他东西:
newtype Reader r a = Reader { runReader :: (r, a) }
instance Monad (Reader r) where
-- Here I cannot get r when defining return function,
-- so does that's the reason that must using a function whose input is an "r"?
return a = Reader (r_unknown, a)
m >>= k = Reader (fst $ runReader m) (f (snd $ runReader m))
根据Reader的定义,我们需要一个"environment",我们可以用它来生成一个"value"。我认为 Reader 类型应该包含 "environment" 和 "value" 的信息,所以元组看起来很完美。
首先请注意,您的绑定函数是错误的,无法编译。
如果 Reader
是按照您描述的元组定义的,则会出现问题:
将违反单子法则,例如左标识,其中指出:
return a >>= f == f a
或正确的身份:
m >>= return == m
会被破坏,这取决于 >>=
的实现,因为 >>=
会 忘记 第一个参数或第二个参数的第一个元组元素,即如果实现是:
(Reader (mr, mv)) >>= f =
let (Reader (fr, fv)) = f mv
in Reader (mr, fv)
那么我们总是会丢失来自 f
(又名 fr
)的 reader 值,否则如果 >>=
将是
(Reader (mr, mv)) >>= f =
let (Reader (fr, fv)) = f mv
in Reader (fr, fv)
-- ^^^ tiny difference here ;)
我们总是输mr
。
- A
Reader
是一些动作,可能ask
是一个常量值,不能被另一个单子动作改变,即 read-only.
但是当用元组定义时,我们可以 super-easy 覆盖 reader 值,例如具有此功能:
tell :: x -> BadReader x ()
tell x = BadReader (x, ())
如果 reader 是用函数定义的,这是不可能的(试一试)
- 此外,enviroment 实际上不需要 before 将
Reader
转换为纯值(又名 运行ning Reader),因此仅从这一点来看,使用函数而不是元组是有意义的。
使用元组时,我们甚至必须在实际 运行 操作之前提供 Reader
值。
您可以看到,在您的 return
定义中,您甚至指出了 r_unknown
来自哪里的问题...
为了获得更好的直觉,让我们假设一个 Reader
动作 returns Person
与 Addressbook
中的某个 age
:
data Person = MkPerson {name :: String, age :: Int}
type Addressbook = [Person]
personsWithThisAge :: Int -> Reader Addressbook [Person]
personsWithThisAge a = do
addressbook <- ask
return (filter (\p -> age p == a) addressbook)
这个 personsWithAge
函数 returns 一个 Reader
动作,因为它只有 ask
s 用于 Addressbook
,它就像一个函数接受一个地址簿并返回一个 [Person]
列表,
所以很自然地定义一个 reader 就是这样,一个从一些输入到结果的函数。
我们可以将此 Reader
操作重写为 Addressbook
的函数,如下所示:
personsWithThisAgeFun :: Int -> Addressbook -> [Person]
personsWithThisAgeFun a addressbook =
filter (\p -> age p == a) addressbook
但是为什么发明Reader
??
Reader
的实际价值在组合多个函数时显示,例如personsWithThisAge
,这一切都取决于(相同的)一个常数 Addressbook
。
使用 Reader
我们不必显式传递一些 Addressbook
,单个 Reader
操作甚至根本没有任何方法可以修改 Addressbook
- Reader
向我们保证,每个动作都使用 相同的、未修改的 Addressbook
,并且所有 Reader
动作都可以与 环境是ask
。
具有这些保证的唯一实现方法是使用函数。
此外,如果您查看标准库中包含的 monad 实例,您会发现 (r ->)
是一个 monad;实际上,除了一些技术差异外,它与 Reader
monad 相同。
现在你用元组描述的结构实际上非常接近 Writer
monad,什么是 write-only ,但这超出了范围。
您没有在问题中提及它,但我猜您特别想到使用一对来定义 Reader
,因为将其视为提供固定环境的一种方式也很有意义。假设我们在 Reader
monad 中有一个较早的结果:
return 2 :: Reader Integer Integer
我们可以使用此结果对固定环境进行进一步计算(Monad
方法保证它在整个 (>>=)
链中保持固定):
GHCi> runReader (return 2 >>= \x -> Reader (\r -> x + r)) 3
5
(如果你在上面的表达式中替换return
、(>>=)
和runReader
的定义并简化它,你会看到它是如何减少到2 + 3
.)
现在,让我们按照您的建议定义:
newtype Env r a = Env { runEnv :: (r, a) }
如果我们有一个类型为 r
的环境和一个类型为 a
的先前结果,我们可以从中创建一个 Env r a
...
Env (3, 2) :: Env Integer Integer
...我们也可以从中得到一个新的结果:
GHCi> (\(r, x) -> x + r) . runEnv $ Env (3, 2)
5
那么问题是我们是否可以通过 Monad
接口捕获此模式。答案是不。虽然是一个Monad
对实例,但它做了一些完全不同的事情:
newtype Writer r a = Writer { Writer :: (r, a) }
instance Monoid r => Monad (Writer r) where
return x = (mempty, x)
m >>= f = Writer
. (\(r, x) -> (\(s, y) -> (mappend r s, y)) $ f x)
$ runWriter m
需要 Monoid
约束,以便我们可以使用 mempty
(这解决了您注意到的必须凭空创建 r_unknown
的问题)和 mappend
(这使得以不违反 monad 法则的方式组合 pair 的第一个元素成为可能)。但是,这个 Monad
实例与 Reader
实例做的事情非常不同。该对的第一个元素不是固定的(它可能会发生变化,因为我们 mappend
其他生成的值)并且我们不使用它来计算该对的第二个元素(在上面的定义中, y
既不依赖于 r
也不依赖于 s
)。 Writer
是记录器; r
这里的值是输出,不是输入。
然而,有一种方法可以证明您的直觉:我们不能使用一对来制作 reader-like monad,但我们可以制作 reader-like co单子。说的很笼统,Comonad
就是把Monad
界面倒过来得到的:
-- This is slightly different than what you'll find in Control.Comonad,
-- but it boils down to the same thing.
class Comonad w where
extract :: w a -> a -- compare with return
(=>>) :: w a -> (w a -> b) -> w b -- compare with (>>=)
我们可以给我们已经放弃的 Env
一个 Comonad
实例:
newtype Env r a = Env { runEnv :: (r, a) }
instance Comonad (Env r) where
extract (Env (_, x)) = x
w@(Env (r, _)) =>> f = Env (r, f w)
这让我们可以根据 (=>>)
:
2 + 3
示例
GHCi> runEnv $ Env (3, 2) =>> ((\(r, x) -> x + r) . runEnv)
(3,5)
了解此方法为何有效的一种方法是注意 a -> Reader r b
函数(即您赋予 Reader
的 (>>=)
的内容)与 Env r a -> b
一个(即你给 Env
的 (=>>)
的东西):
a -> Reader r b
a -> (r -> b) -- Unwrap the Reader result
r -> (a -> b) -- Flip the function
(r, a) -> b -- Uncurry the function
Env r a -> b -- Wrap the argument pair
作为进一步的证据,这里有一个函数可以将一个变成另一个:
GHCi> :t \f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
\f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
:: (t -> Reader r a) -> Env r t -> a
GHCi> -- Or, equivalently:
GHCi> :t \f -> uncurry (flip (runReader . f)) . runEnv
\f -> uncurry (flip (runReader . f)) . runEnv
:: (a -> Reader r c) -> Env r a -> c
总结一下,这里有一个稍长的示例,有 Reader
和 Env
版本 side-by-side:
GHCi> :{
GHCi| flip runReader 3 $
GHCi| return 2 >>= \x ->
GHCi| Reader (\r -> x ^ r) >>= \y ->
GHCi| Reader (\r -> y - r)
GHCi| :}
5
GHCi> :{
GHCi| extract $
GHCi| Env (3, 2) =>> (\w ->
GHCi| (\(r, x) -> x ^ r) $ runEnv w) =>> (\z ->
GHCi| (\(r, x) -> x - r) $ runEnv z)
GHCi| :}
5