21 个不稳定版本 (5 个破坏性更改)
新版本 0.6.1 | 2024 年 8 月 17 日 |
---|---|
0.6.0 | 2024 年 7 月 9 日 |
0.5.0 | 2024 年 5 月 15 日 |
0.4.0 | 2023 年 12 月 3 日 |
0.1.0 | 2022 年 9 月 7 日 |
#54 in 数学
每月 222 次下载
145KB
2.5K SLoC
Hooo
带有指数的命题逻辑
要安装 Hooo,请输入
cargo install --example hooo hooo
然后,要检查文件,请输入
hooo <file.hooo>
示例:荒谬
fn absurd : false -> a {
x : false;
let r = match x : a;
return r;
}
处理项目
要检查整个项目,请输入
hooo <project directory>
Hooo 将生成一个 "Hooo.config" 文件,您可以根据需要修改它以使用其他库
dependencies {
path("../my_library");
}
默认情况下,Hooo 添加一个 "std" 库,您可以使用以下方式使用它
use std::not_double;
这将添加一个术语 not_double : a -> !!a
.
Hooo 生成一个文件 "hooo-meta_cache.bin",它存储数据以使重复检查更快。这通常只需要几个 MB 的存储空间。如果您想进行干净的检查,请删除此文件。
Hooo 介绍
直觉主义命题逻辑 (IPL) 是世界上最重要的数学语言,因为它是构造逻辑和许多类型系统的基石。
通常,IPL 被视为一种“简单语言”,在多种方式上进行了泛化。例如,通过添加谓词,可以得到一阶逻辑。
以前,IPL 被视为“完整”的,即它可以推导出关于命题的每个真公式。为了在元级上推理 IPL,数学家依赖于某些元语言(例如,演绎演算)或某些模态逻辑。
例如,在可证明逻辑中,通过在演绎演算中证明 ⊢ a => b
来引入 □(a => b)
。
最近,我发现,虽然IPL中的逻辑蕴涵(=>
)对应于lambda/closures,但没有可能表达函数指针(->
)的类似物。人们以前认为,由于逻辑蕴涵是一种指数对象,IPL在范畴论的意义上涵盖了指数。然而,存在不止一种指数!将IPL扩展到包括函数指针被称为“指数命题”。
指数命题允许统一IPL的元语言和对象语言。这意味着IPL在其以前的形式上是“不完整”的,因为无法表达指数命题。
Hooo通过引入指数命题(HOOO EP)来最终确定直觉主义逻辑。这解决了之前使用单独元语言的问题。Hooo中的推理规则是一等公民。
Hooo中没有语言和元语言之间的分离。所有类型,包括规则,都是构造性命题。
与可证明逻辑的关系
HOOO EP听起来可能像可证明逻辑,但它不是同一回事。可证明逻辑与HOOO EP不兼容,因为Löb的公理是荒谬的。证明见“source/std/modal.hooo”中的lob_absurd
。
您可以通过使用显式符号表示模态运算符来在HOOO EP中模拟可证明逻辑。
HOOO EP
"HOOO EP"代表“高阶运算符重载指数命题”。
HOOO EP最初在Prop库中开发,该库利用了Rust的类型系统中的函数指针。
HOOO EP的基本思想是函数指针具有证明的类型。通过计算这些类型,可以得到新的证明。现在,为了做到这一点,你需要一些特殊的函数指针,其类型证明了可以证明哪些类型的函数指针。这些特殊函数指针是公理。换句话说:类型理论魔法!
HOOO EP中有3个公理
pow_lift : a^b -> (a^b)^c
tauto_hooo_imply : (a => b)^c -> (a^c => b^c)^true
tauto_hooo_or : (a | b)^c -> (a^c | b^c)^true
HOOO EP的哲学是公理是直观的。这就是人们知道可以信任公理的原因。
从这些公理中,可以得到无限复杂的逻辑后果。保持公理少而简单是很重要的,以免以后造成麻烦。
然而,一些很多人认为“显然正确”的陈述可能很难证明。这就是为什么需要一个库来展示如何证明这样的陈述。
例如,在Hooo中,可以证明函数组合是可能的
fn pow_transitivity : b^a & c^b -> c^a {
use hooo_imply;
use pow_and_lift;
x : b^a;
y : c^b;
fn f : a -> ((b^a & c^b) => c) {
x : a;
lam g : (b^a & c^b) => c {
y : b^a;
z : c^b;
let x2 = y(x) : b;
let x3 = z(x2) : c;
return x3;
}
return g;
}
let x2 = hooo_imply(f) : (b^a & c^b)^a => c^a;
let x3 = pow_and_lift(x, y) : (b^a & c^b)^a;
let r = x2(x3) : c^a;
return r;
}
请注意,与证明lambda/closure组合相比,这个证明是多么复杂
fn imply_transitivity : (a => b) & (b => c) -> (a => c) {
x : a => b;
y : b => c;
lam f : (a => c) {
arg : a;
let x2 = x(arg) : b;
let x3 = y(x2) : c;
return x3;
}
return f;
}
直观上,函数组合是可能的,所以大多数人只是把它当作理所当然的事情。以前,数学家需要元语言来证明这样的属性。现在,人们可以直接使用类似Hooo的语言来证明这些属性。
Hooo是为构造性/直觉主义逻辑中的元定理证明而设计的。这意味着Hooo可以对其自身规则进行推理。通过利用构造逻辑中元定理证明的全部力量,可以从现有规则生成新规则。
指数命题是一个函数指针,或者是一个推理规则。您可以将函数指针的类型写成a -> b或
b^a
。
语法
true True (unit type)
false False (empty type)
a -> b Exponential/function pointer/inference rule
b^a Exponential/function pointer/inference rule
a => b Imply (lambda/closure)
a & b And (struct type)
a | b Or (enum type)
!a Not (sugar for `a => false`)
a == b Equal (sugar for `(a => b) & (b => a)`)
a =^= b Pow equal (sugar for `b^a & a^b`)
excm(a) Excluded middle (sugar for `a | !a`)
sd(a, b) Symbolic distinction (see section [Symbolic distinction])
~a Path semantical qubit (see section [Path Semantics])
a ~~ b Path semantical quality (see section [Path Semantics])
(a, b) Ordered pair (see section [Avatar Logic])
(a : b) Type judgement (see section [Type judgement])
(f . g) Function composition
all(a) Lifts `a` to matching all types
□a Necessary `a` (modal logic)
◇a Possibly `a` (modal logic)
foo' Symbol `foo` (see section [Symbols])
foo'(a) Apply symbol `foo` to `a`
sym(f, f') Symbolic block (see section [Symbolic blocks])
sym a Locally declared symbol `a` (see section [Symbolic blocks])
x : a Premise/argument
let y Theorem/variable
return x Make a conclusion (safe)
unsafe return x Override safety (unsafe)
sym foo; Declare a symbol `foo'`.
axiom foo : a Introduce axiom `foo` of type `a`
() : a Prove `a`, e.g. `() : true`
f(x) Apply one argument `x` to `f`
f(x, y, ...) Apply multiple arguments
match x : a If `x : false`
match x (l, r) : c If `x : (a | b)`, `l : a => c` and `r : b => c`
use <tactic> Imports tactic
fn <name> : a -> b { ... } Function
lam <name> : a => b { ... } Lambda/closure
^
运算符的优先级高,而->
运算符的优先级低。
例如,b^a => b
被解析为 (b^a) => b
。 b -> a => b
被解析为 b -> (a => b)
。
术语和类型设计
在Hooo中,设计基于一个原则:使术语简单,而使类型复杂。这是因为术语主要用作策略,而类型是想要证明的陈述。
这一原则的哲学是,用户希望专注于要证明什么,而不是如何证明。因此,如何证明应该易于记住,同时有一个清晰的进度概述。
类型总是列在右侧,以便于阅读证明
let <name> = <term> : <type>;
Hooo的设计针对三个用例进行了优化
- 类型检查性能
- 证明可读性
- 源代码维护
这导致了一些设计决策,从正常编程的角度来看可能有些奇怪。
用户被迫根据Hooo的语法显式地执行每个步骤
let y = foo(x) : a; // Allowed
let y = foo(bar(z)) : a; // Disallowed
这是因为,否则,用户可能会倾向于将证明压缩成难以阅读的形式。在Hooo中编写证明是对未来的投资。抱怨证明长度的人通常在评估证明系统时采取了错误的方法。
难以阅读的证明在长期内的成本超过了可压缩代码的短期好处。然而,Hooo并不阻止人们努力缩短证明。由于Hooo支持元定理证明,它鼓励用户重用通用定理。这是生成形式化证明的正确方式。
类型判断
大多数现代定理证明助手都是基于依赖类型。依赖类型语言中的一种常见设计是在底层语言之上构建高级语言。这确保了更复杂的高级语言的正确性,只要转换为底层语言的变换是有效的。
技术上,Hooo不是依赖类型。Hooo实际上是简单类型,因为在HOOO EP中,每个类型都是一个命题。然而,由于HOOO EP引入了指数命题,Hooo的强大功能与依赖类型系统相当。
可以认为这是没有标准库的Hooo类似于构建高级语言的底层语言。
例如,而不是
z' : nat'
Hooo需要一个额外的术语来表示类型判断
x : (z' : nat')
直觉是Hooo通过其类型从低到高启动。最低级别的术语非常简单且受限。一旦你为你的需求开发了一个库,Hooo就接近了高级语言。
这种设计的好处是,Hooo可以不断地从低到高启动。可以从现有规则生成新规则,同时保持一组简单的基本术语。
安全性启发式方法
安全性启发式方法用于覆盖两种情况
- 当证明某事可能导致不一致性时
- 当用户可能有意证明其他事情时
当尝试证明 all(..)
或 sym(..)(..)
时,安全启发式规则会被触发。这在正常的定理证明中是不寻常的,并且是被劝阻的。例如,如果用户正在设计这种形式的公理,那么最好格外小心,因为它很容易导致无意中的荒谬。
当求解器的安全启发式规则过于强烈时,会使用 unsafe return
语法。这是用户传达这部分需要关注的一种方式。
不安全返回的一个应用场景是需要对不合理的推理进行推理。由于 Hooo 用来检查其他数学语言,有时不仅需要能够对做得正确的案例进行推理,还需要对出错的情况进行推理。当正确使用时,Hooo 可以通过在精心设计的公理后面保护不合理推理来安全地完成。
符号
Hooo 的当前版本使用简单的符号。这意味着,而不是声明数据结构,可以简单地使用例如 foo'(a, b)
。符号在使用前必须声明一次。
sym foo;
在其他地方,必须使用符号
use foo;
符号是全局的,所以 foo'
在任何地方都是 foo'
。
符号块
由于 Hooo 通过提升 all(..)
表达式中的所有变量来使用隐式量化,它需要一个方法来“冻结”变量以表达某些陈述。
例如,all(a & b -> a)
将 a & b -> a
提升到所有类型 a
和 b
。这对于可证明的陈述来说是可以的,但如果存在一个假设 a -> b
,则这是不安全的。一个假设 a -> b
是荒谬的。
fn sym_all_pow_absurd : all(a -> b) -> false {
x : all(a -> b);
let x2 = x() : a -> b;
let x3 = x() : (a -> b) -> false;
let r = x3(x2) : false;
return r;
}
Hooo 自动提升 all(..)
陈述。这意味着 all(..)
不需要在大多数地方使用。
然而,有时我们需要使用假设公理进行推理。问题是,由于 all(..)
提升所有变量,没有方法可以防止某些部分被提升。这就是为什么我们需要符号块。
fn sym_absurd : a & sym(a, all(a' -> b))(a) -> false {
x : a;
y : sym(a, all(a' -> b))(a);
let y2 = y() : a -> false;
let r = y2(x) : false;
return r;
}
在 sym_absurd
的例子中,需要 a
和一个符号块 sym(a,
all(a & b -> a)
) 来证明 false
。单独使用的符号块不足以提供足够的强度。
Hooo 理解如何自动地在 sym-blocks 之间进行转换。例如,a
与 sym(b, b')(a)
是相同的。
在某些情况下,您需要证明 sym-blocks 的属性。为了使这更容易,有一个公理 sym_transport
sym_transport : sym(a, b) & (b == c)^(sym a) -> sym(a, c)
这允许您传输 sym-block 的主体表达式。类型 sym a
没有项,但您可以仅局部声明一个符号
fn sym_eq_refl : sym a -> a' == a' {
sym a;
use eq_refl;
use triv;
let r = triv(eq_refl) : a' == a';
return r;
}
同构
当操作符 f
是正常同构时
a == b -> f(a) == f(b)
当操作符 f
是重言式同构时
(a == b)^true -> f(a) == f(b)
大多数操作符是正常同构。有些理论将理论中的所有操作符视为正常同构,例如一阶逻辑中的谓词。
然而,在 路径语义 中,有一些操作符是重言式同构。
例如,路径语义质量和符号区分是重言式同构。
在传统数学中,为了方便起见引入了定义等价性,这可能被解释为主观等价性,其强度大于重言式等价性。
使用定义等价性的问题
- 重言式和正常等价性之间没有区别
- 没有已知例子表明重言式等价性是不够的
- 在可能的情况下,优先选择正常等价性
由于 Hooo 不能假设所有操作符的同构,因此必须为每个操作符进行公理化。有关更多信息,请参阅 "source/std/cong.hooo"。
优点是 Hooo 更具有逻辑精确性。缺点是必须为 sym-blocks 证明正常同构,例如
fn cong_sym_id : true -> cong'(sym(a, a')) {
use cong_from;
use refl;
x : true;
let x2 = refl() : all(a == b -> a == b);
let x3 = x2() : all(a == b -> sym(a, a')(a) == sym(a, a')(b));
let x4 = x3() : sym(f, all(a == b -> f'(a) == f'(b)))(sym(a, a'));
let r = cong_from(x4) : cong'(sym(a, a'));
return r;
}
注意这个证明如何使用 Hooo 对 sym-blocks 的内部知识。上述证明被认为是一种非常深刻的洞察,因为它表明正常同构是数学的一种自然属性。
符号区分
论文:符号区分。
在正常逻辑中,没有方法在不添加显式假设的情况下区分命题。换句话说,逻辑不允许内部了解数据的准确表示。这是因为逻辑用于进行假设推理。
例如,您知道符号 x'
与 x'
是相同的。然而,逻辑无法内部了解这一点,因为这会导致不一致。如果有一个两个不可区分的符号且它们不相等,那么这在逻辑上是不一致的。
您必须始终允许将命题视为相等,尽管它们在符号上是严格区分的
a == b
(正常等价性)(a == b)^true
(重言式等价性)
逻辑中不应有比重言式等价性更强的等价性概念。大多数操作符通过正常等价性同构,但一些操作符只能通过重言式等价性同构。
然而,使用符号区分在逻辑中没有问题。
sd(a, b)
(a
和b
是符号区分的)
例如,在 Hooo 中,可以区分两个符号 foo'
和 bar'
letx= () : sd(foo',bar');
这在处理需要某些形式唯一性的理论时非常有用。
例如,在类型理论中,类型的成员只能拥有唯一的类型。当我们用类型理论进行逻辑推理时,可以假设两种类型相等,但理论会决定这种假设是否允许。理论无法阻止我们做出假设,但它可以控制后果。如果没有符号区分,逻辑上无法判断两种类型是否不允许相等。
符号区分允许你为这种情况添加公理,并且仍然能够进行假设推理。
路径语义
路径语义是数学编程的表达性语言。
与常规编程相比,数学编程通常处理更高维度的数据。在常规编程中,你只有一个评估维度。在多维编程中,你可以有多个评估维度。这并不像并行性那样简单,因为你可以将函数评估为“函数表面”的边界。这些称为“正常路径”的表面必须被视为独立的数学对象,这需要多维编程的基础。
例如
len(concat(a, b)) <=> add(len(a), len(b))
将其写为正常路径
concat[len] <=> add
在范畴论中,concat[len]
是一个“开放盒子”,通过add
来封闭。
现在,由于add[even] <=> eqb
,可以证明
concat[len][even] <=> add[even]
concat[len][even] <=> eqb
concat[even . len] <=> eqb
这种想法是正常路径在“正交维度”上与正常计算组合。使用这种记号是因为它不需要变量,因此是一种“无变量”定理证明的风格。
多维编程的基础非常困难,所以如果你不理解整个理论,不要感到难过。有很多东西需要消化,所以请慢慢来。
路径语义只是多维编程的一种方法。另一种方法是立方体类型理论。
类型层次的角度转变
由于高维通常在组合复杂性中爆炸,我们无法探索所有可能性的整个空间。因此,我们必须满足于证明多个维度上的某些属性。
自然地,我们习惯于将类型视为组织数据的方式。然而,如果你有一个像HOOO EP这样的强大逻辑,那么将类型层次视为“时间的瞬间”也是有意义的。每个瞬间都是IPL中进行推理的局部空间。
路径语义在根本上是允许命题从一个时间瞬间过渡到另一个时间瞬间的机制。
这体现在路径语义的核心公理中
ps_core : (a ~~ b) & (a : c) & (b : d) -> (c ~~ d)
运算符~~
是路径语义质量,这是一种通过符号区分提升双条件的不完全等价
q_lift : sd(a, b) & (a == b) -> a ~~ b
这意味着,当一个人在某些时间瞬间假设两个可以证明为符号区分的命题相等时,他可以引入质量,从而在未来时刻传播新的质量命题。
这种机制的原因是,在任何时间瞬间,信息都是相对于其内部状态的。因此,至少需要两个独立的物理状态来存储过去的信息。路径语义质量是这个现象的逻辑模型。
如果您觉得这很难理解,那么您可以直接使用拇指法则:“如果两个符号满足等价关系 a ~~ b
,那么它们的含义 (a : c) & (b : d)
等价于 c ~~ d
”。这是处理逻辑中符号语义的正确方法。请注意,您不应使用“等于”,因为自反性和逻辑蕴涵使得这种说法不正确。
路径语义质量 ~~
是在逻辑中表达有意等价关系的一种方式。例如,统一或存在统一的可能性。这个运算符有时被认为是一条路径,所以使用路径的符号的意义是为什么这个领域被称为“路径语义”。
在哲学中,路径语义质量与黑格尔的存在哲学密切相关。黑格尔的哲学被罗素所拒绝,罗素创立了分析哲学。这证明是一个错误,可能是因为罗素受到了一阶逻辑语言偏好的影响,其中所有谓词都是正常的全同。通过使用重言式全同,人们可以很好地推理黑格尔的哲学。然而,这需要 HOOO EP。
自质量 a ~~ a
等价于 ~a
,这被称为“量子比特”。这个名字“量子比特”来自经典逻辑中的模型,其中使用 a
作为随机生成器的种子来生成 ~a
的随机真值表。这使得 ~a
似乎处于所有命题的超位置,因此在经典电路的近似中类似于量子比特。人们也可以将其视为引入一个新的命题。
路径语义质量与量子比特是重言式全同的。
唯一性、解和想象
路径语义质量 ~~
,或量子比特 ~
,常被用作其他理论的“标志”,以表达唯一性或解。
!~a
是一个表示a
是唯一的表达式~a
是一个表示a
有某些解的表达式
默认情况下,您无法证明 ~a
或 !~a
,这意味着您有一个选择。您可以设计一个倾向于 ~a
的理论,或者您可以走相反的方向,倾向于 !~a
。一个理论设计的方向被称为“语言偏好”。请注意,它们都是底层思想“存在解”的表达。它们的区别在于数量,其中 !~a
表示“一个”,而 ~a
表示“多个”。
倾向于 !~a
的理论被称为“赛希特主义”,源自 赛希特主义。另一方面,倾向于 ~a
的理论被称为“柏拉图主义”,源自柏拉图主义。赛希特主义是柏拉图主义的对立面。它们在存在被语言偏向的方式上有所不同,要么偏向“一性”(具体),要么偏向“多性”(抽象)。
直观上,抽象对象可以被复制,因此它内部具有一种“多性”。具体对象不能被复制,因为这会破坏它的独特性。
当数学在没有解的情况下工作时,我们称之为“虚数”,它具有某种“零性”,例如复数中的虚单位 i
。在逻辑中,这仅仅是无需 !~a
或 ~a
的定理。
阿凡达逻辑
阿凡达逻辑是第一阶逻辑的替代品,更适合用于高维数学。原因是它消除了谓词,并用二元关系、阿凡达和角色来代替。这种设计更好地推广到多个评估维度。
您可以使用类似于 Prolog 的语言 Avalog 来实验阿凡达逻辑。
阿凡达逻辑中有两个基本公理
ava_univ : !~b & (a, b) & (b : c) -> c(a) == b
ava_ava : (a, b(c)) & (b(c) : d) -> d(a) => b(c)
第一个公理 ava_univ
表示,如果 b
是唯一的(表示为 !~b
),并且 b
扮演着 c
角色(表示为 b : c
),那么仅用 (a, b)
(有序对)来决定 c(a) == b
就足够了。
在某种意义上,ava_univ
表示了“计算” b
的含义。另一种思考方式是将它视为 a
的属性 c
。当 b
是唯一的时,它被迫对所有其他对象表现出这种特性。每个其他对象都必须以相同的方式使用 b
。这就是为什么 b
被视为一个独特的通用二元关系。
然而,在许多情况下,独特的通用二元关系过于限制性。这就是为什么我们需要一个“阿凡达”。阿凡达是一个符号,它封装了另一个表达式,例如 ava'(a)
,其中 ava'
是阿凡达,而 a
是被 ava'
封装的表达式。
第二个公理 ava_ava
表示,当 b(c) : d
时,仅用 (a, b(c))
就足够了,以确定 d(a) => b(c)
。请注意,这是一个较弱的结论。
例如,如果 (carl', parent'(alice'))
和 parent'(alice') : mom'
,那么 mom'(carl') == parent'(alice')
。在这种情况下,Carl 可以有不止一个母亲。
如果 !~parent'(alice')
,那么 Carl 只有 Alice 一个母亲,因为 mom'(carl') == parent'(alice')
。
Avatar 逻辑非常适合处理自然界中常见的复杂关系,例如家庭关系。在某种程度上,Avatar 逻辑处理“规则例外”非常出色。
还有一个公理处理头像之间的冲突
ava_collide :
(b, q1(a1)) & (b, q2(a2)) &
(q1(a1) : p) & (q2(a2) : p) -> !sd(q1, q2)
例如,如果你尝试在相同位置使用 foo'
和 bar'
作为头像,可以证明它们将会发生冲突
sym foo;
sym bar;
sym p;
fn test :
(b, foo'(a1)) & (b, bar'(a2)) &
(foo'(a1) : p') & (bar'(a2) : p')
-> false {
arg : (b, foo'(a1)) & (b, bar'(a2)) &
(foo'(a1) : p') & (bar'(a2) : p');
use std::ava_collide;
let x = ava_collide(arg) : !sd(foo', bar');
let y = () : sd(foo', bar');
let r = x(y) : false;
return r;
}
如果你想将一个角色 p
作为其所有成员的唯一角色处理,那么你可以使用以下定理
ava_lower_univ : !~p & (b, a) & (a : p) -> p(b) == a
同伦型理论
同伦型理论(HoTT)是数学的一个分支,它使用类型理论来形式化同伦理论。同伦理论是拓扑学的子领域,它将两个拓扑空间视为等价,如果这两个空间可以被收缩为彼此。结果发现,同伦型理论也作为数学的基础很有用,因为证明是数学对象,它们对应于拓扑空间中的路径,这些路径在等伦性上是等价的。
关于在 Hooo 中对 HoTT 的形式化,请参阅 “source/hott” 在 Hooo 存储库中。
依赖关系
~3MB
~65K SLoC