Erg 的类型系统

badge

下面简单介绍一下 Erg 的类型系统。详细信息在其他部分进行说明

如何定义

Erg 的独特功能之一是(普通)变量、函数(子例程)和类型(Kind)定义之间的语法没有太大区别。所有都是根据普通变量和函数定义的语法定义的

f i: Int = i + 1
f # <函数 f>
f(1) # 2
f.method self = ... # 语法错误: 无法为子例程定义方法

T I: Int = {...}
T # <kind 'T'>
T(1) # 类型 T(1)
T.method self = ...
D = Class {private = Int; .public = Int}
D # <类 'D'>
o1 = {private = 1; .public = 2} # o1 是一个不属于任何类的对象
o2 = D.new {private = 1; .public = 2} # o2 是 D 的一个实例
o2 = D.new {.public = 2} # 初始化错误: 类 'D' 需要属性 'private'(: Int) 但未定义

Classification

Erg 中的所有对象都是强类型的 顶层类型是{=},实现了__repr____hash__clone等(不是必须的方法,这些属性不能被覆盖) Erg 的类型系统包含结构子类型 (SST)。该系统类型化的类型称为结构类型 结构类型主要分为三种: Attributive(属性类型)、Refinement(细化类型)和Algebraic(代数类型)

RecordEnumIntervalUnionIntersectionDiff
kindAttributiveRefinementRefinementAlgebraicAlgebraicAlgebraic
generatorrecordsetrange operatoror operatorand operatornot operator

也可以使用名义子类型(NST),将 SST 类型转换为 NST 类型称为类型的名义化。结果类型称为名义类型 在 Erg 中,名义类型是类和Trait。当我们简单地说类/Trait时,我们通常指的是记录类/Trait

TypeAbstractionSubtyping procedure
NSTNominalTypeTraitInheritance
SSTStructuralTypeStructural Trait(Implicit)

整个名义类型的类型(NominalType)和整个结构类型的类型(StructuralType)是整个类型(Type)的类型的子类型

Erg 可以将参数(类型参数)传递给类型定义。带有类型参数的 OptionArray 等称为多项式类型。这些本身不是类型,但它们通过应用参数成为类型。诸如 IntStr 等没有参数的类型称为简单类型(标量类型)

一个类型可以看成一个集合,并且存在包含关系。例如,"Num"包含"Add"、"Sub"等,"Int"包含"Nat" 所有类的上类是Object == Class {:},所有类型的下类是Never == Class {}。这在下面描述

类型

Array T 这样的类型可以看作是 Type -> Type 类型的函数,它以 T 类型为参数并返回 Array T 类型(在类型论中也称为 Kind)。像 Array T 这样的类型专门称为多态类型,而 Array 本身称为一元 Kind

已知参数和返回类型的函数的类型表示为(T, U) -> V。如果要指定同一类型的整个双参数函数,可以使用 |T| (T, T) -> T,如果要指定整个 N 参数函数,可以使用 Func N。但是,Func N 类型没有关于参数数量或其类型的信息,因此所有返回值在调用时都是Obj 类型

Proc 类型表示为 () => Int 等等。此外,Proc 类型实例的名称必须以 ! 结尾

Method 类型是一个函数/过程,其第一个参数是它所属的对象 self(通过引用)。对于依赖类型,也可以在应用方法后指定自己的类型。这是 T!(!N) 类型和 T!(N ~> N-1)。() => Int 等等

Erg 的数组(Array)就是 Python 所说的列表。[诠释; 3]是一个数组类,包含三个Int类型的对象

Note: (Type; N) 既是类型又是值,所以可以这样使用

Types = (Int, Str, Bool)

for! Types, T =>
    print! T
# Int Str Bool
a: Types = (1, "aaa", True)
pop|T, N|(l: [T; N]): ([T; N-1], T) =
    [*l, last] = l
    (l, last)

lpop|T, N|(l: [T; N]): (T, [T; N-1]) =
    [first, *l] = l
    (first, l)

! 结尾的类型可以重写内部结构。例如,[T; !N] 类是一个动态数组 要从"T"类型的对象创建"T!"类型的对象,请使用一元运算符"!"

i: Int! = !1
i.update! i -> i + 1
assert i == 2
arr = [1, 2, 3]
arr.push! 4 # 导入错误
mut_arr = [1, 2, 3].into [Int; !3]
mut_arr.push4
assert mut_arr == [1, 2, 3, 4].

类型定义

类型定义如下

Point2D = {.x = Int; .y = Int}

