单子、组成和计算顺序

2023-12-09

所有 monad 文章经常指出,monad 允许您按顺序对效果进行排序。

但是简单的构图又如何呢?不是

f x = x + 1
g x = x * 2

result = f g x

需要g x之前要计算f ...?

单子是否做同样的事情,但处理效果?


免责声明:单子有很多东西。众所周知,它们很难解释,所以我不会尝试解释什么是单子一般来说在这里,因为这个问题没有要求这一点。我假设你对什么有基本的了解Monad接口以及它如何处理一些有用的数据类型,例如Maybe, Either, and IO.


什么是效果?

你的问题以注释开头:

所有 monad 文章经常指出,monad 允许您按顺序对效果进行排序。

唔。这很有趣。事实上,它很有趣,有几个原因,其中之一你已经确定了:它意味着 monad 可以让你创建某种排序。确实如此,但这只是图片的一部分:它also表明测序发生在effects.

但问题是……什么是“效果”?两个数相加有效果吗?根据大多数定义,答案是否定的。将某些内容打印到标准输出怎么样,这是效果吗?那么,我想大多数人都会同意答案是肯定的。然而,考虑一些更微妙的事情:通过产生短路计算Nothing效果?

错误影响

让我们看一个例子。考虑以下代码:

> do x <- Just 1
     y <- Nothing
     return (x + y)
Nothing

该示例的第二行“短路”是由于Monad实例为Maybe。这也能算是一种效果吗?从某种意义上说,我认为是这样,因为它是非本地的,但从另一种意义上说,可能不是。毕竟,如果x <- Just 1 or y <- Nothing行交换后,结果仍然相同,因此顺序并不重要。

然而,考虑一个稍微复杂的例子,它使用Either代替Maybe:

> do x <- Left "x failed"
     y <- Left "y failed"
     return (x + y)
Left "x failed"

现在这更有趣了。如果你现在交换前两行,你会得到不同的结果!不过,这是否像您在问题中提到的那样代表了“效果”?毕竟,这只是一堆函数调用。如你所知,do符号只是一系列用法的替代语法>>=运算符,所以我们可以将其展开:

> Left "x failed" >>= \x ->
    Left "y failed" >>= \y ->
      return (x + y)
Left "x failed"

我们甚至可以替换>>=运算符与Either-完全摆脱单子的特定定义:

> case Left "x failed" of
    Right x -> case Left "y failed" of
      Right y -> Right (x + y)
      Left e -> Left e
    Left e -> Left e
Left "x failed"

因此,很明显 monad 确实强加了某种排序,但这并不是因为它们是 monad,也不是因为 monad 很神奇,只是因为它们碰巧启用了一种编程风格,looks比 Haskell 通常允许的更不纯粹。

单子和状态

但也许这并不令你满意。错误处理并不引人注目,因为它只是短路,结果实际上没有任何排序!好吧,如果我们达到一些稍微复杂的类型,我们就可以做到。例如,考虑Writer类型,它允许使用单子接口进行某种“日志记录”:

> execWriter $ do
    tell "hello"
    tell " "
    tell "world"
"hello world"

这比以前更有趣,因为现在每个计算的结果都在doblock没有被使用,但是仍然影响输出!这显然是有副作用的,而且顺序显然非常重要!如果我们重新排序tell表达式,我们会得到非常不同的结果:

> execWriter $ do
    tell " "
    tell "world"
    tell "hello"
" worldhello"

但这怎么可能呢?好吧,我们再次重写它以避免do符号:

execWriter (
  tell "hello" >>= \_ ->
    tell " " >>= \_ ->
      tell "world")

我们可以内联以下定义>>=再次为了Writer,但它太长了,无法在这里很好地说明。但重点是,Writer只是一个完全普通的 Haskell 数据类型,不执行任何 I/O 或类似操作,但我们使用单子接口来创建看起来像有序效果的东西。

我们可以更进一步,创建一个如下所示的界面可变状态使用State type:

> flip execState 0 $ do
    modify (+ 3)
    modify (* 2)
6

再次,如果我们重新排序表达式,我们会得到不同的结果:

> flip execState 0 $ do
    modify (* 2)
    modify (+ 3)
3

显然,单子是创建接口的有用工具,look尽管实际上只是普通的函数调用,但它是有状态的并且具有明确定义的顺序。

为什么 monad 可以做到这一点?

是什么赋予单子这样的力量?好吧,它们并不神奇——它们只是普通的纯 Haskell 代码。但请考虑类型签名>>=:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

请注意第二个参数如何取决于a,并且获得的唯一方法是a是从第一个参数开始的吗?这意味着>>=需要“运行”第一个参数来产生一个值before它可以应用第二个参数。这与评估顺序无关,而与实际编写进行类型检查的代码有关。

现在,Haskell 确实是一种懒惰的语言。但 Haskell 的懒惰对于这一切来说并不重要,因为所有这些代码实际上都是纯粹的,即使是使用的示例State!它只是一种以纯粹的方式对计算进行编码的模式,看起来有点有状态,但如果您实际上实现了State你自己,你会发现它只是传递了定义中的“当前状态”>>=功能。没有任何实际的突变。

就是这样。 Monad 凭借其接口,对如何评估其参数以及实例施加了排序Monad利用它来制作有状态的界面。你不need Monad不过,正如您所发现的那样,要进行评估排序;显然在(1 + 2) * 3加法将在乘法之前进行计算。

但是关于IO??

好吧,你明白了。问题是这样的:IO是魔法。

Monad 不是魔法,但是IO is.上面的所有示例都是纯函数式的,但显然读取文件或写入 stdout 并不是纯粹的。那么到底是怎么做的IO work?

Well, IO由 GHC 运行时实现,您无法自己编写。然而,为了使其与 Haskell 的其余部分很好地工作,需要有一个明确定义的评估顺序!否则,事情就会以错误的顺序打印出来,并且各种其他地狱都会爆发。

好吧,事实证明Monad的接口是确保评估顺序可预测的好方法,因为它已经适用于纯代码。所以IO利用相同的接口来保证评估顺序相同,并且运行时实际上定义了该评估的含义。

