方法和特征#

方法系统#

MoonBit 支持方法的方式与传统的面向对象语言不同。MoonBit 中的方法只是与类型构造器关联的顶级函数。可以使用语法 fn TypeName::method_name(...) -> ... 定义方法:

enum List[X] {
  Nil
  Cons(X, List[X])
}

fn List::concat[X](xs : List[List[X]]) -> List[X] {
  ...
}

一个简写是,当函数的第一个参数命名为 self 时,MoonBit 会自动将该函数定义为 self 类型的方法:

fn List::map[X, Y](xs : List[X], f : (X) -> Y) -> List[Y] {
  ...
}

等价于

fn map[X, Y](self : List[X], f : (X) -> Y) -> List[Y] {
  ...
}

方法只是由类型构造器拥有的常规函数。因此,当没有歧义时,可以直接使用常规函数调用语法调用方法:

let xs : List[List[_]] = { ... }
let ys = concat(xs)

与常规函数不同,方法支持重载:不同类型可以定义相同名称的方法。如果作用域中有多个相同名称的方法(但是针对不同类型),仍然可以通过显式添加 TypeName:: 前缀来调用它们:

struct T1 {
  x1 : Int
}

fn T1::default() -> T1 {
  { x1: 0 }
}

struct T2 {
  x2 : Int
}

fn T2::default() -> T2 {
  { x2: 0 }
}

test {
  // default() : T1::default() ? T2::default()?
  let t1 = T1::default()
  let t2 = T2::default()

}

当方法的第一个参数也是它所属的类型时,可以使用点语法 x.method(...) 调用方法。MoonBit 根据 x 的类型自动找到正确的方法,无需编写方法的类型名称甚至包名称:

pub(all) enum List[X] {
  Nil
  Cons(X, List[X])
}

pub fn List::concat[X](xs : List[List[X]]) -> List[X] {
  ...
}
以别名 list 使用包#
fn f() -> Unit {
  let xs : @list.List[@list.List[Unit]] = Nil
  let _ = xs.concat()
  let _ = @list.List::concat(xs)
  let _ = @list.concat(xs)

}

只有在 @list 中没有歧义时,才能像高亮显示的行那样书写。

运算符重载#

MoonBit 支持通过方法对内置运算符进行运算符重载。与运算符 <op> 对应的方法名是 op_<op>。例如:

struct T {
  x : Int
}

fn op_add(self : T, other : T) -> T {
  { x: self.x + other.x }
}

test {
  let a = { x: 0 }
  let b = { x: 2 }
  assert_eq!((a + b).x, 2)
}

关于 op_getop_set 的另一个例子:

struct Coord {
  mut x : Int
  mut y : Int
} derive(Show)

fn op_get(self : Coord, key : String) -> Int {
  match key {
    "x" => self.x
    "y" => self.y
  }
}

fn op_set(self : Coord, key : String, val : Int) -> Unit {
  match key {
    "x" => self.x = val
    "y" => self.y = val
  }
}
fn main {
  let c = { x: 1, y: 2 }
  println(c)
  println(c["y"])
  c["x"] = 23
  println(c)
  println(c["x"])
}
输出#
{x: 1, y: 2}
2
{x: 23, y: 2}
23

目前,可以重载以下运算符:

运算符名称

方法名称

+

op_add

-

op_sub

*

op_mul

/

op_div

%

op_mod

=

op_equal

<<

op_shl

>>

op_shr

-(一元)

op_neg

_[_](获取项)

op_get

_[_] = _(设置项)

op_set

_[_:_](视图)

op_as_view

&

land

|

lor

^

lxor

<<

op_shl

>>

op_shr

通过实现 op_as_view 方法,可以为用户定义的类型创建视图。以下是一个例子:

type DataView String

struct Data {}

fn Data::op_as_view(_self : Data, start~ : Int = 0, end? : Int) -> DataView {
  "[\{start}, \{end.or(100)})"
}

test {
  let data = Data::{  }
  inspect!(data[:]._, content="[0, 100)")
  inspect!(data[2:]._, content="[2, 100)")
  inspect!(data[:5]._, content="[0, 5)")
  inspect!(data[2:5]._, content="[2, 5)")
}

Trait(特征)系统#

MoonBit 具有用于重载/特殊多态的结构特征系统。特征声明一系列操作,当类型想要实现特征时,必须提供这些操作。特征可以如下声明:

trait I {
  method_(Int) -> Int
  method_with_label(Int, label~: Int) -> Int
  //! method_with_label(Int, label?: Int) -> Int
}

在特征定义的主体中,使用特殊类型 Self 来引用实现特征的类型。

扩展特征#

特征(子特征)可以依赖于其他特征(超特征),例如:

trait Position {
  pos(Self) -> (Int, Int)
}
trait Draw {
  draw(Self) -> Unit
}

trait Object : Position + Draw {}

要实现子特征,必须实现子特征和所有超特征中定义的方法。

实现特征#

要实现特征,类型必须提供特征所需的所有方法。

这使得类型可以隐式满足特征,从而允许不同的包在不看到或依赖于彼此的情况下运行。例如,内置数字类型(如 IntDouble)自动满足了以下的特征:

trait Number {
  op_add(Self, Self) -> Self
  op_mul(Self, Self) -> Self
}

显式实现特征方法可以通过语法 impl Trait for Type with method_name(...) { ... } 提供,例如:

trait MyShow {
  to_string(Self) -> String
}

struct MyType {}

impl MyShow for MyType with to_string(self) { ... }

struct MyContainer[T] {}

// 使用类型参数实现特征。
// `[X : Show]` 意味着类型参数 `X` 必须实现 `Show`,
// 我们将稍后介绍。
impl[X : MyShow] MyShow for MyContainer[X] with to_string(self) { ... }

impl 实现的类型注释可以省略:MoonBit 将根据 Trait::method 的签名和 self 类型自动推断类型。

特征的作者还可以为特征中的某些方法定义默认实现,例如:

trait J {
  f(Self) -> Unit
  f_twice(Self) -> Unit
}

impl J with f_twice(self) {
  self.f()
  self.f()
}

I 的类型实现特征时不必为 f_twice 提供实现:要实现 I,只有 f 是必要的。如果需要,他们总是可以显式地用 impl I for Type with f_twice 覆盖默认实现。

如果找不到显式的 impl 或默认实现,特征方法解析将回退到常规方法。

使用特征#

在声明泛型函数时,可以使用特征注释类型参数,来定义受约束的泛型函数。例如:

fn square[N : Number](x : N) -> N {
  x * x // <=> x.op_mul(x)
}

如果没有 Number 要求,square 中的表达式 x * x 将产生方法/运算符未找到的错误。现在,函数 square 可以使用任何实现 Number 的类型调用,例如:

struct Point {
  x : Int
  y : Int
} derive(Eq, Show)

impl Number for Point with op_add(self, other) {
  { x: self.x + other.x, y: self.y + other.y }
}

impl Number for Point with op_mul(self, other) {
  { x: self.x * other.x, y: self.y * other.y }
}

test {
  assert_eq!(square(2), 4)
  assert_eq!(square(1.5), 2.25)
  assert_eq!(square(Point::{ x: 2, y: 3 }), { x: 4, y: 9 })
}

直接调用特征方法#

可以通过 Trait::method 直接调用特征的方法。MoonBit 将推断 Self 的类型,并检查 Self 是否确实实现了 Trait,例如:

test {
  assert_eq!(Show::to_string(42), "42")
  assert_eq!(Compare::compare(1.0, 2.5), -1)
}

