【问题标题】:Scala generic type with constraints具有约束的 Scala 泛型类型
【发布时间】:2017-02-19 13:46:57
【问题描述】:

我正在使用 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++ 泛型类型(我的背景)。根据我提到的约束更正此代码的最简单方法是什么?我完全错了,我应该使用其他方法吗?

【问题讨论】:

  • 我不明白您使用类型参数的动机。用 Inner 替换 InType 就可以了。
  • 其实我应该会问,你有没有一个具体的用途,只能通过使用类型参数才能实现?否则我的建议是不要将参数多态性与子类型混合:这会使事情变得非常快,正如您从下面的答案中看到的那样。我从 +- '98 开始就不再使用 C++ 模板(天哪,可能是 20 年!),否则我会尝试和你一起走 C++ templates to parametric polymorphism 路径。
  • 我不知道类型参数是否是最好的解决方案。我喜欢使用方法创建类,而不是修改对象,而是返回相同类型的对象。所以这里的代码在更新和导出方面非常简单,但我正在编写的真实代码在更新调用期间对数据执行操作,这可能非常多样化,包括统计数据的计算。折叠使用 Outer 类实例作为累加器,在折叠结束时,我可能希望内部类导出一些结果。我需要能够使用 Inner 或其他处理,因此需要 type 参数。

标签: scala generic-programming


【解决方案1】:

我能想到的一种方法是要求我们同时使用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

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

【讨论】:

    【解决方案2】:

    我可以想到 2 种方法来做到这一点,而无需提及黑魔法:

    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 =&gt; 将确保它被用作 @987654325 @ - 作为 F 有界类型),那么我们还要确保 InType 会实现它(InType &lt;: 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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-17
      • 1970-01-01
      • 2018-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-07
      相关资源
      最近更新 更多