不过,不要被误导!你不需要 monad 来用纯语言进​​行 I/O,也不需要IO具有单元效应。 Haskell 的早期版本尝试使用非单子方式进行 I/O,这个答案的其他部分解释了如何获得纯粹的单子效应。请记住,单子并不特殊或神圣,它们只是 Haskell 程序员因其各种属性而发现有用的一种模式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单子、组成和计算顺序 的相关文章

  • Haskell:GHC 无法推断类型。由类型签名错误绑定的刚性类型变量

    我看过几篇主题相似的帖子 但它们并不能真正帮助我解决我的问题 所以我才敢重复 现在我有一个带有签名的函数 run Expr query gt RethinkDBHandle gt query gt IO JSON 这是一个数据库查询运行函数
  • 在一元上下文中使用 Data.Map

    我正在操作的地图具有单子键 类型为IO Double 我需要使用findMax在这张地图上 我可以用吗liftM为了这 Map findMax Map fromList f x X f y Y f z Z Here f x有类型IO Dou
  • Haskell 中实例声明的参数顺序切换

    我想进行实例声明 但自由类型变量不是最后一个变量 例如 我有一个类声明 class Poppable m where tryPop m a gt Maybe a m a 现在我想让 Q PSQ 优先级队列 成为 Poppable 的实例 具
  • Haskell 中的多态函数作为参数

    我有一个带有两个构造函数的 ADT 一个包裹着一个Double和一个包裹着Integer 我想创建一个函数 它采用一元函数Numtypeclass 并返回一个函数 该函数将该一元函数应用于我的 ADT 的内容 我试过这个 data X Y
  • 不明白这个 haskell 代码中的内容

    我有一些 Haskell 代码 我正在尝试完成它 但我不明白其中发生了什么 type Bag a a gt Int emptyB Bag a emptyB e gt 0 countB Eq a gt Bag a gt a gt Int co
  • 系统地将函数应用于 haskell 记录的所有字段

    我有一条包含不同类型字段的记录 以及一个适用于所有这些类型的函数 举一个小 愚蠢 的例子 data Rec Rec flnum Float intnum Int deriving Show 比如说 我想定义一个为每个字段添加两条记录的函数
  • 如何从有向无环图导出FRP?

    我目前正在研究我的下一个项目 目前处于预规划阶段 因此这个问题只是为了了解现有技术的概述 Setup 我有一个具有多个输入和输出的有向无环图 DAG 现在考虑人工神经网络 处理这种结构的常见方法是在每个 时间 步骤上处理整个网络 我相信这是
  • 对参数进行排序以利用柯里化

    我最近两次重构代码以更改参数的顺序 因为代码太多 黑客喜欢flip or x gt foo bar x 42正在发生 在设计函数签名时 哪些原则可以帮助我充分利用柯里化 对于轻松支持柯里化和部分应用的语言 有一系列令人信服的论点 最初来自
  • Haskell 如何将整数文字转换为不同类型?

    我有以下匿名函数 Exercises gt g Sum n gt Sum n 1 我这样使用它 Exercises gt g Sum 56 Sum getSum 55 Exercises gt g 56 Sum getSum 55 第二个例
  • enumFromTo 如何工作?

    我无法将号码添加到Char 以下内容将无法编译 a 1 但是 a z 成功创建一个字符串 其中每个字符值都会递增 有没有一个特殊的函数可以增加Char 我知道我能做到chr ord c 1 如何 a z 或底层enumFromTo函数增加结
  • Haskell 中是否可以部分应用第 n 个参数?

    我很好奇是否可以写一个函数apply nth它接受一个函数 参数的数量以及该参数的值 然后返回一个新的 部分应用的函数 我的感觉是 由于类型系统的原因 这是不可能的 但我无法给出令人满意的答案 我也无法提出工作类型签名 如果语言的类型更加松
  • 我可以在程序内更改堆栈大小限制吗?

    我可以通过传递配置 GHC 编译的 Haskell 程序的最大堆栈大小 RTS Kn到它 在哪里n是某个数字 有没有办法在程序内更改此设置 我想对各种函数的堆栈消耗进行基准测试 因此尝试在各种限制下运行它 捕获StackOverflow例外
  • Python 的“with”是一元吗?

    像我之前的许多鲁莽的先驱者一样 我正在努力穿越理解单子这片无路可走的荒原 我仍然在蹒跚学步 但我不禁注意到 Python 的某种类似 monad 的品质with陈述 考虑这个片段 with open input filename r as
  • 记录语法和求和类型

    我有关于 Haskell 中的总和类型的问题 我想创建一个由两个或多个其他类型组成的总和类型 并且每个类型可能包含多个字段 一个简单的例子是这样的 data T3 T1 a Int b Float T2 x Char deriving Sh
  • Haskell 中的随机枢轴快速排序

    是否有可能在 Haskell 中实现快速排序 使用 RANDOM PIVOT 但仍然有一个简单的Ord a gt a gt a 签名 我开始了解 Monad 目前 我将 monad 解释为某种 命令模式 这对于 IO 非常有用 所以 我知道
  • 自动函子实例

    给定以下代数类型 ghci gt data Foo a Foo a 然后我实例化其中之一 ghci gt let f Foo foo 最后 我想打电话给fmap将函数应用为 a gt b gt Foo a gt Foo b ghci gt
  • 使用来自其他 Cython 代码的自定义 Cython 代码

    我目前正在尝试优化我的 Python 程序 并开始使用 Cython 以减少函数调用开销 也许稍后会包含优化的 C 库函数 所以我遇到了第一个问题 我在代码中使用组合来创建一个更大的类 到目前为止 我已经将我的一个 Python 类转换为
  • 带边界的 haskell 列表数据类型

    我有以下类型定义来表示卡片 data Suit Hearts Spades Diamonds Clubs data Rank Numeric Integer Jack Queen King Ace data Card Card Rank S
  • 如何在 Haskell 中编写 MST 算法(Prim 或 Kruskal)?

    我可以用 C 或 Java 编写 Prim 和 Kruskal 算法来查找最小生成树 但我想知道如何在 Haskell 中以 O mlogm 或 O mlogn 实现它们 纯函数式程序更好 多谢 正如斯文宁森所说 优先搜索队列 http h
  • 如何在 Haskell 中处理这个简单的 IO 异常

    全部处理 在下面的代码中 getDirectoryContents dir可能会失败 例如dir可能不存在 如何捕获这个并向用户抛出有意义的消息 我知道 IO 异常处理已经被问过很多次了 但我仍然找不到一个简单的方法来做到这一点 walk

