但是现在我们无法使用 let(或|>)轻松组合它们,因为 fetch 的输出类型与 build 的输入不匹配 。
但是,我们可以定义一个类似的操作,let(或>>=)来处理承诺 。它立即返回对最终结果的承诺,并在第一个承诺实现后调用 let* 的主体 。那么我们有:
let fab c =let* src = https://www.isolves.com/it/cxkf/bk/2021-02-19/fetch c inbuild src
换句话说,通过在周围撒上几个星号字符,我们可以将简单的旧管道变成一个新的并发管道!使用 let* 编写 promise returning 函数的时间规则与使用 let 编写常规函数的时间规则完全相同,因此使用 promise 编写程序与编写常规程序一样简单 。
仅仅使用 let *不会在我们的管道中添加任何并发(它只允许它与其他代码并发执行) 。但是我们可以为此定义额外的函数,比如 all 一次计算一个列表中的每个承诺,或者使用 and 运算符指示两个事物应该并行运行:
除了处理承诺外,我们还可以为可能返回错误的函数(只有在第一个值成功时才调用 let 的主体)或为实时更新(每次输入更改时都调用主体)或为所有这些东西一起定义 let* 。这是单子的基本概念 。
这其实很管用 。在 2016 年,我用这种方法做了 DataKitCI,它最初用于 Docker-for-mac 上的 CI 系统 。之后,Madhavapeddy 用它创建了 opam-repo-ci,这是 opam-repository 上的 CI 系统,OCaml 上主要的软件仓库 。这将检查每个新的 PR 以查看它添加或修改了哪些包,针对多个 OCaml 编译器版本和 linux 发行版(Debian、Ubuntu、Alpine、centos、Fedora 和 OpenSUSE)测试每个包,然后根据更改的包查找所有包的所有版本,并测试这些版本 。
使用 monad 的主要问题是我们不能对管道进行静态分析 。考虑上面的 example2 函数 。在查询 GitHub 以获得测试提交之前,我们无法运行该函数,因此不知道它将做什么 。一旦我们有了 commit,我们就可以调用 example2commit,但是在 fetch 和 docker_pull 操作完成之前,我们无法计算 let* 的主体来找出管道接下来将做什么 。
换言之,我们只能绘制图表,显示已经执行或正在执行的管道位,并且必须使用和 * 手动指示并发的机会 。
Arrow 方法Arrow 使管道的静态分析成为可能 。而不是我们的一元函数:
val fetch : commit -> source promiseval build : source -> image promise
我们可以定义箭头类型:
type ('a, 'b) arrowval fetch : (commit, source) arrowval build : (source, image) arrow
a('a,'b)arrow 是一个接受 a 类型输入并生成 b 类型结果的管道 。如果我们定义类型('a,'b)arrow='a->'b promise,则这与一元版本相同 。但是,我们可以将箭头类型抽象化,并对其进行扩展,以存储我们需要的任何静态信息 。例如,我们可以标记箭头:
type ('a, 'b) arrow = {f : 'a -> 'b promise;label : string;}
这里,箭头是一个记录 。f 是旧的一元函数,label 是“静态分析” 。
用户看不到 arrow 类型的内部,必须使用 arrow 实现提供的函数来构建管道 。有三个基本功能可用:
val arr : ('a -> 'b) -> ('a, 'b) arrowval ( >>> ) : ('a, 'b) arrow -> ('b, 'c) arrow -> ('a, 'c) arrowval first : ('a, 'b) arrow -> (('a * 'c), ('b * 'c)) arrow
arr 接受纯函数并给出等价的箭头 。对于我们的承诺示例,这意味着箭头返回已经实现的承诺 。>>>把两个箭头连在一起 。首先从“a”到“b”取一个箭头,改为成对使用 。该对的第一个元素将由给定的箭头处理,第二个组件将原封不动地返回 。
我们可以让这些操作自动创建带有适当 f 和 label 字段的新箭头 。例如,在 a>>>b 中,结果 label 字段可以是字符串{a.label}>>{b.label} 。这意味着我们可以显示管道,而不必先运行它,如果需要的话,我们可以很容易地用更结构化的内容替换 label 。
我们的第一个例子是:
let example1 commit =let src = https://www.isolves.com/it/cxkf/bk/2021-02-19/fetch commit inlet image = build src intest image
to
let example1 =fetch >>> build >>> test
虽然我们不得不放弃变量名,但这似乎很令人愉快 。但事情开始变得复杂,有了更大的例子 。例如 2,我们需要定义几个标准组合:
(** Process the second component of a tuple, leaving the first unchanged. *)let second f =let swap (x, y) = (y, x) inarr swap >>> first f >>> arr swap(** [f *** g] processes the first component of a pair with [f] and the secondwith [g]. *)let ( *** ) f g =first f >>> second g(** [f &&& g] processes a single value with [f] and [g] in parallel andreturns a pair with the results. *)let ( &&& ) f g =arr (fun x -> (x, x)) >>> (f *** g)
推荐阅读
- 辨别铁观音好坏的方法,碧螺春茶叶辨别好坏的方式
- 白茶长期存放,白茶存放方法
- 白茶的功效与作用有哪些 白茶的保存方法
- 天目湖白茶的功效与作用 天目湖白茶的保存方法
- mac操作界面?mac桌面设置方法
- 冬季最有效的减肥方法是什么呢
- 男士冬季减肥方法有哪些呢
- 冬季运动减肥最好的方法是什么呢
- 瘦腿最快运动方法有哪些呢
- 六安瓜片最正宗的读法,六安瓜片鉴别方法