请注意,如果从变量中省略 .,它将成为类型中使用的私有变量。但是,这也是必需的属性 由于类型也是对象,因此类型本身也有属性。这样的属性称为类型属性。在类的情况下,它们也称为类属性

数据类型

如前所述,Erg 中的"类型"大致表示一组对象

下面是 Add 类型的定义,需要 +(中间运算符)。R, O 是所谓的类型参数,可以是真正的类型(类),例如 IntStr。在其他语言中,类型参数被赋予特殊的符号(泛型、模板等),但在 Erg 中,它们可以像普通参数一样定义 类型参数也可以用于类型对象以外的类型。例如数组类型[Int; 3]Array Int, 3 的语法糖。如果类型实现重叠,用户必须明确选择一个

Add R = Trait {
    .AddO = Type
    . `_+_` = Self.(R) -> Self.AddO
}

._+_是Add._+_的缩写。前缀运算符 .+_Num 类型的方法

Num = Add and Sub and Mul and Eq
NumImpl = Patch Num
NumImpl.
    `+_`(self): Self = self
    ...

多态类型可以像函数一样对待。通过将它们指定为 Mul Int、Str 等,它们可以是单态的(在许多情况下,它们是用实际参数推断出来的,而没有指定它们)

1 + 1
`_+_` 1, 1
Nat.`_+_` 1, 1
Int.`_+_` 1, 1

前四行返回相同的结果(准确地说,底部的返回 Int),但通常使用顶部的 Ratio.+(1, 1) 将返回 2.0 而不会出错 这是因为 Int <: Ratio,所以 1 向下转换为 Ratio 但这不是演员

i = 1
if i: # 类型错误: i: Int 不能转换为 Bool,请改用 Int.is_zero()
    log "a"
    log "b"

这是因为 Bool <: Int (True == 1, False == 0)。转换为子类型通常需要验证

类型推理系统

Erg 使用静态鸭子类型,因此几乎不需要显式指定类型

f x, y = x + y

在上面的代码中,带有 + 的类型,即 Add 是自动推断的; Erg 首先推断出最小的类型。如果f 0, 1,它将推断f x: {0},y: {1},如果n: Nat; f n, 1,它会推断f x: Nat, y: {1}。最小化之后,增加类型直到找到实现。在 {0}, {1} 的情况下,NatNat 是单态的,因为 Nat 是具有 + 实现的最小类型 如果是 {0}, {-1},它与 Int 是单态的,因为它不匹配 Nat。如果子类型和父类型之间没有关系,则首先尝试具有最低浓度(实例数)(或者在多态类型的情况下参数更少)的那个 {0}{1} 是枚举类型,它们是部分类型,例如 IntNat 例如,可以为枚举类型指定名称和请求/实现方法。在有权访问该类型的命名空间中,满足请求的对象可以使用实现方法

Binary = Patch {0, 1}
Binary.
    # self 包含一个实例。在此示例中,为 0 或 1
    # 如果你想重写self,你必须追加! 必须添加到类型名称和方法名称
    is_zero(self) = match self:
        0 -> True
        1 -> False # 你也可以使用 _ -> False
    is_one(self) = not self.is_zero()
    to_bool(self) = match self:
        0 -> False
        1 -> True

此后,代码"0.to_bool()"是可能的(尽管"0 as Bool == False"是内置定义的) 这是一个实际上可以重写 self 的类型的示例,如代码所示

Binary! = Patch {0, 1}!
Binary!
    switch! ref! self = match! self:
        0 => self = 1
        1 => self = 0

b = !1
b.switch!()
print! b # => 0

结构类型(匿名类型)

Binary = {0, 1}

上面代码中的 Binary 是一个类型,其元素是 01。它也是 Int 类型的子类型,它同时具有 01{} 这样的对象本身就是一种类型,可以在分配或不分配给上述变量的情况下使用 这样的类型称为结构类型。当我们想强调它作为后者而不是类(命名类型)的用途时,它也被称为未命名类型。{0, 1}这样的结构类型称为枚举类型,还有区间类型、记录类型等

类型标识

无法指定以下内容。例如,您不能指定 IntIntIntIntIntInt 例如,IntStr都是Add,但是IntStr不能相加

add l: Add, r: Add =
    l + r # 类型错误: `_+_` 没有实现: |T, U <: Add| (T, U) -> <失败>

此外,下面的类型 AB 不被认为是同一类型。但是,类型"O"被认为是匹配的

... |R1, R2, O, A <: Add(R1, O); B <: Add(R2, O)|