继承
继承允许您定义一个新类,为现有类添加功能或专业化 继承类似于包含在Trait中。继承的类成为原始类的子类型
NewInt = Inherit Int
NewInt.
plus1 self = self + 1
assert NewInt.new(1).plus1() == 2
assert NewInt.new(1) + NewInt.new(1) == 2
如果你希望新定义的类是可继承的,你必须给它一个 Inheritable
装饰器
您可以指定一个可选参数 additional
以允许该类具有其他实例属性,但前提是该类是一个值类。但是,如果类是值类,则不能添加实例属性
@Inheritable
Person = Class {name = Str}
Student = Inherit Person, additional: {id = Int}
john = Person.new {name = "John"}
alice = Student.new {name = "Alice", id = 123}
MailAddress = Inherit Str, additional: {owner = Str} # 类型错误: 实例变量不能添加到值类中
Erg 的特殊设计不允许继承"Never"类型。Erg 的特殊设计不允许继承 Never
类型,因为 Never
是一个永远无法实例化的独特类
枚举类的继承
Or 类型 也可以被继承。在这种情况下,您可以通过指定可选参数 Excluding
来删除任何选项(可以使用 or
进行多项选择)
不能添加其他选项。添加选项的类不是原始类的子类型
Number = Class Int or Float or Complex
Number.abs(self): Float =
match self:
i: Int -> i.abs().into Float
f: Float -> f.abs()
c: Complex -> c.abs().into Float
# c: 复杂不能出现在匹配选项中
RealNumber = Inherit Number, Excluding: Complex
同样,也可以指定细化类型
Months = Class 0..12
MonthsNot31Days = Inherit Months, Excluding: {1, 3, 5, 7, 8, 10, 12}
StrMoreThan3 = Class StrWithLen N | N >= 3
StrMoreThan4 = Inherit StrMoreThan3, Excluding: StrWithLen N | N == 3
覆盖
该类与补丁相同,可以将新方法添加到原始类型,但可以进一步"覆盖"该类
这种覆盖称为覆盖。要覆盖,必须满足三个条件
首先,覆盖必须有一个 Override
装饰器,因为默认情况下它会导致错误
另外,覆盖不能改变方法的类型。它必须是原始类型的子类型
如果你重写了一个被另一个方法引用的方法,你也必须重写所有被引用的方法
为什么这个条件是必要的?这是因为重写不仅会改变一种方法的行为,而且可能会影响另一种方法的行为
让我们从第一个条件开始。此条件是为了防止"意外覆盖"
换句话说,必须使用 Override
装饰器来防止派生类中新定义的方法的名称与基类的名称冲突
接下来,考虑第二个条件。这是为了类型一致性。由于派生类是基类的子类型,因此它的行为也必须与基类的行为兼容
最后,考虑第三个条件。这种情况是 Erg 独有的,在其他面向对象语言中并不常见,同样是为了安全。让我们看看如果不是这种情况会出现什么问题
# 反面示例
@Inheritable
Base! = Class {x = Int!}
Base!
f! ref! self =
print! self::x
self.g!()
g! ref! self = self::x.update! x -> x + 1
Inherited! = Inherit Base!
Inherited!
@Override
g! ref! self = self.f!() # 无限递归警告: 此代码陷入无限循环
# 覆盖错误: 方法 `.g` 被 `.f` 引用但未被覆盖
在继承类 Inherited!
中,.g!
方法被重写以将处理转移到 .f!
。但是,基类中的 .f!
方法会将其处理转移到 .g!
,从而导致无限循环。.f
是 Base!
类中的一个没有问题的方法,但它被覆盖以一种意想不到的方式使用,并且被破坏了
Erg 已将此规则构建到规范中
# OK.
@Inheritable
Base! = Class {x = Int!}
Base!
f! ref! self =
print! self::x
self.g!()
g! ref! self = self::x.update! x -> x + 1
Inherited! = Inherit Base!
Inherited!
@Override
f! ref! self =
print! self::x
self::x.update! x -> x + 1
@Override
g! ref! self = self.f!()
然而,这个规范并没有完全解决覆盖问题。然而,这个规范并没有完全解决覆盖问题,因为编译器无法检测覆盖是否解决了问题 创建派生类的程序员有责任纠正覆盖的影响。只要有可能,尝试定义一个别名方法
替换Trait(或看起来像什么)
尽管无法在继承时替换Trait,但有一些示例似乎可以这样做
例如,Int
,Real
的子类型(实现了 Add()
),似乎重新实现了 Add()
Int = Class ... , Impl := Add() and ...
但实际上 Real
中的 Add()
代表 Add(Real, Real)
,而在 Int
中它只是被 Add(Int, Int)
覆盖
它们是两个不同的Trait(Add
是一个 covariate,所以Add(Real, Real) :> Add(Int, Int)
)
多重继承
Erg 不允许普通类之间的交集、差异和互补
Int and Str # 类型错误: 无法合并类
该规则防止从多个类继承,即多重继承
IntAndStr = Inherit Int and Str # 语法错误: 不允许类的多重继承
但是,可以使用多个继承的 Python 类
多层(多级)继承
Erg 继承也禁止多层继承。也就是说,您不能定义从另一个类继承的类 从"Object"继承的可继承类可能会异常继承
同样在这种情况下,可以使用 Python 的多层继承类
重写继承的属性
Erg 不允许重写从基类继承的属性。这有两个含义
第一个是对继承的源类属性的更新操作。例如,它不能重新分配,也不能通过 .update!
方法更新
覆盖与重写不同,因为它是一种用更专业的方法覆盖的操作。覆盖也必须替换为兼容的类型
@Inheritable
Base! = Class {.pub = !Int; pri = !Int}
Base!
var = !1
inc_pub! ref! self = self.pub.update! p -> p + 1
Inherited! = Inherit Base!
Inherited!
var.update! v -> v + 1
# 类型错误: 不能更新基类变量
@Override
inc_pub! ref! self = self.pub + 1
# 覆盖错误: `.inc_pub!` 必须是 `Self! 的子类型! () => ()`
第二个是对继承源的(变量)实例属性的更新操作。这也是被禁止的。基类的实例属性只能从基类提供的方法中更新 无论属性的可见性如何,都无法直接更新。但是,它们可以被读取
@Inheritable
Base! = Class {.pub = !Int; pri = !Int}
Base!
inc_pub! ref! self = self.pub.update! p -> p + 1
inc_pri! ref! self = self::pri.update! p -> p + 1
self = self.pub.update!
Inherited!
# OK
add2_pub! ref! self =
self.inc_pub!()
self.inc_pub!()
# NG, `Child` 不能触摸 `self.pub` 和 `self::pri`
add2_pub! ref! self =
self.pub.update! p -> p + 2
毕竟 Erg 继承只能添加新的属性和覆盖基类的方法
使用继承
虽然继承在正确使用时是一项强大的功能,但它也有一个缺点,即它往往会使类依赖关系复杂化,尤其是在使用多层或多层继承时。复杂的依赖关系会降低代码的可维护性 Erg 禁止多重和多层继承的原因是为了降低这种风险,并且引入了类补丁功能以降低依赖关系的复杂性,同时保留继承的"添加功能"方面
那么,反过来说,应该在哪里使用继承呢?一个指标是何时需要"基类的语义子类型"
Erg 允许类型系统自动进行部分子类型确定(例如,Nat,其中 Int 大于或等于 0)
但是,例如,仅依靠 Erg 的类型系统很难创建"表示有效电子邮件地址的字符串类型"。您可能应该对普通字符串执行验证。然后,我们想为已通过验证的字符串对象添加某种"保证"。这相当于向下转换为继承的类。将 Str object
向下转换为 ValidMailAddressStr
与验证字符串是否采用正确的电子邮件地址格式是一一对应的
ValidMailAddressStr = Inherit Str
ValidMailAddressStr.
init s: Str =
validate s # 邮件地址验证
Self.new s
s1 = "invalid mail address"
s2 = "foo@gmail.com"
_ = ValidMailAddressStr.init s1 # 恐慌: 无效的邮件地址
valid = ValidMailAddressStr.init s2
valid: ValidMailAddressStr # 确保电子邮件地址格式正确
另一个指标是您何时想要实现名义多态性
例如,下面定义的 greet!
过程将接受任何类型为 Named
的对象
但显然应用 Dog
类型的对象是错误的。所以我们将使用 Person
类作为参数类型
这样,只有 Person
对象、从它们继承的类和 Student
对象将被接受为参数
这是比较保守的,避免不必要地承担过多的责任
Named = {name = Str; ...}
Dog = Class {name = Str; breed = Str}
Person = Class {name = Str}
Student = Inherit Person, additional: {id = Int}
structural_greet! person: Named =
print! "Hello, my name is \{person::name}."
greet! person: Person =
print! "Hello, my name is \{person::name}."
max = Dog.new {name = "Max", breed = "Labrador"}
john = Person.new {name = "John"}
alice = Student.new {name = "Alice", id = 123}
structural_greet! max # 你好,我是马克斯
structural_greet! john # 你好,我是约翰
greet! alice # 你好,我是爱丽丝
greet! max # 类型错误: