具有约束的 Scala 泛型类型

Scala generic type with constraints

我正在研究 Scala 并想生成一些通用代码。我想要两个 class,一个 "outer" class 和一个 "inner" class。外部 class 应该是通用的,并接受任何类型的内部 class ,它遵循一些限制。这是我想要的那种架构,在不可编译的代码中。 Outer 是通用类型,Inner 是可以在 Outer 中使用的类型示例。

class Outer[InType](val in: InType) {
  def update: Outer[InType] = new Outer[InType](in.update)

  def export: String = in.export
}

object Outer {
  def init[InType]: Outer[InType] = new Outer[InType](InType.empty)
}

class Inner(val n: Int) {
  def update: Inner = new Inner(n + 1)

  def export: String = n.toString
}

object Inner {
  def empty: Inner = new Inner(0)
}

object Main {
  def main(args: Array[String]): Unit = {
    val outerIn: Outer[Inner] = Outer.empty[Inner]
    println(outerIn.update.export) // expected to print 1
  }
}

重要的一点是,无论 InType 是什么,in.update 都必须 return 一个 "updated" InType 对象。我还希望可以调用伴随方法,例如 InType.empty。这样 Outer[InType]InType 都是不可变类型,并且在伴生对象中定义的方法是可调用的。

前面的代码无法编译,因为它写得像 C++ 泛型类型(我的背景)。根据我提到的约束更正此代码的最简单方法是什么?我是不是完全错了,我应该使用另一种方法吗?

我能想到的一种方法是要求我们使用 F-Bounded Polymorphism along with Type Classes.

首先,我们将创建一个需要 update 方法可用的特征:

trait AbstractInner[T <: AbstractInner[T]] {
  def update: T
  def export: String
}

Inner 创建具体实现:

class Inner(val n: Int) extends AbstractInner[Inner] {
  def update: Inner = new Inner(n + 1)
  def export: String = n.toString
}

要求 Outer 仅采用扩展 AbstractInner[InType]:

的输入类型
class Outer[InType <: AbstractInner[InType]](val in: InType) {
  def update: Outer[InType] = new Outer[InType](in.update)
}

我们得到了用于创建 in 更新版本的类型,我们需要以某种方式使用 empty 创建一个新实例。类型类模式是经典之作。我们创建一个构建 Inner 类型的特征:

trait InnerBuilder[T <: AbstractInner[T]] {
  def empty: T
}

我们要求 Outer.empty 仅采用扩展 AbstractInner[InType] 的类型在范围内具有隐式 InnerBuilder[InType]

object Outer {
  def empty[InType <: AbstractInner[InType] : InnerBuilder] = 
    new Outer(implicitly[InnerBuilder[InType]].empty)
}

并为Inner提供具体实现:

object AbstractInnerImplicits {
  implicit def innerBuilder: InnerBuilder[Inner] = new InnerBuilder[Inner] {
    override def empty = new Inner(0)
  }
}

在 main 中调用:

object Experiment {
  import AbstractInnerImplicits._
  def main(args: Array[String]): Unit = {
    val outerIn: Outer[Inner] = Outer.empty[Inner]
    println(outerIn.update.in.export)
  }
}

产量:

1

我们已经做到了。我知道一开始可能有点难以理解。阅读本文时,请随时提出更多问题。

我可以想到两种不涉及黑魔法的方法:

  1. 具有特征:

    trait Updatable[T] { self: T =>    
      def update: T
    }
    
    class Outer[InType <: Updatable[InType]](val in: InType) {
      def update = new Outer[InType](in.update)
    }
    
    class Inner(val n: Int) extends Updatable[Inner] {
      def update = new Inner(n + 1)
    }
    

    首先我们使用 trait,告诉类型系统 update 方法可用,然后我们对类型进行限制以确保 Updatable 被正确使用(self: T => 将确保它用作 T extends Updatable[T] - F-bounded 类型),然后我们还确保 InType 将实现它(InType <: Updatable[InType])。

  2. 类型 class:

    trait Updatable[F] {
    
      def update(value: F): F
    }
    
    class Outer[InType](val in: InType)(implicit updatable: Updatable[InType]) {
      def update: Outer[InType] = new Outer[InType](updatable.update(in))
    }
    
    class Inner(val n: Int) {
      def update: Inner = new Inner(n + 1)
    }
    
    implicit val updatableInner = new Updatable[Inner] {
      def update(value: Inner): Inner = value.update
    }
    

    首先我们定义类型 class,然后我们隐含地要求它为我们的类型实现,最后我们提供并使用它。把整个理论的东西放在一边,实际的区别是这个接口是你不是强迫 InType 扩展一些 Updatable[InType],而是需要一些 Updatable[InType] 实现在你的范围 - 这样您就可以提供功能,而不是通过修改 InType,而是通过提供一些额外的 class 来满足您的约束或 InType.

由于这样的类型 class 更具可扩展性,您只需要为每个支持的类型提供隐式。

您可以使用的其他方法包括反射(但是这可能会破坏类型安全和重构能力)。