23次重大发布

0.24.0 2023年9月28日
0.23.0 2021年4月26日
0.22.1 2021年4月25日
0.20.0 2021年3月5日
0.14.0 2020年6月15日

#207算法

每月 23次下载

MIT/Apache

200KB
3K SLoC

Poi

一个实用的无点定理证明助手

标准库

=== Poi 0.24 ===
Type `help` for more information.
> and[not]
and[not]
or
∵ and[not] => or
<=>  (not · and) · (not · fst, not · snd)
     ∵ (not · and) · (not · fst, not · snd) <=> or
<=>  not · nor
     ∵ not · nor <=> or
∴ or

Poi使用数学知识库来自动进行定理证明,同时也向您展示替代方案。

  • 表示 "因为"
  • 表示 "因此"
  • <=> 一个替代方案

要从您的终端运行Poi Reduce,请输入

cargo install --example poi poi

然后,要运行

poi

您可以使用 help 来了解Poi中的命令及其背后的理论。

下载并试用! (将其视为一种通过实践学习的工具)

另外,查看路径语义的常见问题解答

示例:连接列表的长度

Poi允许您指定一个目标并自动证明它。

例如,当计算两个连接列表的长度时,有一种更快的方法,就是计算每个列表的长度并将它们相加

> goal len(a)+len(b)
new goal: (len(a) + len(b))
> len(a++b)
len((a ++ b))
depth: 1 <=>  (len · concat)(a)(b)
     ∵ (f · g)(a)(b) <=> f(g(a)(b))
.
(len · concat)(a)(b)
(concat[len] · (len · fst, len · snd))(a)(b)
∵ len · concat => concat[len] · (len · fst, len · snd)
(add · (len · fst, len · snd))(a)(b)
∵ concat[len] => add
depth: 0 <=>  ((len · fst)(a)(b) + (len · snd)(a)(b))
     ∵ (f · (g0, g1))(a)(b) <=> f(g0(a)(b))(g1(a)(b))
((len · fst)(a)(b) + (len · snd)(a)(b))
(len(a) + (len · snd)(a)(b))
∵ (f · fst)(a)(_) => f(a)
(len(a) + len(b))
∵ (f · snd)(_)(a) => f(a)
∴ (len(a) + len(b))
Q.E.D.

concat[len] 的表示法是一个 "正常路径",它允许您将其转换为更有效的程序。正常路径是可组合的,并且是无点的,与它们的等式表示不同。

对于深度自动定理证明,Poi使用Levenshtein距离启发式。这简单地说就是文本表示中的最小单字符编辑距离。

尝试以下操作

> goal a + b + c + d
> d + c + b + a
> auto lev

命令 auto lev 告诉Poi在所有子证明中自动选择具有最小Levenshtein距离的等价表达式。

Poi和路径语义简介

在 "无点" 或 "沉默" 编程中,函数不标识它们操作的参数(或 "点")。参见 维基百科文章

Poi是路径语义的小子集的实现。为了解释Poi是如何工作的,需要简要介绍路径语义。

路径语义是一种用于数学编程的高度表达性语言,它除了常规计算外,还具有“路径空间”。如果常规编程是二维的,那么路径语义就是三维的。路径语义通常与范畴论逻辑等结合使用。

“路径”(或“常规路径”)是导航函数间的一种方式,例如

and[not] <=> or

用文字翻译这句话意味着

If you flip the input and output bits of an `and` function,
then you can predict the output directly from the input bits
using the function `or`.

在常规编程中,无法直接表达这种想法,但可以将逻辑关系表示为方程

not(and(a, b)) = or(not(a), not(b))

这被称为德摩根定律之一。

以交换图的形式表示时,可以直观地看到维度

         not x not
      o ---------> o           o -------> path-space
      |            |           |  x
  and |            | or        |     x
      V            V           |   x
      o ---------> o           V        x - Sets are points
           not            computation

计算和路径类似于复数,其中“实部”是计算,“虚部”是路径。

这以非对称路径表示法书写

and[not x not -> not] <=> or

在对称路径表示法中

and[not] <=> or

计算和路径空间都是方向性的,这意味着不一定总能找到逆元素。路径空间中的组合仅仅是函数组合

f[g][h] <=> f[h . g]

如果想象computation = 2D,那么computation + path-space = 3D

路径语义可以被视为方程的“无点风格”子集。这个方程子集在编程中特别有用。

Poi的设计

Poi被设计成用作Rust库。

这意味着任何人都可以在Poi之上创建自己的工具,而不需要很多依赖项。

Poi主要使用重写规则进行定理证明。这意味着其核心设计是“愚蠢”的,当给定错误的规则时,可能会执行无限循环等愚蠢的操作。