特征实现也可以通过点语法调用,但有以下限制:

  1. 如果存在常规方法,使用点语法时总是优先选择常规方法

  2. 只有位于 self 类型的包中的特征实现才能通过点语法调用

    • 如果有多个具有相同名称的特征方法(来自不同的特征)可用,将报告歧义错误

  3. 如果上述两条规则都不适用,还将在当前包中搜索特征 impl 以进行点语法。这允许在本地扩展外部类型。

    • 这些 impl 只能在本地通过点语法调用,即使它们是公共的。

上述规则确保了 MoonBit 的点语法具有良好的特性,同时也具有灵活性。例如,由于歧义,添加新依赖关系永远不会破坏现有的点语法代码。这些规则还使 MoonBit 的名称解析非常简单:通过点语法调用的方法必须始终来自当前包或类型的包!

以下是使用点语法调用特征 impl 的示例:

struct MyCustomType {}

impl Show for MyCustomType with output(self, logger) { ... }

fn f() -> Unit {
  let x = MyCustomType::{  }
  let _ = x.to_string()

}

特征对象#

MoonBit 支持通过特征对象实现运行时多态。如果 t 是类型 T,它实现了特征 I,可以通过 t as &I 将实现 IT 的方法与 t 一起打包到运行时对象中。特征对象擦除了值的具体类型,因此可以将从不同具体类型创建的对象放入相同的数据结构并统一处理:

trait Animal {
  speak(Self) -> String
}

type Duck String

fn Duck::make(name : String) -> Duck {
  Duck(name)
}

fn speak(self : Duck) -> String {
  "\{self._}: quack!"
}

type Fox String

fn Fox::make(name : String) -> Fox {
  Fox(name)
}

fn Fox::speak(_self : Fox) -> String {
  "What does the fox say?"
}

test {
  let duck1 = Duck::make("duck1")
  let duck2 = Duck::make("duck2")
  let fox1 = Fox::make("fox1")
  let animals : Array[&Animal] = [
    duck1 as &Animal,
    duck2 as &Animal,
    fox1 as &Animal,
  ]
  inspect!(
    animals.map(fn(animal) { animal.speak() }),
    content=
      #|["duck1: quack!", "duck2: quack!", "What does the fox say?"]
    ,
  )
}

并非所有特征都可以用于创建对象。“对象安全” 特征的方法必须满足以下条件:

  • Self 必须是方法的第一个参数

  • 方法的类型中只能出现一个 Self (即第一个参数)

用户可以为特征对象定义新方法,就像为结构体和枚举定义新方法一样:

trait Logger {
  write_string(Self, String) -> Unit
}

trait CanLog {
  log(Self, &Logger) -> Unit
}

fn &Logger::write_object[Obj : CanLog](self : &Logger, obj : Obj) -> Unit {
  obj.log(self)
}

// 使用新的方法来简化代码
impl[A : CanLog, B : CanLog] CanLog for (A, B) with log(self, logger) {
  let (a, b) = self
  logger
  ..write_string("(")
  ..write_object(a)
  ..write_string(", ")
  ..write_object(b)
  .write_string(")")
}

内建特征#

MoonBit 提供了以下有用的内建特征:

trait Eq {
  op_equal(Self, Self) -> Bool
}

trait Compare : Eq {
  // `0` 代表相等,`-1` 代表小于,`1` 代表大于
  compare(Self, Self) -> Int
}

trait Hash {
  hash_combine(Self, Hasher) -> Unit // 待实现
  hash(Self) -> Int // 有默认实现
}

trait Show {
  output(Self, Logger) -> Unit // 待实现
  to_string(Self) -> String // 有默认实现
}

trait Default {
  default() -> Self
}

派生内建特征#

MoonBit 可以自动为一些内建特征派生实现:

struct T {
  x : Int
  y : Int
} derive(Eq, Compare, Show, Default)

test {
  let t1 = T::default()
  let t2 = T::{ x: 1, y: 1 }
  inspect!(t1, content="{x: 0, y: 0}")
  inspect!(t2, content="{x: 1, y: 1}")
  assert_not_eq!(t1, t2)
  assert_true!(t1 < t2)
}

参见 派生 了解有关派生特征的更多信息。