Simplicity的基石:值和类型
Blockstream Research Simplicity Language

Simplicity的基石:值和类型

Kiara Bickers
Kiara Bickers

如果您是一位经验丰富的 Haskell、OCaml、Rust 或 Scala 函数程序员,那么您很可能已经使用过代数数据类型(ADT)。但如果您是 ADT 领域的新手,正在寻找适合初学者的

Simplicity 入门,那么这篇文章(以及系列文章)就是为您准备的!本篇文章是系列文章的第一篇,主要介绍值和类型的基础概念,下一篇将介绍函数、表达式和组合器。

不过,在深入探讨之前,我们首先要定义什么是值、类型和表达式。为了更好地理解这一概念,让我们来看一个简单的数学表达式。

(2 > 1) = True

对于我们来说,布尔表达式 (2 > 1) 的布尔值为 True。布尔类型表示二进制选择,表达式的值要么为 True,要么为 False。这两个值构成了布尔类型中的全部可能值。有时,求布尔值的表达式被称为命题,这是逻辑学领域的一个术语。不过,我们不会在这里进一步探讨这个术语。 类型代表一组具有定义特征的可能值。类型使我们能够以结构化的方式对值进行推理。在大多数编程语言中,表达式代表某种类型的值,与此不同,在 Simplicity 中,表达式代表将源类型的值映射到目标类型的函数。在求值时,这些表达式通过转换输入值产生结果。而值就是特定类型的具体数据。

name := expression : source type → target type

将逻辑转化为代码需要开发人员考虑实现细节,如控制流、错误处理和效率。对其中任何一个方面处理不当,都可能导致代码库中出现错误。像 Haskell、Coq 和 Agda 这样的语言是专门为表达数学概念和计算而设计的,因此代码与底层数学更紧密地结合在一起,从而比一般的编程语言产生更少的错误。数学语言在两个关键领域表现出色:(1) 为数学计算编写代码;(2) 形式推理。这两方面在可编程货币中都非常有价值,也符合 Simplicity 设计的核心原则。

 Simplicity 是一种智能合约语言,其精神类似于比特币脚本或 EVM。与 Hacspec(或 TLA+,或 Alloy)一样,Simplicity 也是一种规范语言,这意味着它的语义有一个精确的数学模型,可以正式证明关于程序的语句。如果合同是用 Simplicity 编写的,那么就可以直接证明正确性属性。例如,你可以证明硬币在没有签名 X 的情况下不能移动,或者程序不能超过 Y 内存阈值。

Simplicity功能中的类型有三种基本形式:

  • 一个单位类型正好定义一个可能的值。类型 1(或备选 ONE)
  • 乘积类型使用 "和" 操作组成一对类型。A × B
  • 求和类型通过 "或 " 运算将两个类型组合在一起。A + B

Simplicity 的类型系统完全由单位类型、总和类型和乘积类型组成。Simplicity 中的所有类型都是由这三种低级类型操作组合而成的。

从这些基础类型中还可以衍生出许多类型,我们将在本篇文章中进一步探讨。

  • 位类型是两个单位类型的总和。2(或两个)
  • 选项类型是单位类型和第二个任意类型的总和。1 + A
  • 字类型是位类型的重复乘积。2ⁿ

但让我们从头开始。

一开始,什么都没有。从无到有,单位类型的概念应运而生!

单位类型(1,空)

单位类型恰好有一个值,令人困惑的是它也被称为 "单位"。真漂亮!但这是什么意思呢? 单位类型 被写成 1,或 ONE。

单位写 成( )。我们可以写成 () :1 表示 () 的类型是 1。

单元类型是一种特殊类型,即空元组,因为它不携带任何信息。它通常被称为 "空",因为它只包含一个值。因为你没有选择的余地,只有一个值,所以无法编码任何信息。熵为零。你可能会问,一个没有可区分元素的类型有什么用?

尽管单元类型表面上看似空洞,但它却能在简单性中组成更复杂的类型,让我们建立更大的概念。