随机推荐

  • iOS 8 旋转方法弃用 - 向后兼容性

    在iOS 8中 界面旋转的方法有已弃用 这包括 willRotateToInterfaceOrientation duration didRotateFromInterfaceOrientation willAnimateRotationT
  • 使用 LINQ 从 XML 文件中选择元素

    我有这样的 XML 结构
  • Python:存储大数据结构

    我目前正在用 python 做一个项目 该项目使用相对较大的字典 大约 800 MB 我尝试使用 pickle 来存储其中一本字典 但出现了 MemoryError 在 python 中保存此类文件的正确方法是什么 我应该使用数据库吗 Py
  • 如何从 Bash 中的字符串中删除最后 n 个字符?

    我有一个变量var在包含字符串的 Bash 脚本中 echo var some string rtf 我想删除该字符串的最后四个字符并将结果分配给一个新变量var2 以便 echo var2 some string 我怎样才能做到这一点 你
  • IBM DataPower 3.7.1.x 与 WCF 客户端相关的问题

    我尝试在 WCF 中使用 IBM DataPower 3 7 1 x Web 服务 但收到以下错误消息 找不到 System IdentityModel Tokens X509SecurityToken 令牌类型的令牌身份验证器 根据当前的
  • Nodejs 中的 setInterval 奇怪行为

    我想每秒运行一次函数 而函数本身需要 3 秒来执行 结果是每个间隔的执行时间相差
  • 同一 3D 轴中的多条独立线

    I would like to draw multiple independent lines in a 3D plot in Python It looks like 我是 Python 新手 你能帮我吗 您必须使用 matplotlib
  • PHP计算数组元素[重复]

    这个问题在这里已经有答案了 你好 有人可以解释一下为什么这会返回 一个数组由 0 个元素组成 arr array 1 3 5 count count arr if count 0 echo An array is empty else ec
  • 使 glReadPixel() 运行得更快

    我想要一种非常快速的方法来为我的应用程序捕获 openGL 帧缓冲区的内容 一般来说 glReadPixels 用于将framebuffer的内容读取到缓冲区中 但这很慢 我试图通过创建 4 个线程使用 glReadPixels 从 4 个
  • 为什么我的 ListView 和 CheckBox id 变得混乱?

    我正在女巫单元中实现带有复选框的 ListView 但问题是 当我签入一个单元格并向下滚动列表时 它会变得混乱 其他单元格也会被检查 我需要在 getView 方法中做其他事情吗 这是我的 CustonAdapter public clas
  • 选择矩阵中一行的一些值

    在带有矩阵的 R 中 m lt matrix 1 20 ncol 4 colnames m lt letters 1 4 如果我只想选择一个值 它就可以了 像这样 subset m m 4 17 但如果我想要 3 个值 就会出现错误 看这个
  • JSTL c:out 不显示变量的值

    我正在关注教程关于春天我应该在控制器中设置一个变量 以便在呈现请求的 jsp 中打印 代码如下 Controller public class HelloController RequestMapping value hello htm p
  • 使用超线程运行模拟使运行时间加倍

    我使用用 python numpy cython 编写的模拟 由于我需要对许多模拟运行进行平均 因此我使用多处理模块来批量运行所有单独的模拟运行 在办公室 我有一个带 HT 的 i7 920 工作站 我家里有一台 i5 560 没有 我认为
  • 无法从 Node Js 加载 Javascript 和 CSS 文件

    我读过很多关于 无法从节点js加载js和css文件 的问题 看起来像一个愚蠢的路径错误 但由于我是节点js新手 我无法弄清楚它 我的所有文件都位于一个名为 d3 的文件夹下 有什么想法哪里出了问题吗 var http require htt
  • 我有两个错误:“UIWebview”没有可见的@interface

    我有两个错误 UIWebView 没有可见的 interface 声明选择器 highlightAllOccurencesOfString 另一个 UIWebView 没有可见的 interface声明选择器 removeAllHighli
  • 如何:后退按钮支持“Ajax”

    我有一个asp net ajax网站 它充满了在同一页面上发生的事情 无需重新加载页面 例如排序记录 分页 当用户转到同一站点中的另一个页面并按浏览器后退按钮时 如何使浏览器保存页面状态以使用预选选项 例如排序选项 分页中的页码 返回到该页
  • 我可以从 Graphics 对象中获取矢量数据吗?

    在 Flash 10 播放器中 假设我加载了一个只有十几条随机线的 Sprite 有什么方法可以检查 Sprite 的图形对象并找出这些线的位置吗 目前 我有一个疯狂的工作 我将线条绘制到位图并检查位图 但这是粗糙且昂贵的 不 又是 没有特
  • 伪造 Entity Framework 4.1 的 DbContext 来测试我的存储库

    我有一个基本存储库 所有实体存储库都继承自该存储库 在我的睾丸中 我创建了一个 Fake DbContext 和 Fake DbSet 来测试我的存储库 但是在我的 FakeDbContext 中实现一些方法时 我无法实现IDbContex
  • Oracle针对不同条件的order by

    我有一个查询 我想根据条件进行两种订单 例如 如果一个字段为 NULL 我需要下一个订单 如果不是 我必须下一个订单 我怎样才能做到呢 select from table 1 t order by if t field1 is null t
  • 单子、组成和计算顺序

    所有 monad 文章经常指出 monad 允许您按顺序对效果进行排序 但是简单的构图又如何呢 不是 f x x 1 g x x 2 result f g x 需要g x之前要计算f 单子是否做同样的事情 但处理效果 免责声明 单子有很多东