Deriving
一个简单示例:
import std.deriving.*
@Derive[ToString]
class User {
User(
let name: String,
let id: Int
) {}
}
main() {
println(User("root", 0)) // -> "User(name = root, id = 0)"
}
当 @Derive[ToString]
应用于类或结构体时, Deriving 会收集和使用类或结构体的可变和不可变字段,包括在主构造函数中指定的字段,并自动实现 ToString
的方法。当 @Derive[ToString]
应用于枚举时, Deriving 将收集枚举的构造函数参数。静态字段和属性将不会被收集和使用,另外, Deriving 收集的字段不允许存在私有字段,否则将抛出编译错误。
收集到的字段用于 Deriving 时,其类型需要实现目标接口,以便将字段结果组合在一起。例如,当处理 ToString
时,生成的代码将在所有收集到的字段上调用 toString
,然后将结果与对应的字段名称连接成一个字符串。如果其中一个字段的类型不支持 ToString
,则会抛出编译错误并且 Deriving 无法完成。
注意
标记为派生的类应该是最终的:它不应该是开放的、抽象的或
sealed
的。
有些字段可能具有特殊含义,它们的值没有多大意义,则可通过在这些字段上应用 @DeriveExclude
来排除这些字段:
@Derive[ToString]
class User {
User(let name: String) {}
@DeriveExclude
let lazyHashCode = 0 // it will not be printed because it's excluded
}
默认情况 Deriving 仅使能字段,对于属性则需要通过 @DeriveInclude
来显式使能:
@Derive[ToString]
class User {
User(
let id: Int
) {}
@DeriveInclude
prop name: String {
get() { passwdFile.getUserName(id) }
}
}
main() {
println(User(0)) // -> "User(id = 0, name = root)"
}
请注意,因为属性 name
是在 id
之后声明的,因此打印的顺序为先 id
后 name
。
如果需要更改打印的顺序,可以使用 @DeriveOrder
:
@Derive[ToString]
@DeriveOrder(name, id)
class User {
User(
let id: Int
) {}
@DeriveInclude
prop name: String {
get() { passwdFile.getUserName(id) }
}
}
main() {
println(User(0)) // -> "User(name = root, id = 0)"
}
常见的 Deriving 语法
@Derive
宏支持以逗号分隔的接口名称列表。此外,该宏可以重复多次被调用,但所有 @Derive
宏调用都应位于声明的顶部,而其他宏(如 @DeriveOrder
)应始终位于其后。
支持的接口列表的顺序没有影响。
@Derive[ToString, Hashable]
@Derive[Equatable]
@DeriveOrder[currency,price,quantity]
struct Order {}
当 Deriving 多个相交的接口时,例如,Comparable
还包括 Equatable
,则允许两者同时存在,等同于仅有范围最广的一个:
@Derive[Comparable] // does also generate Equatable
等同于:
@Derive[Comparable, Equatable]
包含和排除
默认情况下会处理所有字段,包括定义为主构造函数参数的字段。
当需要排除某个字段时,可以对其应用 @DeriveExclude
:
@Derive[ToString]
struct S {
S(let id) { key = "s_${id}" }
@DeriveExclude
let key: String
}
默认情况下不处理属性,需要通过 @DeriveInclude
包含属性。
@Derive[ToString]
struct S {
S(let id) {}
@DeriveInclude
prop key: String {
get() { "s_${id}" }
}
}
被 Deriving 的字段和属性都不能是 private
的。因此,private
的字段或者属性应被除外或者使其为包内可见属性。
注意
静态的字段和属性始终会被忽略,因此他们都不能被
@DeriveInclude
和@DeriveInclude
修饰。
支持的接口
当前仅支持如下接口:
ToString
Hashable
Equatable
Comparable
暂不支持用户自定义的接口。
变更顺序
在对由多个字段组成的复杂类型的实例进行排序和比较时,测试字段的顺序通常很重要。默认情况下,所有字段都按声明顺序考虑。可以使用 @DeriveOrder
宏修改顺序。
@Derive[Comparable, ToString]
struct Floor {
Floor(
let level: Int,
let building: Int
) {}
}
main() {
let floors = [
Floor(1, 2),
Floor(3, 2),
Floor(2, 1)
]
floors.sort()
for (f in floors) { println(f) }
}
上述示例将打印以下内容,看起来顺序没有很大影响。
Floor(level = 1, building = 2)
Floor(level = 2, building = 1)
Floor(level = 3, building = 2)
但是当我实现 Comparable
时,不同的顺序将影响结果。
@Derive[Comparable, ToString]
@DeriveOrder[building, level]
struct Floor {
Floor(
let level: Int,
let building: Int
) {}
}
此时,结果将首先按 building
排序,然后按 level
排序:
Floor(building = 1, level = 2)
Floor(building = 2, level = 1)
Floor(building = 2, level = 3)
泛型
实现泛型类型的接口通常需要应用约束,以便类型仅在某些条件下实现接口。例如:
class Cell<T> {
Cell(let value: T) {}
}
此时可能希望仅当单元格的值可打印时才能够打印该单元格。为了实现它,我们将编写一个带有约束的扩展:
extend <T> Cell<T> <: ToString where T <: ToString {
public func toString(): String {
"Cell(value = ${value})"
}
}
当使用 Deriving 时,它会默认尝试对所有泛型参数应用约束,因此以下内容与上面的扩展相同:
@Derive[ToString]
class Cell<T> {
Cell(let value: T) {}
}
然而在某些情况下,默认行为并不符合期望。此时,可使用 @Derive
内部的 where
来覆盖默认约束:
interface PrintableCellValue <: ToString { ... }
@Derive[ToString where T <: PrintableCellValue]
class Cell<T> {}
请注意,在上面的示例中,自定义约束仅适用于 ToString
,因此如果需要对所有接口进行约束,则应单独为每个接口重复此动作。
@Derive[ToString where T <: PrintableCellValue]
@Derive[Hashable where T <: PrintableCellValue & Hashable]
class Cell<T> {}
性能说明
由于 Deriving 是基于仓颉宏的,不涉及任何反射,因此 Deriving 实现的运行时性能与手写相当。但是,Deriving 涉及编译时的代码转换,因此它会影响编译时间。