派生内建特征#

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 : Option[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 : Option[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 文件反序列化/序列化给定类型的方法。

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

enum JsonTest2 {
  A(x~: Int)
  B(x~: Int, y~: Int)
} derive(FromJson, ToJson, 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 序列化参数的实际行为是不稳定的。

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(rename_fields = "SCREAMING_SNAKE_CASE", repr(ext_tagged)),
  ToJson(rename_fields = "SCREAMING_SNAKE_CASE", repr(ext_tagged)),
  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": { "X": 123 } }
  assert_eq!(input.to_json(), expected)
  assert_eq!(@json.from_json!(expected), input)
}

枚举表示#

枚举表示为 JSON 有多种风格。表示有两个方面:

  • 标签位置 决定枚举标签(即构造器名称)的名称存储在哪里。

  • 构造器表示 决定如何表示枚举的负载。

让我们考虑以下枚举定义:

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

对于标签位置,有 4 个变体:

  • 内部标记 将标签与负载值一起放置:

    { "$tag": "Uniform", "0": 1 }, { "$tag": "Axes", "x": 2, "y": 3 }

  • 外部标记 将标签作为 JSON 对象键放置在负载值之外:

    { "Uniform": { "0": 1 } }, { "Axes": { "x": 2, "y": 3 } }

  • 相邻标记 将标签负载放置在 JSON 对象中的两个相邻键中:

    { "t": "Uniform", "c": { "0": 1 } }, { "t": "Axes", "c": { "x": 2, "y": 3 } }

  • 无标记 没有明确的标记标识数据属于哪个构造器:

    { "0": 1 }, { "x": 2, "y": 3 }.

    JSON 反序列化器将尝试按顺序反序列化每个构造器,并返回第一个成功的构造器。

对于构造器表示,有 2 个变体:

  • 对象式 表示将枚举负载序列化为 JSON 对象,其键是标签名称或结构体中的位置索引的字符串。

    { "0": 1 }, { "x": 2, "y": 3 }

  • 元组式 表示将枚举负载序列化为元组(JSON 数组),顺序与类型声明相同。标签在元组式表示中被省略。

    [1], [2, 3]

这两个方面可以自由组合,除了一个情况:内部标记 枚举不能使用 元组式 表示。

容器参数#

  • repr(...) 配置容器的表示。这控制枚举的标签位置。对于结构体,假定标签是类型的类型。

    有 4 种可供选择的表示:

    • repr(tag = "tag") – 使用内部标记表示,标记的对象键名称如指定。

    • repr(untagged) – 使用无标记表示。

    • repr(ext_tagged) – 使用外部标记表示。

    • repr(tag = "tag", contents = "contents") – 使用相邻标记表示,标记和内容键名称如指定。

    结构体的默认表示为 repr(untagged)

    枚举的默认表示为 repr(tag = "$tag")

  • case_repr(...)(仅枚举)配置容器的构造器表示。此选项仅适用于枚举。

    • case_repr(struct) – 使用结构体式表示的枚举。

    • case_repr(tuple) – 使用元组式表示的枚举。

  • rename_fieldsrename_cases(仅枚举)、rename_struct(仅结构体)、rename_all 分别将字段、构造器名称、结构体名称和所有名称重命名为特定风格。

    可选的参数有:

    • 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 = "..." 重命名此特定字段,覆盖现有的容器范围重命名指令(如果有的话)。