Class

badge

Erg 中的类大致是一种可以创建自己的元素(实例)的类型 这是一个简单类的示例

Person = Class {.name = Str; .age = Nat}
# 如果 `.new` 没有定义,那么 Erg 将创建 `Person.new = Person::__new__`
Person.
    new name, age = Self::__new__ {.name = name; .age = age}

john = Person.new "John Smith", 25
print! john # <Person object>
print! classof(john) # Person

赋予"Class"的类型(通常是记录类型)称为需求类型(在本例中为"{.name = Str; .age = Nat}") 可以使用 <Class name>::__new__ {<attribute name> = <value>; 创建实例 ...} 可以创建 {.name = "约翰·史密斯"; .age = 25} 只是一条记录,但它通过传递 Person.new 转换为 Person 实例 创建此类实例的子例程称为构造函数 在上面的类中,.new 方法被定义为可以省略字段名等

请注意,以下不带换行符的定义将导致语法错误

Person.new name, age = ... # 语法错误: 不能直接在对象上定义属性

新类型符号

对于非记录类型T ',可以通过' C = class T定义类C。这是一个简写符号,相当于C = Class {base = T}。 这是为了简化所谓“新型模式”的定义。 同样,构造函数 __new__ / new可以直接传递给T类型对象,而无需将其包装在记录中

Id = Class {base = Int}
i = Id.new {base = 1}
# ↓
Id = Class Int
i = Id.new 1

实例和类属性

在 Python 和其他语言中,实例属性通常在块侧定义如下,但请注意,这样的写法在 Erg 中具有不同的含义

# Python
class Person:
    name: str
    age: int
# 在Erg中,这个符号意味着类属性的声明(不是实例属性)
Person = Class()
Person.
    name: Str
    age: Int
# 以上 Python 代码的 Erg 代码
Person = Class {
    .name = Str
    .age = Nat
}

元素属性(在记录中定义的属性)和类型属性(也称为实例/类属性,尤其是在类的情况下)是完全不同的东西。类型属性是类型本身的属性。当一个类型的元素本身没有所需的属性时,它指的是一个类型属性。元素属性是元素直接拥有的唯一属性 为什么要进行这种区分? 如果所有属性都是元素属性,那么在创建对象时复制和初始化所有属性将是低效的 此外,以这种方式划分属性明确了诸如"该属性是共享的"和"该属性是分开持有的"之类的角色

下面的例子说明了这一点。species 属性对所有实例都是通用的,因此将其用作类属性更自然。但是,属性 name 应该是实例属性,因为每个实例都应该单独拥有它

Person = Class {name = Str}
Person::
    species = "human"
Person.
    describe() =
        log "species: \{Person::species}"
    greet self =
        log "Hello, My name is \{self::name}."

Person.describe() # 类型: Person
Person.greet() # 类型错误: 未绑定的方法 Person.greet 需要一个参数

john = Person.new {name = "John"}
john.describe() # 类型: human
john.greet() # 你好,我是约翰

alice = Person.new {name = "Alice"}
alice.describe() # 类型: human
alice.greet() # 你好,我是爱丽丝

顺便说一下,如果实例属性和类型属性具有相同的名称和相同的类型,则会发生编译错误。这是为了避免混淆

C = Class {.i = Int}
C.i = 1 # 属性错误: `.i` 已在实例字段中定义

类(Class), 类型(Type)

请注意,1 的类和类型是不同的 只有一个类 Int1 的生成器。可以通过classof(obj)obj.__class__获取对象所属的类 相比之下,1有无数种。例如,{1}, {0, 1}, 0..12, Nat, Int, Num 但是,可以将最小类型定义为单一类型,在本例中为"{1}"。可以通过Typeof(obj)获取对象所属的类型。这是一个编译时函数 对象可以使用补丁方法以及类方法 Erg 不允许您添加类方法,但您可以使用 patch 来扩展类

您还可以从现有类(Inheritable 类)继承 您可以使用 Inherit 创建一个继承类。左侧的类型称为派生类,右侧的"继承"的参数类型称为基类(继承类)

MyStr = Inherit Str
# other: 如果你设置 ``other: Str'',你可以使用 MyStr
MyStr.
    `-` self, other: Str = self.replace other, ""

abc = MyStr.new("abc")
# 这里的比较是向上的
assert abc - "b" == "ac"

