Skip to content

Commit

Permalink
update & refactor document for traits: we can now call trait impl wit…
Browse files Browse the repository at this point in the history
…h dot syntax
  • Loading branch information
Guest0x0 committed Nov 26, 2024
1 parent f7fbed8 commit 622bcb0
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 99 deletions.
124 changes: 81 additions & 43 deletions moonbit-docs/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
```

Implementators 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:

Expand All @@ -1919,7 +1955,7 @@ trait Number {
}
fn square[N: Number](x: N) -> N {
x * x
x * x // same as `x.op_mul(x)`
}
```

Expand All @@ -1938,29 +1974,20 @@ trait Number {
}
fn square[N: Number](x: N) -> N {
x * x
x * x // same as `x.op_mul(x)`
}
struct Point {
x: Int
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 }
}
```

Expand Down Expand Up @@ -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.
Expand Down
122 changes: 66 additions & 56 deletions moonbit-docs/i18n/zh/docusaurus-plugin-content-docs/current/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```

在声明泛型函数时,类型参数可以用它们应该实现的接口作为注解。
如此便能定义只对某些类型可用的泛型函数。例如:
Expand Down Expand Up @@ -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 }
}
```

Expand Down Expand Up @@ -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` 声明,它们在外部是完全不可见的。
Expand Down

0 comments on commit 622bcb0

Please sign in to comment.