然而,这种设计也使Poi非常灵活,因为它可以在任何方向上进行模式匹配,独立于计算方向。在Rust代码中定义此类规则相对容易。

语法

Poi使用Piston-Meta来描述其语法。Piston-Meta是一种用于人类可读文本文档的元解析语言。它使得轻松更改Poi的语法成为可能,并保持向后兼容。

由于Piston-Meta可以描述自己的语法规则,这意味着Piston-Meta的未来版本可以解析旧版本Poi的语法。然后可以使用合成将旧文档转换为Poi的新版本。

核心设计

Poi的核心是Expr结构

/// Function expression.
#[derive(Clone, PartialEq, Debug)]
pub enum Expr {
    /// A symbol that is used together with symbolic knowledge.
    Sym(Symbol),
    /// Some function that returns a value, ignoring the argument.
    ///
    /// This can also be used to store values, since zero arguments is a value.
    Ret(Value),
    /// A binary operation on functions.
    EOp(Op, Box<Expr>, Box<Expr>),
    /// A tuple for more than one argument.
    Tup(Vec<Expr>),
    /// A list.
    List(Vec<Expr>),
}

Expr结构的简洁性很重要,并且高度依赖于高级路径语义知识。

一个符号包含每个领域特定符号和符号的泛化。

Ret变体来自高阶运算符重载中使用的表示法。它不是将值描述为值,而是将其视为某种未知输入类型的函数,该函数返回一个已知值。例如,如果一个函数对所有输入都返回2,则可以写成\2。这意味着函数上的无点变换有时可以计算一些内容,而不需要直接引用具体值。更多信息请参阅论文高阶运算符重载和存在路径方程

《EOp》变体泛化了函数上的二元运算,例如《Composition》(组合)、《Path》(路径,正常路径)、《Apply》(调用函数)和《Constrain》(部分函数)。

《Tup》变体表示表达式的元组,其中单元素元组(只有一个元素的元组)被“提升”到高一层次。例如,这可以用来从《and[not x not -> not]》转换到《and[not]》而不需要编写针对非对称情况的规则。

《List》变体表示表达式的列表,例如《[1, 2, 3]》。它与《Tup》的区别在于单元素不被“提升”。

知识表示

在函数式编程的高维中,“归一化”的定义取决于理论的特定用途。直观地,由于有更多的方向,什么算是向答案的进步在一定程度上是任意选择的。因此,这种选择的主体性必须在知识的表示中得到反映。

Poi的知识表示旨在多用途。与常规编程不同,您不想总是做评估之类的操作。相反,您为不同的目的设计不同的工具,使用相同的知识。

《Knowledge》结构体以规则的形式表示数学知识

/// Represents knowledge about symbols.
pub enum Knowledge {
    /// A symbol has some definition.
    Def(Symbol, Expr),
    /// A reduction from a more complex expression into another by normalization.
    Red(Expr, Expr),
    /// Two expressions that are equivalent but neither normalizes the other.
    Eqv(Expr, Expr),
    /// Two expressions that are equivalent but evaluates from left to right.
    EqvEval(Expr, Expr),
}

《Def》变体表示定义。当评估表达式时,定义会被内联。

《Red》变体表示特定领域理论中什么是“归一化”。它可以使用正常评估意义上的计算,或者使用路径空间。这个规则是方向性的,这意味着它模式匹配第一个表达式并绑定变量,这些变量通过第二个表达式合成的。

《Eqv》变体表示在路径上旅行时可以做出的选择。向一个方向前进可能和向另一个方向前进一样好。当不清楚应该向哪个方向前进时使用这个规则。这个规则是双向的,这意味着可以将其视为两个方向的归约。

《EqvEval》变体与《Eqv》类似,但在评估表达式时,它是从左到右归约的。例如,它在《sin(τ/8)》上使用。当进行定理证明时,通常希望《sin(τ/8)》的可读性。例如,在Poi Reduce中,`sin(τ/8)`的值被表示为选择(等价)。当评估表达式时,最好只是用计算值替换它。

Poi不是什么

有些人希望Poi能以更方便的方式用于解决使用依赖类型的问题。

虽然Poi使用了依赖类型的思想,但它不适合依赖类型的其他应用,例如通过将其应用于机器代码的即时表示来验证程序。

未来可能会使用正常路径进行此类应用,但这可能需要一个不同的架构。

这个实现是为代数问题设计的

  • 对象模型限制在动态类型
  • 归约与等价平衡

这意味着并非所有事物都可以被证明,因为这使得自动定理证明更加困难,而这对于解决代数问题时所需的深度是必要的。

依赖项

~250KB