是一个类型 在给定的上下文中无效_类型和示例制导的程序合成技术

本文提出了一种程序合成算法,结合类型信息和输入输出示例,用于合成处理代数数据类型的递归函数。算法引入了精简树数据结构,提高了搜索效率。实验表明,该算法在时间和合成规模上达到了当前技术水平。
摘要由CSDN通过智能技术生成
3e4edca943389303f252541fc35a29ba.png

摘要

本文提出了一种程序合成算法,用于解决处理代数数据类型的递归函数的合成问题。该算法建立在证明论的基础上,在合成过程中利用类型信息和输入输出示例对搜索空间进行修剪;同时,该算法还引入了一种名为精简树的数据结构。利用这一数据结构,算法可以为目标代码所需的约束条件构建出简明的表达形式。为了评估算法,作者对算法进行了原型实现,本文采用的数据集由 40 多个较小的基准程序、若干较大的复杂程序组成。实验结果表明:该算法的时间开销与合成结果规模已经达到甚至超过了当前的最新技术水平。

关键词

函数式编程;程序合成;类型理论;证明搜索

1 引言

本文提出了一种用于合成处理代数数据类型的、具有纯粹函数性质的递归程序的技术。作者团队的方法改进了“程序合成是一种证明搜索”(Proof Search)的古老观念:除使用类型信息外,算法还使用具体的输入输出示例对搜索空间进行缩减,并获得了十分显著的缩减效果。作者将这些额外的信息(输入输出示例)利用于构建一种名为精简树的数据结构,并通过这种数据结构有效地对非平凡(规模较大)的程序进行了合成。

f4f822ad51872ec000107f0292f17988.png

图 1:一个程序合成问题示例(上)和合成结果实现(下)

图 1 的小例子简单地展示了程序合成的过程。算法的原型采用 OCaml 语法实现,但注意该算法并不仅局限于这一种语法。算法的输入包括:类型签名、关于必要辅助函数的定义以及合成目标。图 1 所示的过程定义了 nat(自然数)和 list 两种类型,同时没有辅助函数。合成目标则是一个名为 stutter、映射类型为 list->list 的函数。该函数的一部分由紧随“|>”标记后的一系列输入输出示例指定。这些示例用于进一步完善目标类型,比如图 1 中的示例表明了 stutter 函数应该返回一个列表,并且这个列表应该由输入的列表中的每个元素重复一次组成。如第三个示例所示:stutter [1;0]对应的输出为[1;1;0;0]。

本文提出的合成算法可以在很短的时间(约 0.001s)内产生合成结果。stutter 函数的合成结果如图 1 的下半部分所示。可以看到:算法将合成一个完备的函数。该函数在输入列表中为每个 Cons 单元创建了两个副本,并通过递归传递 f1 l2 实现了函数的“结巴功能”(stuttering)。

这种具有通用性的程序合成技术拥有许多潜在的应用场景,例如:利用程序示例编写电子表格宏、在大型 API 库语境下完成代码补全、生成缓存一致性协议等。本文主要关注类型化编程语言中涉及结构化数据类型的递归程序,以及高阶程序的合成问题。这个领域上是对上述研究领域的补充。

将示例与类型相结合:作为一种基于证明搜索的技术,编程语言的类型结构是本文提出的算法避免产生不良类型项的关键参照。同时,在合成过程中,该算法只在解空间中对 β-normal 和 η-long 形式的程序进行搜索。

关于如何将示例输入输出与类型信息相结合,作者的意见是采用“修改键入规则”的方式。采用这种方式,算法可以将示例“推入”派生树的叶子节点,从而使派生树可以充当生成程序的框架。这样做可以使算法在搜索过程的早期就对候选项进行评估,从而才有可能大幅度缩减搜索空间。本文提出的算法没有遵循“先枚举再评估”的朴素策略,而是采用了更为细致的“在枚举时评估”的方式。

路线图:本文引入了一种类似机器学习模式的类型系统。该系统通过结合输入输出示例对合法程序派生集进行约束。类型系统可以看成程序合成问题的非确定性规范,其最终的目的是找到具有有效类型的项。为了将规则转化成算法,作者将类型系统划分为两个部分:1)一种名为精简树的数据结构,用于协助验证搜索空间中众多候选解的重叠部分;2)一种枚举搜索方法,用于实现解的搜索。作者团队将算法实现为一个原型工具:MYTH。该工具采用 OCaml 语法进行代码合成算法的设计,在合成效率和合成规模方面均比肩甚至超过现有的技术水平。

2 技术概览

假设现在有三种数据:一种具有代数数据特征的数据类型 match、一些顶级函数定义和几个显式递归函数,一个程序合成问题可以通过:(1)一些数据类型定义和顶级 let-绑定(let-binding),(2)一个目标类型以及(3)一些目标类型的示例来定义。一个程序合成任务就是生成与示例一致且符合目标类型的程序。

面向类型的程序合成过程主要包括两个部分:1)基于目标类型,使用给定的输入输出示例对约束进行完善;2)推测出与给定示例结果一致的目标类型值。

类型细化:以图 1 中 stutter 函数的合成过程为例。首先,从目标类型 list->list 我们得知:生成程序的顶层结构必须采用“let rec f1 (l1:list) : list = ?”的形式;之后,在合成 stutter 函数主体的过程中,三个输入输出示例告诉我们:在 l1(输入)为[ ]的“设定”(stutter 函数的假设评估)下,函数的期望输出为[ ];而在 l1 为[0]的设定下,期望输出为[0; 0];最后在 l1 为[1; 0]的设定下,期望输出为[1; 1; 0; 0]。在合成主体时,三个示例输入输出会分别精炼为一个新的示例设定。这个示例设定保证输出值是目标类型,并且与对应的输入值绑定。

推测:现在要使用 list 类型对函数主体进行填充。通过观察我们可知:没有一个单独的 list 构造函数可以同时满足这些示例(因为有一些示例同时以 Nil 和 Cons 开头)。解决这一点,可以尝试枚举或者推测一些包含变量的良好类型术语,例如 l1。但 l1 并不能同时满足所有示例,比如在 l1 为[0]的设定下修改 l1 为[0; 0]之后,其他的项可能是良好类型,也有可能是不良类型,但是都不满足结构性递归的条件。这个时候,类型推测就会失败。

注意:除了一些不良类型项和非结构性递归项之外,一些良好类型项也需要被排除。例如良好类型项 “match Nil with Nil -> e1 | Const -> e2”,它与不含 match 的较小项(在本例中为 e2)等效,作者称之为“与常量构造函数相匹配的项”。这种项通常也需要排除在外。由于引入了额外的 lambda 表达式和应用,这类不好的项被大量引入。为了避免产生这种无效的冗余项,推测阶段只能生成无法进一步约简的、β-normal 形式的项。

匹配细化:这部分引入 match 表达式。作者首先推测 match 的可能匹配项:这种项必须具有一些代数类型,并且必须由上下文中的变量构造而来。唯一满足这些条件的项就是 l1,则接下来考虑如何完成项:

aeb0710ec7d72e3e469a9deba095aeca.png

当匹配到模板时,作者根据 match 在每个设定下的配股结果分配示例。这种情况下:l1 在第一个设定下的评估结果为 Nil,在其余两个设定下的评估结果为 Cons(_, _)。由此,可以将第一个示例发送到 Nil 分支,将其他两个示例发送到 Cons 分支。如此在下一阶段中,可以通过细化的设定解决 Nil 和 Cons 两个子目标类型。Nil 分支可以立刻解决:通过符合示例设定“[]”的 l1 表达式可以完成关于 Nil 的匹配细化。

递归函数:解决 Cons 分支则需要进行新一轮的推测,同时解决方案中需要对 f1 进行递归调用。那么面对这种基于类型和示例的方法,应当如何生成递归函数呢?答案是:在引入 f1 这种递归函数时,可以将对应这种函数的输入输出示例解释为函数的一部分,从而对 f1 的功能进行合理近似。在这里,f1 的实例就是给出的三个示例设定定义的分部函数。在给定上下文中该类信息的情况下,推测过程可以很快的确定“Cons(n1, Cons(n1, f1 l2))”就是 Cons 分支的一种匹配情况。

3 实现

8f347477a92e6c24db08d91ebea39999.png

3.1 系统的一致性、终止点以及确定性

一致性:由于作者允许在分部函数的域中应用函数值,合成过程中系统无法确定是否存在互相矛盾的示例集(即作者的系统无法确定函数是否等价)。因此在 MYTH 中,无法提前对输入的 X 进行一致性检测。

MYTH 采用了“按照程序大小粗粒度增长”的顺序,对程序空间进行搜索。这解决了上一段中提到的问题。给定一组一致性示例,MYTH 的解空间中一定存在一个可以满足所有示例的程序。这个程序将专门用于对示例(例如一组嵌套的模式匹配项)的精确处理。

终止点:为了达到终止点,系统必须在递归函数调用过程中不断添加结构性递归检查,同时对数据类型施加正向限制。结构性递归检查是 Coq 定理证明器(Coq Theorem Prover)语法检查部分的一种更简单、更粗粒度的形式。如图 2 所示,作者给出的函数都是仅接收一个参数的柯里函数(Curried Function),这要求传递给 fix 的参数都是结构性递减的。这种结构性递减的值只能通过对 fix 的原始参数进行模式匹配获取,这种形式较 Coq 来说更为严格。

1cfb10c4b334d1b0f18496332013ec52.png

作者的策略是对仅搜索所有与示例一致的、具有目标类型特征的可能解。这种搜索可能是无止境的,尤其是当用户没有提供足够的示例时,这种情况就更易发生。因此,作者使用由受项大小影响的迭代加深策略对这个过程进行控制。比如当用户尝试合成一个规模为 1 的有效程序,但关于解的搜索却失败了的时候,系统将增加规模大小以进行下一轮搜索。该过程将持续到搜索成功(找到满足要求的程序)或规模达到用户规定的上限为止。

3.2 精简树

63b642d647661658ac66ec34cef6eac4.png
07e624bb474b4e97339334e84fb3a9bb.png

图 3:stutter 函数的精简树

图 3 给出了一个精简树示例,该精简树对应图 1 中 stutter 方法的合成过程。如图 3 所示精简树的节点分为两种:

(1)目标节点:目标节点包含目标类型,并代表了程序合成过程中可以进行 E-guess 的位置。目标节点对生成的内容进行包装,例如图 3 最高处包含 list->list 的节点,就是一个目标节点。

(2)精简节点:精简节点代表有效地 I-refinement,这些节点将取消对生成内容的包装。例如图 3 中包含 match l1 的节点,就是一个精简节点,代表 l1 上的一个 match 操作。

4 结果分析

递归函数合成领域已经出现了众多优秀的研究成果,其中最著名的是 Escher 和 Leon。在本研究中,作者团队使用的数据集与这两项工作十分类似,具有相似的程序复杂性(数据集中的程序平均包含 15~30 个 AST 节点)。在这种情况下,MYTH 通常具有与这两项工具相近或是更优的执行效率。MYTH 对大部分程序的合成时间可忽略不计,仅在合成某些规模较大的程序时耗时稍长,但都在可以接受的范围内。

致谢

国家重点研发计划课题:基于协同编程现场的智能实时质量提升方法与技术(2018YFB1003901)和国家自然科学基金项目:基于可理解信息融合的人机协同移动应用测试研究(61802171)

本文由南京大学软件学院 2020 级硕士钱瑞祥转述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值