派生内建特征#

MoonBit 支持从类型定义中自动派生一些内建特征。

要派生特征 T,需要类型中使用的所有字段都实现了 T。例如,为结构体 struct A { x: T1; y: T2 } 派生 Show 需要 T1: ShowT2: Show

输出#

derive(Show) 将为类型生成一个漂亮的打印方法。派生的格式类似于代码中构造类型的方式。

struct MyStruct {
  x : Int
  y : Int
} derive(Show)

test "derive show struct" {
  let p = MyStruct::{ x: 1, y: 2 }
  assert_eq(Show::to_string(p), "{x: 1, y: 2}")
}
enum MyEnum {
  Case1(Int)
  Case2(label~ : String)
  Case3
} derive(Show)

test "derive show enum" {
  assert_eq(Show::to_string(MyEnum::Case1(42)), "Case1(42)")
  assert_eq(
    Show::to_string(MyEnum::Case2(label="hello")),
    "Case2(label=\"hello\")",
  )
  assert_eq(Show::to_string(MyEnum::Case3), "Case3")
}

相等和比较#

derive(Eq)derive(Compare) 将为测试相等性和比较生成相应的方法。字段按照它们的定义顺序进行比较。对于枚举,构造器的顺序按照定义的顺序升序。

struct DeriveEqCompare {
  x : Int
  y : Int
} derive(Eq, Compare)

test "derive eq_compare struct" {
  let p1 = DeriveEqCompare::{ x: 1, y: 2 }
  let p2 = DeriveEqCompare::{ x: 2, y: 1 }
  let p3 = DeriveEqCompare::{ x: 1, y: 2 }
  let p4 = DeriveEqCompare::{ x: 1, y: 3 }

  // Eq
  assert_eq(p1 == p2, false)
  assert_eq(p1 == p3, true)
  assert_eq(p1 == p4, false)
  assert_eq(p1 != p2, true)
  assert_eq(p1 != p3, false)
  assert_eq(p1 != p4, true)

  // Compare
  assert_eq(p1 < p2, true)
  assert_eq(p1 < p3, false)
  assert_eq(p1 < p4, true)
  assert_eq(p1 > p2, false)
  assert_eq(p1 > p3, false)
  assert_eq(p1 > p4, false)
  assert_eq(p1 <= p2, true)
  assert_eq(p1 >= p2, false)
}
enum DeriveEqCompareEnum {
  Case1(Int)
  Case2(label~ : String)
  Case3
} derive(Eq, Compare)

test "derive eq_compare enum" {
  let p1 = DeriveEqCompareEnum::Case1(42)
  let p2 = DeriveEqCompareEnum::Case1(43)
  let p3 = DeriveEqCompareEnum::Case1(42)
  let p4 = DeriveEqCompareEnum::Case2(label="hello")
  let p5 = DeriveEqCompareEnum::Case2(label="world")
  let p6 = DeriveEqCompareEnum::Case2(label="hello")
  let p7 = DeriveEqCompareEnum::Case3

  // Eq
  assert_eq(p1 == p2, false)
  assert_eq(p1 == p3, true)
  assert_eq(p1 == p4, false)
  assert_eq(p1 != p2, true)
  assert_eq(p1 != p3, false)
  assert_eq(p1 != p4, true)

  // Compare
  assert_eq(p1 < p2, true) // 42 < 43
  assert_eq(p1 < p3, false)
  assert_eq(p1 < p4, true) // Case1 < Case2
  assert_eq(p4 < p5, true)
  assert_eq(p4 < p6, false)
  assert_eq(p4 < p7, true) // Case2 < Case3
}

默认值#

derive(Default) 将生成一个返回类型的默认值的方法。

对于结构体,默认值是所有字段设置为它们的默认值的结构体。

struct DeriveDefault {
  x : Int
  y : String?
} derive(Default, Eq, Show)

test "derive default struct" {
  let p = DeriveDefault::default()
  assert_eq(p, DeriveDefault::{ x: 0, y: None })
}

对于枚举,默认值是唯一没有参数的构造器。

enum DeriveDefaultEnum {
  Case1(Int)
  Case2(label~ : String)
  Case3
} derive(Default, Eq, Show)

test "derive default enum" {
  assert_eq(DeriveDefaultEnum::default(), DeriveDefaultEnum::Case3)
}

没有构造器或有多个没有参数的构造器的枚举不能派生 Default

enum CannotDerive1 {
    Case1(String)
    Case2(Int)
} derive(Default) // cannot find a constant constructor as default

enum CannotDerive2 {
    Case1
    Case2
} derive(Default) // Case1 and Case2 are both candidates as default constructor

哈希值#

derive(Hash) 将为类型生成一个哈希实现。这将允许类型在期望 Hash 实现的地方使用,例如 HashMapHashSet

struct DeriveHash {
  x : Int
  y : String?
} derive(Hash, Eq, Show)

