在学习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的定义,我们需要一个“环境”来生成“价值”。我认为Reader类型应该包含“environment”和“value”的信息,所以元组看起来很完美。
答案 0 :(得分:5)
你在问题中没有提到它,但我猜你特意想到使用一对来定义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
生成... {/ p>
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定律的方式组合对中的第一个元素)。但是,这个Monad
实例与Reader
实例的做法非常不同。该对中的第一个元素未被修复(它可能会发生变化,因为我们mappend
生成了其他值}并且我们不会使用它来计算该对的第二个元素(在上面的定义中,y
既不依赖r
也不依赖s
)。 Writer
是记录器;这里的r
值是输出,而不是输入。
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
基本相同{1}}一个(即您提供给Env
的{{1}}):
(=>>)
作为进一步的证据,这是一个将一个变为另一个的函数:
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
答案 1 :(得分:3)
首先请注意,你的绑定函数是错误的,不会编译。
如果您使用元组描述Reader
,则会出现问题:
monad法律将被违反,例如左侧身份,其中指出:
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 (mr, mv)) >>= f =
let (Reader (fr, fv)) = f mv
in Reader (fr, fv)
-- ^^^ tiny difference here ;)
我们总是会失去mr
。
Reader
是一些操作,可能ask
为常量值,不能通过另一个monadic操作更改,只读。但是当使用元组定义时,我们可以超级容易地覆盖读取器值,例如:这个功能:
tell :: x -> BadReader x ()
tell x = BadReader (x, ())
如果用一个函数定义了一个阅读器,这是不可能的(试一试)
Reader
转换为纯值(也就是运行Reader),所以仅此一点就可以了使用函数而不是元组的意义。 使用元组时,我们必须在实际执行操作之前提供Reader
值。
您可以在return
定义中看到,您甚至可以指出问题,r_unknown
的来源......
为了获得直觉,让我们假设Reader
动作从Person
返回age
来自Addressbook
的{{1}}:
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
函数返回Reader
个操作,因为ask
只有Addressbook
个,所以它就像一个函数接受一个地址簿并返回一个{ {1}}列表,
所以很自然地将读者定义为从某个输入到结果的函数。
我们可以将此[Person]
操作重写为Reader
的函数,如下所示:
Addressbook
但为什么要发明 personsWithThisAgeFun :: Int -> Addressbook -> [Person]
personsWithThisAgeFun a addressbook =
filter (\p -> age p == a) addressbook
??
Reader
的实际值显示何时组合多个函数,例如Reader
,全部依赖于(相同)一个常量personsWithThisAge
。
使用Addressbook
我们不必明确传递一些Reader
,个别Addressbook
行动甚至根本无法修改Reader
{1}} - Addressbook
向我们保证,每个操作都使用相同的,未修改的 Reader
,并且所有Addressbook
操作都可以使用环境为Reader
。
实现这一目标的唯一方法是使用这些功能。
另外,如果你看一下标准库中包含的monad实例,你会发现ask
是一个monad;实际上除了一些技术差异外,它与(r ->)
monad相同。
现在你用元组描述的结构实际上非常接近Reader
monad,什么是只写,但这超出了范围。