具有约束的Scala泛型类型

时间:2017-02-19 13:46:58

标签: scala generic-programming

我正在使用Scala并希望生成一些通用代码。我想有两个班,一个是“外”班,一个是“内”班。外部类应该是通用的,并接受遵循一些约束的任何类型的内部类。这是我想要的架构,在无法编译的代码中。 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都必须返回“已更新”的InType对象。我也希望同伴方法可以调用,比如InType.empty。这样,Outer[InType]InType都是不可变类型,并且伴随对象中定义的方法是可调用的。

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

2 个答案:

答案 0 :(得分:2)

我能想到的一种方法是要求我们使用F-Bounded PolymorphismType 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创建新实例。 Typeclass Pattern是经典之作。我们创建了一个构建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 :(得分:0)

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

  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方法是否可用,然后我们对类型设置限制以确保正确使用Updatableself: T =>将确保它被用作T extends Updatable[T] - 作为F-bounded类型),然后我们还确保InType将实现它(InType <: Updatable[InType])。

  2. 类型为:

    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
    }
    

    首先我们定义类型类,然后我们隐式地要求它为我们的类型实现,最后我们提供并使用它。抛开整个理论的东西,实际的不同之处在于,这个界面是你不强迫InType扩展一些Updatable[InType],而是要求你有一些Updatable[InType]实现在你的范围 - 因此您可以不是通过修改InType来提供功能,而是通过提供一些可以满足您的约束或InType的其他类。

  3. 由于此类类型更具可扩展性,因此您只需为每种受支持的类型提供隐式。

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