補丁

badge

Erg 不允許修改現有類型和類 這意味著,不可能在類中定義額外的方法,也不能執行特化(一種語言特性,單態化多態聲明的類型并定義專用方法,如在 C++ 中) 但是,在許多情況下,您可能希望向現有類型或類添加功能,并且有一個稱為"修補"的功能允許您執行此操作

StrReverse = Patch Str
StrReverse.
    reverse self = self.iter().rev().collect(Str)

assert "abc".reverse() == "cba"

補丁的名稱應該是要添加的主要功能的簡單描述 這樣,被修補類型的對象(Str)可以使用修補程序的方法(StrReverse) 實際上,內置方法.reverse并不是Str的方法,而是StrRReverse中添加的方法

但是,補丁方法的優先級低于名義類型(類/trait)的方法,并且不能覆蓋現有類型

StrangeInt = Patch Int
StrangeInt.
    `_+_` = Int.`_-_` # 賦值錯誤: . `_+_` 已在 Int 中定義

如果要覆蓋,則必須從類繼承 但是,基本上建議不要覆蓋并定義具有不同名稱的方法 由于一些安全限制,覆蓋不是很容易做到

StrangeInt = Inherit Int
StrangeInt.
# 覆蓋方法必須被賦予覆蓋裝飾器
    # 另外,你需要覆蓋所有依賴于 Int.`_+_` 的 Int 方法
    @Override
    `_+_` = Super.`_-_` # OverrideError: Int.`_+_` 被 ... 引用,所以這些方法也必須被覆蓋

選擇修補程序

可以為單一類型定義修復程序,并且可以組合在一起

# foo.er

StrReverse = Patch(Str)
StrReverse.
    reverse self = ...
StrMultiReplace = Patch(Str)
StrMultiReverse.
    multi_replace self, pattern_and_targets: [(Pattern, Str)] = ...
StrToCamelCase = Patch(Str)
StrToCamelCase.
    to_camel_case self = ...
StrToKebabCase = Patch(Str)
StrToKebabCase.
    to_kebab_case self = ...

StrBoosterPack = StrReverse and StrMultiReplace and StrToCamelCase and StrToKebabCase
StrBoosterPack = StrReverse and StrMultiReplace and StrToCamelCase and StrToKebabCase
{StrBoosterPack;} = import "foo"

assert "abc".reverse() == "cba"
assert "abc".multi_replace([("a", "A"), ("b", "B")]) == "ABc"
assert "to camel case".to_camel_case() == "toCamelCase"
assert "to kebab case".to_kebab_case() == "to-kebab-case"

如果定義了多個修復程序,其中一些可能會導致重復實施

# foo.er

StrReverse = Patch(Str)
StrReverse.
    reverse self = ...
# 更高效的實現
StrReverseMk2 = Patch(Str)
StrReverseMk2.
    reverse self = ...

"hello".reverse() # 補丁選擇錯誤: `.reverse` 的多個選擇: StrReverse, StrReverseMk2

在這種情況下,您可以使用 related function 形式而不是方法形式使其唯一

assert StrReverseMk2.reverse("hello") == "olleh"

You can also make it unique by selectively importing.

{StrReverseMk2;} = import "foo"

assert "hello".reverse() == "olleh"

膠水補丁

維修程序也可以將類型相互關聯。StrReverse 補丁涉及 StrReverse 這樣的補丁稱為 glue patch 因為 Str 是內置類型,所以用戶需要使用膠水補丁來改造Trait

Reverse = Trait {
    .reverse = Self.() -> Self
}

StrReverse = Patch Str, Impl := Reverse
StrReverse.
    reverse self =
        self.iter().rev().collect(Str)

每個類型/Trait對只能定義一個膠水補丁 這是因為如果多個膠水修復程序同時"可見",就不可能唯一確定選擇哪個實現 但是,當移動到另一個范圍(模塊)時,您可以交換維修程序

NumericStr = Inherit Str
NumericStr.
    ...

NumStrRev = Patch NumericStr, Impl := Reverse
NumStrRev.
    ...
# 重復修補程序錯誤: 數值Str已與"反向"關聯`
# 提示: 'Str'(NumericStr'的父類)通過'StrReverse'與'Reverse'關聯

附錄: 與 Rust Trait的關系

Erg 修復程序相當于 Rust 的(改造的)impl


#![allow(unused)]
fn main() {
// Rust
trait Reverse {
    fn reverse(self) -> Self;
}

impl Reverse for String {
    fn reverse(self) -> Self {
        self.chars().rev().collect()
    }
}
}

可以說,Rust 的Trait是 Erg 的Trait和修復程序的Trait。這使得 Rust 的Trait聽起來更方便,但事實并非如此

# Erg
Reverse = Trait {
    .reverse = Self.() -> Self
}

StrReverse = Patch(Str, Impl := Reverse)
StrReverse.
    reverse self =
        self.iter().rev().collect(Str)

因為 impl 塊在 Erg 中被對象化為補丁,所以在從其他模塊導入時可以選擇性地包含。作為副作用,它還允許將外部Trait實現到外部結構 此外,結構類型不再需要諸如 dyn traitimpl trait 之類的語法

# Erg
reversible: [Reverse; 2] = [[1, 2, 3], "hello"]

iter|T|(i: Iterable T): Iterator T = i.iter()

#![allow(unused)]
fn main() {
// Rust
let reversible: [Box<dyn Reverse>; 2] = [Box::new([1, 2, 3]), Box::new("hello")];

fn iter<I>(i: I) -> impl Iterator<Item = I::Item> where I: IntoIterator {
    i.into_iter()
}
}

通用補丁

補丁不僅可以為一種特定類型定義,還可以為"一般功能類型"等定義 在這種情況下,要給出自由度的項作為參數給出(在下面的情況下,T: Type)。以這種方式定義的補丁稱為全對稱補丁 如您所見,全對稱補丁正是一個返回補丁的函數,但它本身也可以被視為補丁

FnType T: Type = Patch(T -> T)
FnType(T).
    type = T

assert (Int -> Int).type == Int

結構補丁

此外,可以為滿足特定結構的任何類型定義修復程序 但是,這比名義上的維修程序和類方法具有較低的優先級

在定義結構修復程序時應使用仔細的設計,因為某些屬性會因擴展而丟失,例如以下內容

# 這不應該是 `Structural`
Norm = Structural Patch {x = Int; y = Int}
Norm.
    norm self = self::x**2 + self::y**2

Point2D = Class {x = Int; y = Int}
assert Point2D.new({x = 1; y = 2}).norm() == 5

Point3D = Class {x = Int; y = Int; z = Int}
assert Point3D.new({x = 1; y = 2; z = 3}).norm() == 14 # AssertionError: