functional-programming - writer monad 和 list writer monad 有什么区别
问题描述
我正在查看 writer monad 的示例以了解它是如何工作的,几乎所有这些看起来都像 list writer monad。我知道列表作家单子是作家单子的一种。但真正用外行的话来说是作家单子。
解决方案
通俗地说,writer monad 是让你在产生值的同时将项目“写入”到“日志”的 monad。完成后,您将得到您生成的值和包含您编写的所有内容的日志。换句话说,monad 的副作用是“将内容写入日志”。
让我们通过列表编写器和(通用)编写器 monad 的示例使这一点更加具体。我将在这里使用 Haskell,因为它是描述函数式编程的 Monad的原始上下文。
列表编写者 Monad
我假设“列表编写器”monad 是一个将项目(我们将调用的某种类型w
)记录到项目列表([w]
当然是类型)中的单子。它还产生一个类型的值a
。(如果您自己使用此代码时遇到错误,请参阅底部的注释。)
newtype ListWriter w a = ListWriter { runListWriter :: ([w], a) }
instance Monad (ListWriter w) where
return a = ListWriter ([], a) -- produce an a, don't log anything
ListWriter (ws, a) >>= k =
let ListWriter (xs, a') = k a -- run the action 'k' on the existing value,
in ListWriter (ws ++ xs, a') -- add anything it logs to the existing log,
-- and produce a new result value
-- Add an item to the log and produce a boring value.
-- When sequenced with >>, this will add the item to existing log.
tell :: w -> ListWriter w ()
tell w = ListWriter ([w], ())
ex1 :: ListWriter String Int
ex1 = do
tell "foo"
tell "bar"
return 0
(注意:这相当于ex1 = tell "foo" >> tell "bar" >> return 0
,演示了使用tell
with>>
向日志添加项目。)
如果我们runListWriter ex1
在 GHCi 中进行评估,我们会看到它在日志中写入了“foo”和“bar”并产生了结果值0
。
λ> runListWriter ex1
(["foo","bar"],0)
(通用)作家 Monad
现在,让我们看看我们如何把它变成通用的 writer monad。writer monad 可以处理任何可以组合在一起的东西,而不仅仅是一个列表。具体来说,它适用于任何Monoid
:
class Monoid m where
mempty :: m -- an empty m
mappend :: m -> m -> m -- combine two m's into a single m
列表是一个 Monoid,分别带有[]
和(++)
asmempty
和mappend
。Monoid 的非列表示例是整数之和:
λ> Sum 1 <> Sum 2 -- (<>) = mappend
Sum {getSum = 3}
那么作家单子是
newtype Writer w m = Writer { runWriter :: (w, m) }
w
我们只有一个 w,而不是 's 列表。但是当我们定义 Monad 时,我们确保它w
是 a Monoid
,因此我们可以从一个空日志开始并在日志中附加一个新条目:
instance Monoid w => Monad (Writer w) where
return a = Writer (mempty, a) -- produce an a, don't log anything
Writer (w, a) >>= k =
let Writer (x, a') = k a -- we combine the two w's rather than
in Writer (w <> x, a') -- (++)-ing two lists
注意这里的区别:我们使用mempty
代替[]
和(<>)
代替(++)
。这就是我们从列表泛化到任何 Monoid 的方式。
所以 writer monad 实际上是 list monad 对可以组合的任意事物的概括,而不仅仅是列表。您可以使用带有 的列表Writer
来获得(几乎)等效于ListWriter
. 唯一的区别是,当您将记录的项目附加到日志时,您必须将其包装在列表中:
ex2 :: Writer [String] Int
ex2 = do
tell ["foo"]
tell ["bar"]
return 0
但你得到相同的结果:
λ> runWriter ex2
(["foo","bar"],0)
这是因为您不是在记录“将被放入列表中的项目”,而是在记录“列表”。(这确实意味着您可以通过传递多个元素的列表来同时记录多个项目。)
对于 Writer 的非列表使用示例,请考虑计算排序函数进行的比较。每次您的函数进行比较时,您都可以tell (Sum 1)
. (你可以告诉别人。明白了吗?这东西在吗?)然后,最后,你会得到所有比较的总数(即总和)以及排序列表。
注意:如果您尝试自己使用这些ListWriter
和定义,Writer
GHC 会告诉您缺少实例。一旦你有了实例,你可以用它的术语来写其他的:Functor
Applicative
Monad
import Control.Monad (ap, liftM)
instance Functor (ListWriter w) where
fmap = liftM
instance Applicative (ListWriter w) where
pure = return
(<*>) = ap
同样对于Writer
. 为了清楚起见,我在上面省略了它们。
推荐阅读
- javascript - 以 \\\ 作为替换值的未终止字符串常量
- module - Arduino Neo 6m GPS 模块发回奇怪的数字和符号
- javascript - 在 Vanilla JS 中单击父级时仅打开一个子菜单
- bash - 使用 bash 检查最近的文件编辑,并执行基于时间的逻辑
- laravel - 如何使用多个数据库连接运行 SQL 插入
- terminal - 为什么 .rb 没有执行?
- sql - 子查询返回超过 1 个值。当子查询跟随 =、!=、<、<=、>、>= 或当子查询用作表达式时,这是不允许的
- git - 获取触发 Jenkins 构建的 Git 分支名称
- python - 使用boto3在s3中上传pil图像对象
- python - 在python中将文件存储在剪贴板中(跨平台)