求和类型(标记联盟)

如果给定两个类型,那么这两个类型的和包含的值要么是左侧类型的值,要么是右侧类型的值。我们把这些选择写成

left(a) :A + B 其中值 a : Type A

right(b) :A + B 其中值 b : Type B

通过将这两个值相加,我们创建了一个新的类型,它可以容纳 A B 类型的值。从根本上说,求和类型迫使你在左和右之间做出选择。如果想同时拥有这两种类型,可以使用乘积类型,我们将在本篇文章的后半部分介绍乘积类型。

单位类型表示缺乏信息,而总和类型则通过表示组成类型值的选择或组合来传递信息。总和类型包括一个标签标记,用于识别值的具体类型,从而区分不同的可能性。即使一对值中的两个值完全相同,如 A + A 类型的 left(a) 和 right(a),它们被不同的标签(左和右)包装这一事实也是非常重要的。

Haskell 中的 Either 类型是一种求和类型,允许我们在两种类型之间进行选择。Rust 中的 Result 类型也是一种求和类型,可确保代码同时处理成功和错误两种情况。Simplicity 中的 sum 类型也是同样的概念,它包含了将多个选项合并为一个选项的思想。

有了 "和 "的能力,自然就有了 "位 "的概念。

位类型(2,布尔类型)

位类型是两个单位类型之和。 比特,定义为 Bit := 1 + 1、2 或 TWO。

0 := left(()) :Bit

1 := right(()) :Bit

比特类型是表示二进制选择的基本类型。它可以有两个不同的值,0 或 1。在总和类型中,值对应于两个子类型中的任何一个,并附有一个标签,标明是哪一个。在位类型中,子类型都是单位,不提供任何附加信息。左标签和右标签携带实际的位,提供新的信息。 布尔类型(如 Python、Java 和 C++ 等语言中的布尔类型)只是一个具有不同名称的位类型,它也定义了两个值之间的选择:真和假。

布尔型,写法为 Boolean := 1 + 1、2 或 TWO。

false := left(()) :Boolean

true := right(()) :Boolean

因为它们在结构上相等,所以实际上是同一类型。如果我们不将两个单位类型相加,而是将一个单位类型与另一个任意类型相加,就会得到选项类型。

选项类型(可归零,可能)

选项类型是单位类型和其他任意类型的总和。

Option[A] := 1 + A 或 A? 选项类型也称为可空类型或也许类型,它封装了两种可能性:要么没有值(由 1 单位类型表示),要么有值(由其他类型 A 表示)。

在 Haskell 中,它被称为 Maybe;在 Rust 中,它被称为 Option。两者都是该类型的示例。该类型允许处理值可能存在也可能不存在的情况。它提供了一种显式处理和表达值不存在的方法。

产品类型(元组)

给定两个类型 A BA B 的产品类型包含将 A 中的一个值和 B 中的一个值包装成对的值。

产品,写成 (a, b) : 其中 (a,b) 是一对 A × B 类型的值。

产品类型中,任何 A 类型的值都可以与任何 B 类型的值配对,从而产生 |A| * |B| 可能的值组合。

这对操作可以形象地理解为 "AND "操作,因为它将两个值合并为一个实体。它被称为乘积类型,因为它的值数是 A 中的值数与 B 中的值数的乘积。我们将来自左侧和右侧类型的值封装在同一个乘积值中,这与我们必须在左侧或右侧类型中做出决定的求和类型不同。乘积类型功能强大,因为它提供了一种表达复合值的方法,使我们能够构建分层结构并组成更大的数据模型。

产品类型的另一个术语是元组。元组是一种常见的数据结构,大多数编程语言都支持它。它们允许我们定义和返回多个值,并方便地表示相关数据或键值对。

在 Simplicity 中,可以使用乘积类型定义点。比方说,我们有一个 Int 类型,想用 x 和 y 坐标来表示二维空间中的一个点:

点 := Int × Int

这定义了一种名为 Point 的类型,它表示一对整数值。

然后,我们可以通过提供 x 和 y 的值来创建一个点的实例:

