CI/CD流水线创建方法?( 二 )


仅仅使用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使管道的静态分析成为可能 。 而不是我们的一元函数:
valfetch:commit->sourcepromisevalbuild:source->imagepromise我们可以定义箭头类型:
type('a,'b)arrowvalfetch:(commit,source)arrowvalbuild:(source,image)arrowa('a , 'b)arrow是一个接受a类型输入并生成b类型结果的管道 。 如果我们定义类型('a , 'b)arrow='a->'bpromise , 则这与一元版本相同 。 但是 , 我们可以将箭头类型抽象化 , 并对其进行扩展 , 以存储我们需要的任何静态信息 。 例如 , 我们可以标记箭头:
type('a,'b)arrow={f:'a->'bpromise;label:string;}这里 , 箭头是一个记录 。 f是旧的一元函数 , label是“静态分析” 。
用户看不到arrow类型的内部 , 必须使用arrow实现提供的函数来构建管道 。 有三个基本功能可用:
valarr:('a->'b)->('a,'b)arrowval(>>>):('a,'b)arrow->('b,'c)arrow->('a,'c)arrowvalfirst:('a,'b)arrow->(('a*'c),('b*'c))arrowarr接受纯函数并给出等价的箭头 。 对于我们的承诺示例 , 这意味着箭头返回已经实现的承诺 。 >>>把两个箭头连在一起 。 首先从“a”到“b”取一个箭头 , 改为成对使用 。 该对的第一个元素将由给定的箭头处理 , 第二个组件将原封不动地返回 。
我们可以让这些操作自动创建带有适当f和label字段的新箭头 。 例如 , 在a>>>b中 , 结果label字段可以是字符串{a.label}>>{b.label} 。 这意味着我们可以显示管道 , 而不必先运行它 , 如果需要的话 , 我们可以很容易地用更结构化的内容替换label 。
我们的第一个例子是:
letexample1commit=let src=https://pcff.toutiao.jxnews.com.cn/p/20210218/fetchcommitinletimage=build srcintestimageto
letexample1=fetch>>>build>>>test虽然我们不得不放弃变量名 , 但这似乎很令人愉快 。 但事情开始变得复杂 , 有了更大的例子 。 例如2 , 我们需要定义几个标准组合:
(**Processthesecondcomponentofatuple,leavingthefirstunchanged.*)letsecondf=letswap(x,y)=(y,x)inarrswap>>>firstf>>>arrswap(**[f***g]processesthefirstcomponentofapairwith[f]andthesecondwith[g].*)let(***)fg=firstf>>>secondg(**[f&&&g]processesasinglevaluewith[f]and[g]inparallelandreturnsapairwiththeresults.*)let(&&&)fg=arr(funx->(x,x))>>>(f***g)Then,example2changesfrom:
letexample2commit=let src=https://pcff.toutiao.jxnews.com.cn/p/20210218/fetchcommitinletbase=docker_pull''ocaml/opam2''inletbuildocaml_version=letdockerfile=make_dockerfile~base~ocaml_versioninletimage=build~dockerfile src~label:ocaml_versionintestimageinbuild''4.07'';build''4.08''to:
letexample2=letbuildocaml_version=first(arr(funbase->make_dockerfile~base~ocaml_version))>>>build_with_dockerfile~label:ocaml_version>>>testinarr(func->((),c))>>>(docker_pull''ocaml/opam2''***fetch)>>>(build''4.07''&&&build''4.08'')>>>arr(fun((),())->())我们已经丢失了大多数变量名 , 而不得不使用元组 , 记住我们的值在哪里 。 这里有两个值并不是很糟糕 , 但是随着更多的值被添加并且我们开始嵌套元组 , 它变得非常困难 。 我们还失去了在build~dockerfile src中使用可选标记参数的能力 , 而是需要使用一个新操作 , 该操作接受dockerfile和源的元组 。