From 5fd4484eecc44e208ed7645f8e35d479e9cc86c2 Mon Sep 17 00:00:00 2001 From: Guset0x0 Date: Tue, 26 Nov 2024 16:38:08 +0800 Subject: [PATCH 1/2] update & refactor document for traits: we can now call trait impl with dot syntax --- moonbit-docs/docs/README.md | 124 ++++++++++++------ .../current/README.md | 122 +++++++++-------- 2 files changed, 147 insertions(+), 99 deletions(-) diff --git a/moonbit-docs/docs/README.md b/moonbit-docs/docs/README.md index 20e0294b..f964443d 100644 --- a/moonbit-docs/docs/README.md +++ b/moonbit-docs/docs/README.md @@ -1900,15 +1900,51 @@ trait I { In the body of a trait definition, a special type `Self` is used to refer to the type that implements the trait. To implement a trait, a type must provide all the methods required by the trait. -However, there is no need to implement a trait explicitly. Types with the required methods automatically implements a trait. For example, the following trait: +Implementation for trait methods can be provided via the syntax `impl Trait for Type with method_name(...) { ... }`, for example: ```moonbit trait Show { to_string(Self) -> String } + +struct MyType { ... } +impl Show for MyType with to_string(self) { ... } + +// trait implementation with type parameters. +// `[X : Show]` means the type parameter `X` must implement `Show`, +// this will be covered later. +impl[X : Show] Show for Array[X] with to_string(self) { ... } ``` -is automatically implemented by builtin types such as `Int` and `Double`. +Type annotation can be omitted for trait `impl`: MoonBit will automatically infer the type based on the signature of `Trait::method` and the self type. + +The author of the trait can also define default implementations for some methods in the trait, for example: + +```moonbit +trait I { + f(Self) -> Unit + f_twice(Self) -> Unit +} + +impl I with f_twice(self) { + self.f() + self.f() +} +``` + +Implementers of trait `I` don't have to provide an implementation for `f_twice`: to implement `I`, only `f` is necessary. +They can always override the default implementation with an explicit `impl I for Type with f_twice`, if desired, though. + +If an explicit `impl` or default implementation is not found, trait method resolution falls back to regular methods. +This allows types to implement a trait implicitly, hence allowing different packages to work together without seeing or depending on each other. +For example, the following trait is automatically implemented for builtin number types such as `Int` and `Double`: + +```moonbit +trait Number { + op_add(Self, Self) -> Self + op_mul(Self, Self) -> Self +} +``` When declaring a generic function, the type parameters can be annotated with the traits they should implement, allowing the definition of constrained generic functions. For example: @@ -1919,7 +1955,7 @@ trait Number { } fn square[N: Number](x: N) -> N { - x * x + x * x // same as `x.op_mul(x)` } ``` @@ -1938,7 +1974,7 @@ trait Number { } fn square[N: Number](x: N) -> N { - x * x + x * x // same as `x.op_mul(x)` } struct Point { @@ -1946,21 +1982,12 @@ struct Point { y: Int } derive(Show) -fn op_add(self: Point, other: Point) -> Point { - { x: self.x + other.x, y: self.y + other.y } -} - -fn op_mul(self: Point, other: Point) -> Point { - { x: self.x * other.x, y: self.y * other.y } +impl Number for Point with op_add(p1, p2) { + { x: p1.x + p2.x, y: p1.y + p2.y } } -``` - -Methods of a trait can be called directly via `Trait::method`. MoonBit will infer the type of `Self` and check if `Self` indeed implements `Trait`, for example: -```moonbit live -fn main { - println(Show::to_string(42)) - println(Compare::compare(1.0, 2.5)) +impl Number for Point with op_mul(p1, p2) { + { x: p1.x * p2.x, y: p1.y * p2.y } } ``` @@ -1991,45 +2018,56 @@ trait Default { } ``` -## Access control of methods and direct implementation of traits - -To make the trait system coherent (i.e. there is a globally unique implementation for every `Type: Trait` pair), and prevent third-party packages from modifying behavior of existing programs by accident, _only the package that defines a type can define methods for it_. So one cannot define new methods or override old methods for builtin and foreign types. - -However, it is often useful to implement new traits for an existing type. So MoonBit provides a mechanism to directly implement a trait, defined using the syntax `impl Trait for Type with method_name(...) { ... }`. Type annotations can be omitted from `impl`, because MoonBit can infer the correct types from the trait's signature. For example, to implement a new trait `ToMyBinaryProtocol` for builtin types, one can (and must) use `impl`: +### Involke trait methods directly +Methods of a trait can be called directly via `Trait::method`. MoonBit will infer the type of `Self` and check if `Self` indeed implements `Trait`, for example: -```moonbit -trait ToMyBinaryProtocol { - to_my_binary_protocol(Self, Buffer) -> Unit +```moonbit live +fn main { + println(Show::to_string(42)) + println(Compare::compare(1.0, 2.5)) } +``` -impl ToMyBinaryProtocol for Int with to_my_binary_protocol(x, b) { ... } - -impl ToMyBinaryProtocol for UInt with to_my_binary_protocol(x, b) { ... } +Trait implementations can also be involked via dot syntax, with the following restrictions: -impl[X : ToMyBinaryProtocol] ToMyBinaryProtocol for Array[X] with to_my_binary_protocol( - arr, - b -) { - ... -} -``` +1. if a regular method is present, the regular method is always favored when using dot syntax +2. only trait implementations that are located in the package of the self type can be involked via dot syntax + - if there are multiple trait methods (from different traits) with the same name available, an ambiguity error is reported +3. if neither of the above two rules apply, trait `impl`s in current package will also be searched for dot syntax. + This allows extending a foreign type locally. + - these `impl`s can only be called via dot syntax locally, even if they are public. -When searching for the implementation of a trait, `impl`s have a higher priority, so they can be used to override ordinary methods with undesirable behavior. `impl`s can only be used to implement the specified trait. They cannot be called directly like ordinary methods. Furthermore, _only the package of the type or the package of the trait can define an implementation_. For example, only `@pkg1` and `@pkg2` are allowed to define `impl @pkg1.Trait for @pkg2.Type` for type `@pkg2.Type`. This restriction ensures that MoonBit's trait system is still coherent with the extra flexibility of `impl`s. +The above rules ensures that MoonBit's dot syntax enjoys good property while being flexible. +For example, adding a new dependency never break existing code with dot syntax due to ambiguity. +These rules also make name resolution of MoonBit extremely simple: +the method called via dot syntax must always come from current package or the package of the type! -To invoke an trait implementation directly, one can use the `Trait::method` syntax: +Here's an example of calling trait `impl` with dot syntax: -```moonbit live -trait MyTrait { - f(Self) -> Unit -} +```moonbit +struct MyType { ... } -impl MyTrait for Int with f(self) { println("Got Int \{self}!") } +impl Show for MyType with ... fn main { - MyTrait::f(42) + let x : MyType = ... + println(x.to_string()) // ok } ``` +## Access control of methods and trait implementations + +To make the trait system coherent (i.e. there is a globally unique implementation for every `Type: Trait` pair), +and prevent third-party packages from modifying behavior of existing programs by accident, +MoonBit employs the following restrictions on who can define methods/implement traits for types: + +- _only the package that defines a type can define methods for it_. So one cannot define new methods or override old methods for builtin and foreign types. +- _only the package of the type or the package of the trait can define an implementation_. + For example, only `@pkg1` and `@pkg2` are allowed to write `impl @pkg1.Trait for @pkg2.Type`. + +The second rule above allows one to add new functionality to a foreign type by defining a new trait and implementing it. +This makes MoonBit's trait & method system flexible while enjoying good coherence property. + ## Visibility of traits and sealed traits There are four visibility for traits, just like `struct` and `enum`: private, abstract, readonly and fully public. Private traits are declared with `priv trait`, and they are completely invisible from outside. diff --git a/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md b/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md index acee18ed..a12fe36e 100644 --- a/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md +++ b/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md @@ -1842,23 +1842,48 @@ trait I { 在接口声明中,`Self` 指代实现接口的那个类型。 -一个类型要实现某个接口,就要满足该接口中所有的方法。例如,下面的接口描述了一个能够比较元素是否相等的类型需要满足的方法: + +一个类型要实现某个接口,就要满足该接口中所有的方法。Trait 中的方法可以用 `impl Trait for Type with method_name(...)` 的形式实现,例如: ```moonbit -trait Eq { - op_equal(Self, Self) -> Bool +trait Show { + to_string(Self) -> String } + +struct MyType { ... } +impl Show for MyType with to_string(self) { ... } + +// 带类型参数的 trait 实现。 +// [X : Show] 表示类型参数 X 必须实现 Show,后面会详细介绍其含义 +impl[X : Show] Show for Array[X] with to_string(self) { ... } ``` -接口无需显式实现,具有所需方法的类型会自动实现接口。考虑以下接口: +`impl` 上可以省略类型标注:MoonBit 会从 `Trait::method` 的签名和 `impl Trait for Type` 中的 `Type` 自动推断出实现的类型。 ```moonbit -trait Show { - to_string(Self) -> String +trait I { + f(Self) -> Unit + f_twice(Self) -> Unit +} + +impl I with f_twice(self) { + self.f() + self.f() } ``` -内置类型如 `Int` 和 `Double` 会自动实现这个接口。 +接口 `I` 的实现者无需为 `f_twice` 提供实现,只需要实现方法 `f` 即可实现接口 `I`。但如果有需要,实现者永远可以用显式的 `impl I for Type with f_twice` 声明来覆盖掉默认实现。 + +如果在尝试寻找某个接口中的方法的实现时,没有找到任何显式的 `impl` 声明,MoonBit 会尝试用目标类型的普通方法作为实现。 +这允许一个类型隐式地实现接口,从而让两个包可以在互不依赖的情况下模块化地一起工作。 +例如,内建的数字类型 `Int`、`Double` 等会自动实现下面的接口: + +```moonbit +trait Number { + op_add(Self, Self) -> Self + op_mul(Self, Self) -> Self +} +``` 在声明泛型函数时,类型参数可以用它们应该实现的接口作为注解。 如此便能定义只对某些类型可用的泛型函数。例如: @@ -1898,22 +1923,12 @@ struct Point { y: Int } derive(Show) -fn op_add(self: Point, other: Point) -> Point { - { x: self.x + other.x, y: self.y + other.y } +impl Number for Point with op_add(p1, p2) { + { x: p1.x + p2.x, y: p1.y + p2.y } } -fn op_mul(self: Point, other: Point) -> Point { - { x: self.x * other.x, y: self.y * other.y } -} -``` - -接口中的方法可以用 `Trait::method` 的语法来直接调用。MoonBit 会推导 `Self` 的具体类型, -并检查 `Self` 是否实现了 `Trait`: - -```moonbit -fn main { - println(Show::to_string(42)) - println(Compare::compare(1.0, 2.5)) +impl Number for Point with op_mul(p1, p2) { + { x: p1.x * p2.x, y: p1.y * p2.y } } ``` @@ -1944,55 +1959,50 @@ trait Default { } ``` -## 方法的访问权限控制、直接实现接口 - -为了使 MoonBit 的接口系统具有一致性(coherence,即任何 `Type: Trait` 的组合都有全局唯一的实现), -防止第三方包意外地修改现有程序的行为,**只有类型所在的包能为它定义方法**。 -所以用户无法为内建类型或来自第三方包的类型定义方法。 - -然而,我们有时也会需要给一个现有类型实现新的接口,因此,MoonBit 允许不定义方法直接实现一个接口。 -这种接口实现的语法是 `impl Trait for Type with method_name(...) { ... }`。 -MoonBit 可以根据接口的签名自动推导出实现的参数和返回值的类型,因此实现不强制要求标注类型。 -例如,假设要为内建类型实现一个新的接口 `ToMyBinaryProtocol`,就可以(且必须)使用 `impl`: +### 直接调用接口中的方法 +接口中的方法可以用 `Trait::method` 的语法来直接调用。MoonBit 会推导 `Self` 的具体类型, +并检查 `Self` 是否实现了 `Trait`: ```moonbit -trait ToMyBinaryProtocol { - to_my_binary_protocol(Self, Buffer) -> Unit +fn main { + println(Show::to_string(42)) + println(Compare::compare(1.0, 2.5)) } +``` -impl ToMyBinaryProtocol for Int with to_my_binary_protocol(x, b) { ... } +此外,接口的实现也可以用 `.` 语法在实现接口的类型上调用,不过用 `.` 语法调用接口的实现需要满足一些限制: -impl ToMyBinaryProtocol for UInt with to_my_binary_protocol(x, b) { ... } +1. 如果目标类型有一个同名的普通方法,普通方法永远会被优先调用 +2. 只有定义在类型所在的包里的实现可以用 `.` 语法调用 + - 如果有多个(来自不同接口的)同名实现,用 `.` 调用会触发一个歧义错误 +3. 如果上述两条规则都没有找到任何实现,MoonBit 会搜索当前包内的接口实现。这允许局部地拓展一个来自外部的类型 + - 但这些实现即使是公开的,也只能在当前包里本地地调用。对外它们无法用 `.` 语法调用 -impl[X : ToMyBinaryProtocol] ToMyBinaryProtocol for Array[X] with to_my_binary_protocol( - arr, - b -) { - ... -} -``` +上述规则保证了 MoonBit 的 `.` 语法在灵活的同时具有良好的性质。例如,添加一个新的依赖永远不会导致现有的代码由于 `.` 语法的歧义而报错。这些规则还使得 MoonBit 的名字解析规则非常简单:用 `.` 调用的方法一定来自当前包或目标类型所属的包。 -在搜索某个接口的实现时,`impl` 比普通方法有更高的优先级, -因此 `impl` 还可以用来覆盖掉行为不能满足要求的现有方法。 -`impl` 只能被用于实现指定的接口,不能像普通的方法一样被直接调用。 -此外,**只有类型或接口所在的包可以定义 `impl`**。 -例如,只有 `@pkg1` 和 `@pkg2` 能定义 `impl @pkg1.Trait for @pkg2.Type with ...`。 -这一限制使得 MoonBit 的接口系统在加入 `impl` 后,仍能保持一致。 +下面是一个用 `.` 语法调用接口实现的例子: -如果需要直接调用一个实现,可以使用 `Trait::method` 语法。例如: - -```moonbit live -trait MyTrait { - f(Self) -> Unit -} +```moonbit +struct MyType { ... } -impl MyTrait for Int with f(self) { println("Got Int \{self}!") } +impl Show for MyType with ... fn main { - MyTrait::f(42) + let x : MyType = ... + println(x.to_string()) // ok } ``` +## 方法的访问权限控制、直接实现接口 + +为了使 MoonBit 的接口系统具有一致性(coherence,即任何 `Type: Trait` 的组合都有全局唯一的实现), +防止第三方包意外地修改现有程序的行为,MoonBit 对 “谁能给类型添加新的方法/接口实现” 有如下限制: + +- **只有类型所在的包能为它定义方法**。所以用户无法为内建类型或来自第三方包的类型定义方法。 +- **只有类型或接口所在的包可以定义 `impl`**。例如,只有 `@pkg1` 和 `@pkg2` 能定义 `impl @pkg1.Trait for @pkg2.Type` + +第二条规则允许用户通过定义新接口来拓展一个第三方类型的功能。这些规则使得 MoonBit 的接口系统在具有灵活表达能力的同时享受良好的一致性。 + ## 接口的可见性与封闭的接口 MoonBit 中,接口和类型一样有四种可见性:私有、抽象、只读和完全公开。 私有接口可以用 `priv trait` 声明,它们在外部是完全不可见的。 From b3a041721627860d9d388d7ac69b6e840839315e Mon Sep 17 00:00:00 2001 From: Guset0x0 Date: Tue, 26 Nov 2024 16:45:13 +0800 Subject: [PATCH 2/2] add doc for range pattern --- moonbit-docs/docs/README.md | 31 +++++++++++++++++++ .../current/README.md | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/moonbit-docs/docs/README.md b/moonbit-docs/docs/README.md index f964443d..bd8cb34f 100644 --- a/moonbit-docs/docs/README.md +++ b/moonbit-docs/docs/README.md @@ -1274,6 +1274,37 @@ match expr { } ``` +### Range Pattern +For builtin integer types and `Char`, MoonBit allows matching whether the value falls in a specific range. +Range patterns have the form `a.. Int { + match x { + _.. -1 + Zero => 0 + 1..<_ => 1 + } +} + +fn classify_char(c : Char) -> String { + match c { + 'a'..='z' => "lowercase" + 'A'..='Z' => "uppercase" + '0'..='9' => "digit" + _ => "other" + } +} +``` + ### Map Pattern MoonBit allows convenient matching on map-like data structures. diff --git a/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md b/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md index a12fe36e..006f99e9 100644 --- a/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md +++ b/moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md @@ -1234,6 +1234,37 @@ match expr { } ``` +### 范围模式匹配 +对于内建的整数类型和字符类型 `Char`,MoonBit 允许匹配一个值是否落在某个范围内。 +范围模式的语法是 `a.. Int { + match x { + _.. -1 + Zero => 0 + 1..<_ => 1 + } +} + +fn classify_char(c : Char) -> String { + match c { + 'a'..='z' => "lowercase" + 'A'..='Z' => "uppercase" + '0'..='9' => "digit" + _ => "other" + } +} +``` + ### 键值对模式匹配 MoonBit 允许模式匹配字典等具有键值对结构的数据结构。在一个字典模式里,`key : value` 语法可以用来匹配 `key` 存在的情况,`value` 会被用于匹配 `key` 在键值对中的值。`key? : value` 语法无论 `key` 是否存在都能匹配,`value` 会被用于匹配 `map[key]` 的值(一个 `Option` 类型的值):