与 Python 不同,默认情况下,定义的 Erg 类是 final(不可继承的) 要使类可继承,必须将 Inheritable 装饰器附加到该类 Str` 是可继承的类之一

MyStr = Inherit Str # OK
MyStr2 = Inherit MyStr # NG

@Inheritable
InheritableMyStr = Inherit Str
MyStr3 = Inherit InheritableMyStr # OK

Inherit ObjClass() 在实践中几乎是等价的。一般使用后者

类具有与类型不同的等价检查机制 类型基于其结构进行等效性测试

Person = {.name = Str; .age = Nat}
Human = {.name = Str; .age = Nat}

assert Person == Human

class has no equivalence relation defined.

Person = Class {.name = Str; .age = Nat}
Human = Class {.name = Str; .age = Nat}

Person == Human # 类型错误: 无法比较类

与结构类型的区别

我们说过类是一种可以生成自己的元素的类型,但这并不是严格的描述。事实上,一个记录类型+补丁可以做同样的事情

Person = {.name = Str; .age = Nat}
PersonImpl = Patch Person
PersonImpl.
    new name, age = {.name; .age}

john = Person.new("John Smith", 25)

使用类有四个优点 第一个是构造函数经过有效性检查,第二个是它的性能更高,第三个是您可以使用符号子类型(NST),第四个是您可以继承和覆盖

我们之前看到记录类型 + 补丁也可以定义一个构造函数(某种意义上),但这当然不是一个合法的构造函数。这当然不是一个合法的构造函数,因为它可以返回一个完全不相关的对象,即使它调用自己.new。在类的情况下,.new 被静态检查以查看它是否生成满足要求的对象

~

类的类型检查只是检查对象的。__class__ 对象的属性。因此可以快速检查一个对象是否属于一个类型

~

Erg 在课堂上启用 NST; NST 的优点包括健壮性 在编写大型程序时,经常会出现对象的结构巧合匹配的情况

Dog = {.name = Str; .age = Nat}
DogImpl = Patch Dog
DogImpl.
    bark = log "Yelp!"
...
Person = {.name = Str; .age = Nat}
PersonImpl = Patch Person
PersonImpl.
    greet self = log "Hello, my name is \{self.name}."

john = {.name = "John Smith"; .age = 20}
john.bark() # "Yelp!"

DogPerson 的结构完全一样,但让动物打招呼,让人类吠叫显然是无稽之谈 前者是不可能的,所以让它不适用更安全。在这种情况下,最好使用类

Dog = Class {.name = Str; .age = Nat}
Dog.bark = log "Yelp!"
...
Person = Class {.name = Str; .age = Nat}
Person.greet self = log "Hello, my name is \{self.name}."

john = Person.new {.name = "John Smith"; .age = 20}
john.bark() # 类型错误: `Person` 对象没有方法 `.bark`

另一个特点是补丁添加的类型属性是虚拟的,实现类不作为实体保存 也就是说,T.xT.bar 是可以通过与 {i = Int} 兼容的类型访问(编译时绑定)的对象,并且未在 {i = Int} C 相反,类属性由类本身持有。因此,它们不能被不处于继承关系的类访问,即使它们具有相同的结构

C = Class {i = Int}
C.
    foo self = ...
print! dir(C) # ["foo", ...].

T = Patch {i = Int}
T.
    x = 1
    bar self = ...
print! dir(T) # ["bar", "x", ...].
assert T.x == 1
assert {i = 1}.x == 1
print! T.bar # <函数 bar>
{i = Int}.bar # 类型错误: Record({i = Int}) 没有方法 `.bar`
C.bar # 类型错误: C 没有方法 `.bar` 打印!
print! {i = 1}.bar # <方法 bar>
C.new({i = 1}).bar # <方法 bar>

与数据类的区别

有两种类型的类: 常规类,通过Class成为记录类,以及从记录类继承(Inherit)的数据类 数据类继承了记录类的功能,具有分解赋值、默认实现的==hash等特性。另一方面,数据类有自己的等价关系和格式展示 另一方面,如果要定义自己的等价关系或格式显示,则应使用普通类

C = Class {i = Int}
c = C.new {i = 1}
d = C.new {i = 2}
print! c # <C object>
c == d # 类型错误: `==` 没有为 `C` 实现

D = Inherit {i = Int}
e = D::{i = 1} # 与`e = D.new {i = 1}`相同
f = D::{i = 2}
print! e # D(i=1)
assert e ! = f

枚举类

为了便于定义"Or"类型的类,提供了一个"Enum"

X = Class()
Y = Class()
XorY = Enum X, Y

每种类型都可以通过XorY.XXorY.Y来访问,构造函数可以通过X.new |> XorY.new获得

x1 = XorY.new X.new()
x2 = (X.new |> XorY.new())()
x3 = (Y.new |> XorY.new())()
assert x1 == x2
assert x1 != x3

类关系

类是需求类型的子类型。类中可以使用需求类型的方法(包括补丁方法)

T = Trait {.foo = Foo}
C = Class(... , impl: T)
C.
    foo = foo
    bar x = ...
assert C < T
assert C.foo == foo
assert not T < C
assert T.foo == Foo