test "derive hash struct" {
  let hs = @hashset.new()
  hs.add(DeriveHash::{ x: 123, y: None })
  hs.add(DeriveHash::{ x: 123, y: None })
  assert_eq(hs.size(), 1)
  hs.add(DeriveHash::{ x: 123, y: Some("456") })
  assert_eq(hs.size(), 2)
}

任意值#

derive(Arbitrary) 将生成给定类型的随机值。

从/到 Json#

derive(FromJson)derive(ToJson) 会自动派生可往返的方法实现,用于将类型序列化为 JSON 以及从 JSON 反序列化。该实现主要用于调试和以人类可读的格式存储类型。

struct JsonTest1 {
  x : Int
  y : Int
} derive(FromJson, ToJson, Eq, Show)

enum JsonTest2 {
  A(x~ : Int)
  B(x~ : Int, y~ : Int)
} derive(FromJson(style="legacy"), ToJson(style="legacy"), Eq, Show)

test "json basic" {
  let input = JsonTest1::{ x: 123, y: 456 }
  let expected : Json = { "x": 123, "y": 456 }
  assert_eq(input.to_json(), expected)
  assert_eq(@json.from_json(expected), input)
  let input = JsonTest2::A(x=123)
  let expected : Json = { "$tag": "A", "x": 123 }
  assert_eq(input.to_json(), expected)
  assert_eq(@json.from_json(expected), input)
}

这两个派生指令都接受一些参数来配置序列化和反序列化的确切行为。

警告

JSON 序列化参数的实际行为是不稳定的。

警告

JSON 派生参数仅用于粗粒度控制派生格式。如果你需要精确控制类型的布局,请考虑 直接实现这两个特征

我们最近弃用了大量高级布局调整参数。对于这种用法和将来对它们的使用,请手动实现这些特征。这些参数包括:reprcase_reprdefaultrename_all 等。

struct JsonTest3 {
  x : Int
  y : Int
} derive (
  FromJson(fields(x(rename="renamedX"))),
  ToJson(fields(x(rename="renamedX"))),
  Eq,
  Show,
)

enum JsonTest4 {
  A(x~ : Int)
  B(x~ : Int, y~ : Int)
} derive (
  FromJson(style="flat"),
  ToJson(style="flat"),
  Eq,
  Show,
)

test "json args" {
  let input = JsonTest3::{ x: 123, y: 456 }
  let expected : Json = { "renamedX": 123, "y": 456 }
  assert_eq(input.to_json(), expected)
  assert_eq(@json.from_json(expected), input)
  let input = JsonTest4::A(x=123)
  let expected : Json = [ "A", 123 ]
  assert_eq(input.to_json(), expected)
  assert_eq(@json.from_json(expected), input)
}

枚举样式#

目前有两种枚举序列化样式:legacyflat,用户必须使用 style 参数选择其中一种。

enum E {
  One
  Uniform(Int)
  Axes(x~: Int, y~: Int)
}

使用 derive(ToJson(style="legacy")),枚举格式化为:

E::One              => { "$tag": "One" }
E::Uniform(2)       => { "$tag": "Uniform", "0": 2 }
E::Axes(x=-1, y=1)  => { "$tag": "Axes", "x": -1, "y": 1 }

使用 derive(ToJson(style="flat")),枚举格式化为:

E::One              => "One"
E::Uniform(2)       => [ "Uniform", 2 ]
E::Axes(x=-1, y=1)  => [ "Axes", -1, 1 ]

容器参数#

  • rename_fieldsrename_cases(仅适用于枚举)分别批量重命名字段(对于枚举和结构体)和枚举构造器为给定格式。可用的参数有:

    • lowercase

    • UPPERCASE

    • camelCase

    • PascalCase

    • snake_case

    • SCREAMING_SNAKE_CASE

    • kebab-case

    • SCREAMING-KEBAB-CASE

    例如:rename_fields = "PascalCase" 用于名为 my_long_field_name 的字段将得到 MyLongFieldName

    重命名假定字段的名称为 snake_case,结构体/枚举构造器的名称为 PascalCase

  • cases(...)(仅枚举)控制枚举构造器的布局。

    警告

    这可能在未来会被构造器属性替换。

    例如,对于一个枚举

    enum E {
      A(...)
      B(...)
    }
    

    您可以使用 cases(A(...), B(...)) 控制每个构造器。

    有关详细信息,请参见下面的 构造器参数

  • fields(...)(仅结构体)控制结构体字段的布局。

    警告

    这可能在未来会被字段属性替换。

    例如,对于一个结构体

    struct S {
      x: Int
      y: Int
    }
    

    您可以使用 fields(x(...), y(...)) 控制每个字段。

    有关详细信息,请参见下面的 字段参数

构造器参数#

  • rename = "..." 重命名此特定构造器,覆盖现有的容器范围重命名指令(如果有的话)。

  • fields(...) 控制此构造器的负载布局。请注意,目前无法重命名位置字段。

    有关详细信息,请参见下面的 字段参数

字段参数#

  • rename = "..." 重命名此特定字段,覆盖现有的容器范围重命名指令(如果有的话)。