p := (3, 5) :Point

要表示一条直线,我们可以定义一个由两个点组成的产品类型,这两个点分别代表直线的起点和终点。

Line := Point × Point

但我们假设使用的是整数,这就有点超前了。这个例子的问题在于,Simplicity 没有内置的整数类型,无法像其他语言(如 Common Lisp 或 Python)中的整数一样表示任意大的数字。在 Simplicity 中,我们必须使用固定大小的类型。

读者可能还记得,在《塞尔达传说》游戏中,林克的最大硬币数被限制为 255,因为硬币是以 8 位整数存储的。这意味着硬币数的范围只能从 0 到 255,即 8 位整数所能表示的最大值。

那么,我们该如何处理 Simplicity 中的比特大小概念呢?

字类型(固定宽度整数、字符)

字类型是通过将多个比特实例组合在一个乘积中形成的。位宽指的是组合的位数。它决定了二进制数据(如整数或字)的大小,并确定了可表达的数值范围。

例如

2¹ := 2 表示 1 位整数,即简单的位类型。

2² := 2¹ × 2¹,表示两个位类型的乘积,或一个 2 位字。

2⁴ := 2² × 2²,表示两个 2 位类型的乘积,或表示一个 4 位字。

2⁸ := 2⁴ × 2⁴,表示两个 4 位类型的乘积,或表示一个 8 位字。

以此类推,位宽不断增加。

4 位字是 4 个 0 或 1 的 16 种可能组合中的任意一种。4 位字类型可用二进制符号表示从 0000 到 1111 的数字。然而,二进制数值很快就会变得冗长难读,尤其是较大的数字。十六进制将四个二进制数字压缩成一个,使数字的表示更加容易。

HexDigit := 2⁴

B := 1011:十六进制数字

或者,我们也可以使用 ASCII 编码来定义 Char:

Char := 2⁸

k' := 0110 1011 : 字符

我们可以将 8 位字定义为 Int:

Int8 := 2⁸

我们可以使用 8 位 Int 来实现 RGB 颜色:

RGB := ((Int8, Int8), Int8) : (2⁸ × 2⁸) × 2⁸

RGB 中的每个 Int8 表示颜色的红色、绿色和蓝色分量,每个分量可以取 0 到 255(2⁸ - 1)之间的任意值来表示该颜色通道的强度。

要创建一个 RGB 颜色实例,我们可以为每个分量提供特定值:

BitcoinOrange := ((255, 153), 0) :RGB

有限类型

Simplicity 中使用的类型符号不仅表示类型的结构,还能让人了解可能存在的不同值的数量。例如,类型 2 + 2⁴ 共有 18 种不同的值。

在简单性中,所有类型都是有限的。这意味着某些类型是不可能的,例如定义为 List := Item + (Item × List) 的无限列表。无限或递归类型给终止、资源管理和程序行为推理带来了挑战。

通过限制类型的有限性,简单性可以为程序行为提供更有力的保证,确保终止,并对代码进行严格的分析和验证。有限类型对于加密协议来说绰绰有余。此外,像 STARKS 和 SNARKs(甚至是卷积)都能将大量计算浓缩为对相对较小的有限类型数据的加密分析。

Simplicity 资源

如果您渴望深入了解 Simplicity,可以在 Simplicity Wiki 上的白皮书核心语言概述中找到有关该语言的主要技术文档。不过,这些资源对于非功能型程序员来说可能有些难度,因此我们在 Build On L2 社区上开设了一个 Simplicity 通用主题。欢迎在此向我们提出任何问题。我举这些例子的目的是弥合这一差距,为这些概念提供更通俗易懂的解释。简约语言作为一种受数学启发的语言,可以将我们带入一个完全可编程的货币世界,并拥有经过正式验证的智能合约。通过了解 Simplicity 的核心类型,我们可以为在本系列接下来的文章中探索函数、表达式和组合器打下坚实的基础。

If you have specific preferences, please, mark the topic(s) you would like to read: