在 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

必须满足:

  1. 'Foo' 必须有一个 0 参数 builder/constructor 定义(大概在它的伴随对象中)

  2. 调用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] 模式。