在 Scala 中,如何对伴随对象执行编译时类型检查?
In Scala, How to perform compile-time type check on companion object?
一件在许多语言中很容易做到但在 Scala 中却做不到的事情是:
定义原型 'Super',这样 'Super' 的所有实现都必须定义构造函数 'create()'.
我发现这个约束非常重要,能够在运行前识别出很多问题。但是,此功能在 Java 中仅部分强制执行(通过定义一个始终抛出错误的 'abstract' 静态方法),而在 Scala 中完全缺失(伴随对象与 class 完全分离,不能在原型中强制执行)。
是否有允许我执行此操作的宏或工具?
更新 抱歉,我的问题缺少上下文和示例。这是 scala 中的一个正式用例:
在项目A中,我们定义了一个可以被所有子项目扩展的接口:
trait AbstractFoo {}
这个接口应该总是有一个默认的0参数builder/constructor,所以项目A可以按需初始化它,但是,项目A不知道每个构造函数的实现:
object AbstractFoo {
def default[T <: AbstractFoo: ClassTag](): T
}
所以问题就变成了:如何严格定义 AbstractFoo,使得对于 A 的所有子项目,AbstractFoo 的任何实现:
case class Foo(...) extends AbstractFoo
必须满足:
'Foo' 必须有一个 0 参数 builder/constructor 定义(大概在它的伴随对象中)
调用AbstractFoo.defaultFoo可以调用这个0参数builder/constructor
需要注意的是,在另一种情况下,存在一种解决方案,即将每个伴生对象定义为隐式类型class:
trait FooBuilder[T <: AbstractFoo] {
def default(): T
}
object AbstractFoo {
implicit object Foo extends FooBuilder[Foo] {
def default() = {...}
}
def default[T <: AbstractFoo: FooBuilder](): T = {
implicitly[FooBuilder[T]].default
}
}
这样,如果隐式对象未定义,编译器将给出隐式未找到错误(我的代码片段可能有一些语法错误,想法来自 http://www.cakesolutions.net/teamblogs/demystifying-implicits-and-typeclasses-in-scala)
不幸的是,它并不总是很方便,因为 A 的这个子项目通常对于项目 A 是未知的。然而默认的隐式构建器不能重新定义,这使得 default() 的每次调用都更加复杂。
我相信 scala 是一种可扩展性很强的语言,因此无论是使用宏、注释还是其他元编程技术,都应该至少有一种方法来强制执行它。我的问题现在够清楚了吗?
UPDATE2:我相信我仔细研究了Scaladoc后找到了解决方案,角落里隐藏着一条评论:
如果有多个符合条件的参数匹配隐式参数的类型,将使用静态重载解析规则选择最具体的一个(参见 Scala 规范 §6.26.4):
...
类型参数的隐式范围 (2.8.0)
...
所以我只需要在 FooBuilder 中编写一个隐式函数:
trait FooBuilder[T <: AbstractFoo] {
def default(): T
implicit def self = this
}
object Foo extends FooBuilder[Foo]
所以每次有人打电话时:
default[Foo]
scala 会引用class Foo 的范围,其中包含对象Foo,其中包含隐式值Foo,并最终找到0 参数构造函数。
我认为这个定义比在对象 FooBuilder 下定义更好,因为你只能定义一次 FooBuilder,因此它不太可扩展。你同意我的看法吗?如果是这样,能否请您修改您的答案以便我给您加分?
我不明白为什么 abstract class
甚至 Trait
不允许这样做?
abstract class DefineCreate{
def create(): Unit
}
case class Foo(one: Int)
object Foo extends DefineCreate{
def create(): Unit = { Console.out.println("side-effect") }
}
因此我强制用户在有问题的 object
上创建一个 create
方法,因为 DefineCreate
的所有实现都必须这样做才能编译。
更新以下评论
好吧,无需求助于宏之类的东西,您可以使用类型 classes:
实现同样的事情
trait Constructor[A]{
def create(): A
}
object Construct{
def create[A](implicit cr: Constructor[A]): A = cr.create()
}
它没有明确强制伴随对象产生方法,但它确实强制用户创建类型 class 如果他们想使用 Constructor.create[Foo]
模式。
一件在许多语言中很容易做到但在 Scala 中却做不到的事情是:
定义原型 'Super',这样 'Super' 的所有实现都必须定义构造函数 'create()'.
我发现这个约束非常重要,能够在运行前识别出很多问题。但是,此功能在 Java 中仅部分强制执行(通过定义一个始终抛出错误的 'abstract' 静态方法),而在 Scala 中完全缺失(伴随对象与 class 完全分离,不能在原型中强制执行)。
是否有允许我执行此操作的宏或工具?
更新 抱歉,我的问题缺少上下文和示例。这是 scala 中的一个正式用例:
在项目A中,我们定义了一个可以被所有子项目扩展的接口:
trait AbstractFoo {}
这个接口应该总是有一个默认的0参数builder/constructor,所以项目A可以按需初始化它,但是,项目A不知道每个构造函数的实现:
object AbstractFoo {
def default[T <: AbstractFoo: ClassTag](): T
}
所以问题就变成了:如何严格定义 AbstractFoo,使得对于 A 的所有子项目,AbstractFoo 的任何实现:
case class Foo(...) extends AbstractFoo
必须满足:
'Foo' 必须有一个 0 参数 builder/constructor 定义(大概在它的伴随对象中)
调用AbstractFoo.defaultFoo可以调用这个0参数builder/constructor
需要注意的是,在另一种情况下,存在一种解决方案,即将每个伴生对象定义为隐式类型class:
trait FooBuilder[T <: AbstractFoo] {
def default(): T
}
object AbstractFoo {
implicit object Foo extends FooBuilder[Foo] {
def default() = {...}
}
def default[T <: AbstractFoo: FooBuilder](): T = {
implicitly[FooBuilder[T]].default
}
}
这样,如果隐式对象未定义,编译器将给出隐式未找到错误(我的代码片段可能有一些语法错误,想法来自 http://www.cakesolutions.net/teamblogs/demystifying-implicits-and-typeclasses-in-scala)
不幸的是,它并不总是很方便,因为 A 的这个子项目通常对于项目 A 是未知的。然而默认的隐式构建器不能重新定义,这使得 default() 的每次调用都更加复杂。
我相信 scala 是一种可扩展性很强的语言,因此无论是使用宏、注释还是其他元编程技术,都应该至少有一种方法来强制执行它。我的问题现在够清楚了吗?
UPDATE2:我相信我仔细研究了Scaladoc后找到了解决方案,角落里隐藏着一条评论:
如果有多个符合条件的参数匹配隐式参数的类型,将使用静态重载解析规则选择最具体的一个(参见 Scala 规范 §6.26.4):
...
类型参数的隐式范围 (2.8.0)
...
所以我只需要在 FooBuilder 中编写一个隐式函数:
trait FooBuilder[T <: AbstractFoo] {
def default(): T
implicit def self = this
}
object Foo extends FooBuilder[Foo]
所以每次有人打电话时:
default[Foo]
scala 会引用class Foo 的范围,其中包含对象Foo,其中包含隐式值Foo,并最终找到0 参数构造函数。
我认为这个定义比在对象 FooBuilder 下定义更好,因为你只能定义一次 FooBuilder,因此它不太可扩展。你同意我的看法吗?如果是这样,能否请您修改您的答案以便我给您加分?
我不明白为什么 abstract class
甚至 Trait
不允许这样做?
abstract class DefineCreate{
def create(): Unit
}
case class Foo(one: Int)
object Foo extends DefineCreate{
def create(): Unit = { Console.out.println("side-effect") }
}
因此我强制用户在有问题的 object
上创建一个 create
方法,因为 DefineCreate
的所有实现都必须这样做才能编译。
更新以下评论
好吧,无需求助于宏之类的东西,您可以使用类型 classes:
实现同样的事情trait Constructor[A]{
def create(): A
}
object Construct{
def create[A](implicit cr: Constructor[A]): A = cr.create()
}
它没有明确强制伴随对象产生方法,但它确实强制用户创建类型 class 如果他们想使用 Constructor.create[Foo]
模式。