From f7fbed87f8dd0ad19f1b3f2360ff556aca0abfe4 Mon Sep 17 00:00:00 2001 From: Guset0x0 Date: Fri, 15 Nov 2024 18:41:10 +0800 Subject: [PATCH] update doc for visibility of types and traits --- moonbit-docs/docs/README.md | 106 ++++++++++------- .../current/README.md | 109 ++++++++++-------- 2 files changed, 129 insertions(+), 86 deletions(-) diff --git a/moonbit-docs/docs/README.md b/moonbit-docs/docs/README.md index a2d6d0d1..20e0294b 100644 --- a/moonbit-docs/docs/README.md +++ b/moonbit-docs/docs/README.md @@ -1695,50 +1695,24 @@ fn reduce[S, T](self: List[S], op: (T, S) -> T, init: T) -> T { ## Access Control -By default, all function definitions and variable bindings are _invisible_ to other packages; types without modifiers are abstract data types, whose name is exported but the internals are invisible. This design prevents unintended exposure of implementation details. You can use the `pub` modifier before `type`/`enum`/`struct`/`let` or top-level function to make them fully visible, or put `priv` before `type`/`enum`/`struct` to make it fully invisible to other packages. You can also use `pub` or `priv` before field names to obtain finer-grained access control. However, it is important to note that: +By default, all function definitions and variable bindings are _invisible_ to other packages. +You can use the `pub` modifier before toplevel `let`/`fn` to make them public. -- Struct fields cannot be defined as `pub` within an abstract or private struct since it makes no sense. -- Enum constructors do not have individual visibility so you cannot use `pub` or `priv` before them. +There are four different kinds of visibility for types in MoonBit: -```moonbit -struct R1 { // abstract data type by default - x: Int // implicitly private field - pub y: Int // ERROR: `pub` field found in an abstract type! - priv z: Int // WARNING: `priv` is redundant! -} +- private type, declared with `priv`, completely invisible to the outside world +- abstract type, which is the default visibility for types. Only the name of an abstract type is visible outside, the internal representation of the type is hidden +- readonly types, declared with `pub(readonly)`. The internal representation of readonly types are visible outside, +but users can only read the values of these types from outside, construction and mutation are not allowed +- fully public types, declared with `pub(all)`. The outside world can freely construct, modify and read values of these types -pub struct R2 { // explicitly public struct - x: Int // implicitly public field - pub y: Int // WARNING: `pub` is redundant! - priv z: Int // explicitly private field -} +Currently, the semantic of `pub` is `pub(all)`. But in the future, the meaning of `pub` will be ported to `pub(readonly)`. +In addition to the visibility of the type itself, the fields of a public `struct` can be annotated with `priv`, +which will hide the field from the outside world completely. +Note that `struct`s with private fields cannot be constructed directly outside, +but you can update the public fields using the functional struct update syntax. -priv struct R3 { // explicitly private struct - x: Int // implicitly private field - pub y: Int // ERROR: `pub` field found in a private type! - priv z: Int // WARNING: `priv` is redundant! -} - -enum T1 { // abstract data type by default - A(Int) // implicitly private variant - pub B(Int) // ERROR: no individual visibility! - priv C(Int) // ERROR: no individual visibility! -} - -pub enum T2 { // explicitly public enum - A(Int) // implicitly public variant - pub B(Int) // ERROR: no individual visibility! - priv C(Int) // ERROR: no individual visibility! -} - -priv enum T3 { // explicitly private enum - A(Int) // implicitly private variant - pub B(Int) // ERROR: no individual visibility! - priv C(Int) // ERROR: no individual visibility! -} -``` - -Another useful feature supported in MoonBit is `pub(readonly)` types, which are inspired by [private types](https://v2.ocaml.org/manual/privatetypes.html) in OCaml. In short, values of `pub(readonly)` types can be destructed by pattern matching and the dot syntax, but cannot be constructed or mutated in other packages. Note that there is no restriction within the same package where `pub(readonly)` types are defined. +Readonly types is a very useful feature, inspired by [private types](https://v2.ocaml.org/manual/privatetypes.html) in OCaml. In short, values of `pub(readonly)` types can be destructed by pattern matching and the dot syntax, but cannot be constructed or mutated in other packages. Note that there is no restriction within the same package where `pub(readonly)` types are defined. ```moonbit // Package A @@ -2056,6 +2030,58 @@ fn main { } ``` +## 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. +Abstract trait is the default visibility. Only the name of the trait is visible from outside, and the methods in the trait are not exposed. +Readonly traits are declared with `pub(readonly) trait`, their methods can be involked from outside, but only the current package can add new implementation for readonly traits. +Finally, fully public traits are declared with `pub(open) trait`, they are open to new implementations outside current package, and their methods can be freely used. +Currently, `pub trait` defaults to `pub(open) trait`. But in the future, the semantic of `pub trait` will be ported to `pub(readonly)`. + +Abstract and readonly traits are sealed, because only the package defining the trait can implement them. +Implementing a sealed (abstract or readonly) trait outside its package result in compiler error. +If you are the owner of a sealed trait, and you want to make some implementation available to users of your package, +make sure there is at least one declaration of the form `impl Trait for Type with ...` in your package. +Implementations with only regular method and default implementations will not be available outside. + +Here's an example of abstract trait: + +```moonbit +trait Number { + op_add(Self, Self) -> Self + op_sub(Self, Self) -> Self +} + +fn add[N : Number](x : X, y: X) -> X { + Number::op_add(x, y) +} + +fn sub[N : Number](x : X, y: X) -> X { + Number::op_sub(x, y) +} + +impl Number for Int with op_add(x, y) { x + y } +impl Number for Int with op_sub(x, y) { x - y } + +impl Number for Double with op_add(x, y) { x + y } +impl Number for Double with op_sub(x, y) { x - y } +``` + +From outside this package, users can only see the following: + +```moonbit +trait Number + +fn op_add[N : Number](x : N, y : N) -> N +fn op_sub[N : Number](x : N, y : N) -> N + +impl Number for Int +impl Number for Double +``` + +The author of `Number` can make use of the fact that only `Int` and `Double` can ever implement `Number`, +because new implementations are not allowed outside. + ## Automatically derive builtin traits MoonBit can automatically derive implementations for some builtin traits: 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 9c37b1d3..acee18ed 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 @@ -1637,55 +1637,20 @@ fn reduce[S, T](self: List[S], op: (T, S) -> T, init: T) -> T { ## 访问控制 默认情况下,所有函数定义和变量绑定对其他包都是 _不可见_ 的; -没有修饰符的类型是抽象数据类型,其名称被导出,但内部是不可见的。 -这种设计防止了意外暴露实现细节。 -您可以在 `type`/`fn`/`let` 前使用 `pub` 修饰符使其完全可见,或在 `type` -前使用 `priv` 修饰符使其对其他包完全不可见。 -您还可以在字段名前使用 `pub` 或 `priv` 获得更细粒度的访问控制。 -但是,请注意: +您可以在 `fn`/`let` 前使用 `pub` 修饰符使其完全可见。 +类型有四种不同的可见性: -- 在抽象或私有结构体内,所有字段都不能被定义为 `pub`,因为这样没有意义。 -- 枚举类型的构造器没有单独的可见性,所以不能在它们前面使用 `pub` 或 `priv` +- 私有类型,用 `priv` 修饰,对外完全不可见 +- 抽象类型,这是默认的可见性,无需额外修饰。对外只有名字可见,类型的内部表示不可见。这种设计防止了意外暴露实现细节 +- 只读类型,用 `pub(readonly)` 修饰,其内部表示对外可见,但外部只能读取类型的值,不能构造或修改 +- 公开类型,用 `pub(all)` 修饰,外部可以自由构造、修改、读取这些类型的值 -```moonbit -struct R1 { // 默认为抽象数据类型 - x: Int // 隐式的私有字段 - pub y: Int // ERROR: 在抽象类型中找到了 `pub` 字段! - priv z: Int // WARNING: `priv` 是多余的! -} +目前,`pub` 修饰符的语义是 `pub(all)`,但未来 `pub` 的语义会调整为 `pub(readonly)`。 +除了类型自身的可见性,一个 `pub(readonly)` 或 `pub(all)` 的结构体的字段可以额外用 `priv` 修饰, +这样能对外完全隐藏这一字段的存在。 +注意含有私有字段的结构体在外部无法直接构造,但可以用结构体更新语法更新公开的字段。 -pub struct R2 { // 显式的公共结构 - x: Int // 隐式的公共字段 - pub y: Int // WARNING: `pub` 是多余的! - priv z: Int // 显式的私有字段 -} - -priv struct R3 { // 显式的私有结构 - x: Int // 隐式的私有字段 - pub y: Int // ERROR: `pub` 字段出现在了私有类型中! - priv z: Int // WARNING: `priv` 是多余的! -} - -enum T1 { // 默认为抽象数据类型 - A(Int) // 隐式的私有变体 - pub B(Int) // ERROR: 无独立可见性! - priv C(Int) // ERROR: 无独立可见性! -} - -pub enum T2 { // 显式的公共枚举 - A(Int) // 隐式的公共变体 - pub B(Int) // ERROR: 无独立可见性! - priv C(Int) // ERROR: 无独立可见性! -} - -priv enum T3 { // 显式的私有枚举 - A(Int) // 隐式的私有变体 - pub B(Int) // ERROR: 无独立可见性! - priv C(Int) // ERROR: 无独立可见性! -} -``` - -MoonBit 中另一个有用的特性是 `pub(readonly)` 类型,其受到了 OCaml [private types](https://v2.ocaml.org/manual/privatetypes.html)的启发。简而言之,`pub(readonly)` 类型的值可以使用模式匹配或点语法析构,但在其他包中,不能被构造或改变。注意到在 `pub(readonly)` 类型定义的同一个包中,它没有任何限制。 +只读类型是一个十分实用的特性,其受到了 OCaml [private types](https://v2.ocaml.org/manual/privatetypes.html)的启发。简而言之,`pub(readonly)` 类型的值可以使用模式匹配或点语法析构,但在其他包中,不能被构造或改变。注意到在 `pub(readonly)` 类型定义的同一个包中,它没有任何限制。 ```moonbit // Package A @@ -2028,6 +1993,58 @@ fn main { } ``` +## 接口的可见性与封闭的接口 +MoonBit 中,接口和类型一样有四种可见性:私有、抽象、只读和完全公开。 +私有接口可以用 `priv trait` 声明,它们在外部是完全不可见的。 +抽象接口是接口的默认可见性。只有接口的名字对外可见,接口中的方法对外是不可见的。 +只读接口可以用 `pub(readonly) trait` 声明,外部可以调用这个接口中的方法,但只有定义这个接口的包可以实现这个接口,外部不能添加新的实现。 +最后,完全公开的接口可以用 `pub(open) trait` 声明,外部可以给这种接口添加新的实现、也可以自由调用其中的方法。 +目前,`pub trait` 默认的语义是 `pub(open) trait`。但未来 `pub trait` 的语义会迁移至 `pub(readonly) trait`。 + +抽象和只读的接口是 **封闭** 的,因为只有定义接口的包可以为它们添加实现。 +如果尝试在外部实现这些接口,就会产生编译错误。 +如果你是一个封闭接口的所有者,并希望你的用户能够使用你提供的、这些接口的实现, +那么一定要保证你的包中至少有一处形如 `impl Trait for Type with ...` 的显式声明。 +也就是说,只有普通方法和默认实现的情况下,封闭接口的实现在外部是不可用的。 + +下面是一个抽象接口的例子: + +```moonbit +trait Number { + op_add(Self, Self) -> Self + op_sub(Self, Self) -> Self +} + +fn add[N : Number](x : X, y: X) -> X { + Number::op_add(x, y) +} + +fn sub[N : Number](x : X, y: X) -> X { + Number::op_sub(x, y) +} + +impl Number for Int with op_add(x, y) { x + y } +impl Number for Int with op_sub(x, y) { x - y } + +impl Number for Double with op_add(x, y) { x + y } +impl Number for Double with op_sub(x, y) { x - y } +``` + +在当前包外,用户只能看见下面的内容: + +```moonbit +trait Number + +fn op_add[N : Number](x : N, y : N) -> N +fn op_sub[N : Number](x : N, y : N) -> N + +impl Number for Int +impl Number for Double +``` + +由于外部不能给 `Number` 添加新的实现。因此,只有 `Int` 和 `Double` 能够实现 `Number`。 +`Number` 的作者可以在编写程序时利用上这一事实。 + ## 自动实现内建接口 Moonbit 可以自动生成一些内建接口的实现: