限制可以扩展 scala 特征的 类
Limiting classes that can extend a scala trait
似乎有三种(或更多)方法可以限制哪些 类 可以混入给定的 scala 特征:
- 使用共同祖先[特质]
- 使用抽象声明
- 在特征中使用自我类型
共同祖先方法需要额外的限制,而且似乎不是最佳选择。同时,自键入和抽象声明似乎是相同的。有人愿意解释差异和用例吗(尤其是 2 和 3 之间)?
我的例子是:
val exampleMap = Map("one" -> 1, "two" -> 2)
class PropsBox (val properties : Map[String, Any])
// Using Common Ancestor
trait HasProperties {
val properties : Map[String, Any]
}
trait KeysAsSupertype extends HasProperties {
def keys : Iterable[String] = properties.keys
}
class SubProp(val properties : Map[String, Any]) extends HasProperties
val inCommonAncestor = new SubProp(exampleMap) with KeysAsSupertype
println(inCommonAncestor.keys)
// prints: Set(one, two)
// Using Abstract Declaration
trait KeysAsAbstract {
def properties : Map[String, Any]
def keys : Iterable[String] = properties.keys
}
val inAbstract = new PropsBox(exampleMap) with KeysAsAbstract
println(inSelfType.keys)
// prints: Set(one, two)
// Using Self-type
trait KeysAsSelfType {
this : PropsBox =>
def keys : Iterable[String] = properties.keys
}
val inSelfType = new PropsBox(exampleMap) with KeysAsSelfType
println(inSelfType.keys)
// prints: Set(one, two)
在您的示例中,PropsBox
没有对 properties
施加任何有趣的约束 - 它只是有一个成员 properties: Map[String, Any]
。因此,无法检测从 PropsBox
继承与只需要 def properties: Map[String, Any]
.
之间的区别
考虑下面的示例,其中确实存在差异。假设我们有两个 类 GoodBox
和 BadBox
。
GoodBox
有properties
,所有键都是只包含数字的短字符串
BadBox
只是 properties
,不保证键的结构
在代码中:
/** Has `properties: Map[String, Any]`,
* and also guarantees that all the strings are
* actually decimal representations of numbers
* between 0 and 99.
*/
class GoodBox(val properties: Map[String, Any]) {
require(properties.keys.forall {
s => s.forall(_.isDigit) && s.size < 3
})
}
/** Has `properties: Map[String, Any]`, but
* guarantees nothing about the keys.
*/
class BadBox(val properties: Map[String, Any])
现在假设我们出于某种原因想要将 Map[String, Any]
转换为稀疏填充的 Array[Any]
,并使用键作为数组索引。同样,这里有两种方法:一种使用 self
类型声明,另一种使用抽象 def properties
成员声明:
trait AsArrayMapSelfType {
self: GoodBox =>
def asArrayMap: Array[Any] = {
val n = 100
val a = Array.ofDim[Any](n)
for ((k, v) <- properties) {
a(k.toInt) = v
}
a
}
}
trait AsArrayMapAbstract {
def properties: Map[String, Any]
def asArrayMap: Array[Any] = {
val n = 100
val a = Array.ofDim[Any](n)
for ((k, v) <- properties) {
a(k.toInt) = v
}
a
}
}
现在试试看:
val goodBox_1 =
new GoodBox(Map("1" -> "one", "42" -> "fourtyTwo"))
with AsArrayMapSelfType
val goodBox_2 =
new GoodBox(Map("1" -> "one", "42" -> "fourtyTwo"))
with AsArrayMapAbstract
/* error: illegal inheritance
val badBox_1 =
new BadBox(Map("Not a number" -> "mbxkxb"))
with AsArrayMapSelfType
*/
val badBox_2 =
new BadBox(Map("Not a number" -> "mbxkxb"))
with AsArrayMapAbstract
goodBox_1.asArrayMap
goodBox_2.asArrayMap
// badBox_1.asArrayMap - not allowed, good!
badBox_2.asArrayMap // Crashes with NumberFormatException, bad
对于 goodBox
,这两种方法都有效并产生相同的结果。但是,对于 badBox
,self-type 与 abstract-def 的行为不同:
- 自类型版本不允许代码编译(编译时捕获错误)
- abstract-def 版本在运行时崩溃
NumberFormatException
(错误发生在运行时)
这就是区别。
似乎有三种(或更多)方法可以限制哪些 类 可以混入给定的 scala 特征:
- 使用共同祖先[特质]
- 使用抽象声明
- 在特征中使用自我类型
共同祖先方法需要额外的限制,而且似乎不是最佳选择。同时,自键入和抽象声明似乎是相同的。有人愿意解释差异和用例吗(尤其是 2 和 3 之间)?
我的例子是:
val exampleMap = Map("one" -> 1, "two" -> 2)
class PropsBox (val properties : Map[String, Any])
// Using Common Ancestor
trait HasProperties {
val properties : Map[String, Any]
}
trait KeysAsSupertype extends HasProperties {
def keys : Iterable[String] = properties.keys
}
class SubProp(val properties : Map[String, Any]) extends HasProperties
val inCommonAncestor = new SubProp(exampleMap) with KeysAsSupertype
println(inCommonAncestor.keys)
// prints: Set(one, two)
// Using Abstract Declaration
trait KeysAsAbstract {
def properties : Map[String, Any]
def keys : Iterable[String] = properties.keys
}
val inAbstract = new PropsBox(exampleMap) with KeysAsAbstract
println(inSelfType.keys)
// prints: Set(one, two)
// Using Self-type
trait KeysAsSelfType {
this : PropsBox =>
def keys : Iterable[String] = properties.keys
}
val inSelfType = new PropsBox(exampleMap) with KeysAsSelfType
println(inSelfType.keys)
// prints: Set(one, two)
在您的示例中,PropsBox
没有对 properties
施加任何有趣的约束 - 它只是有一个成员 properties: Map[String, Any]
。因此,无法检测从 PropsBox
继承与只需要 def properties: Map[String, Any]
.
考虑下面的示例,其中确实存在差异。假设我们有两个 类 GoodBox
和 BadBox
。
GoodBox
有properties
,所有键都是只包含数字的短字符串BadBox
只是properties
,不保证键的结构
在代码中:
/** Has `properties: Map[String, Any]`,
* and also guarantees that all the strings are
* actually decimal representations of numbers
* between 0 and 99.
*/
class GoodBox(val properties: Map[String, Any]) {
require(properties.keys.forall {
s => s.forall(_.isDigit) && s.size < 3
})
}
/** Has `properties: Map[String, Any]`, but
* guarantees nothing about the keys.
*/
class BadBox(val properties: Map[String, Any])
现在假设我们出于某种原因想要将 Map[String, Any]
转换为稀疏填充的 Array[Any]
,并使用键作为数组索引。同样,这里有两种方法:一种使用 self
类型声明,另一种使用抽象 def properties
成员声明:
trait AsArrayMapSelfType {
self: GoodBox =>
def asArrayMap: Array[Any] = {
val n = 100
val a = Array.ofDim[Any](n)
for ((k, v) <- properties) {
a(k.toInt) = v
}
a
}
}
trait AsArrayMapAbstract {
def properties: Map[String, Any]
def asArrayMap: Array[Any] = {
val n = 100
val a = Array.ofDim[Any](n)
for ((k, v) <- properties) {
a(k.toInt) = v
}
a
}
}
现在试试看:
val goodBox_1 =
new GoodBox(Map("1" -> "one", "42" -> "fourtyTwo"))
with AsArrayMapSelfType
val goodBox_2 =
new GoodBox(Map("1" -> "one", "42" -> "fourtyTwo"))
with AsArrayMapAbstract
/* error: illegal inheritance
val badBox_1 =
new BadBox(Map("Not a number" -> "mbxkxb"))
with AsArrayMapSelfType
*/
val badBox_2 =
new BadBox(Map("Not a number" -> "mbxkxb"))
with AsArrayMapAbstract
goodBox_1.asArrayMap
goodBox_2.asArrayMap
// badBox_1.asArrayMap - not allowed, good!
badBox_2.asArrayMap // Crashes with NumberFormatException, bad
对于 goodBox
,这两种方法都有效并产生相同的结果。但是,对于 badBox
,self-type 与 abstract-def 的行为不同:
- 自类型版本不允许代码编译(编译时捕获错误)
- abstract-def 版本在运行时崩溃
NumberFormatException
(错误发生在运行时)
这就是区别。