【问题标题】:How to get structural union types in Scala?如何在 Scala 中获取结构联合类型?
【发布时间】:2020-11-17 10:28:20
【问题描述】:

我不确定“结构联合类型”是否是正确的术语,但我试图在 Scala 中获得以下鸭子类型行为。

假设我有一些容器类Container 具有获取AB 类型对象的方法:

class Container {
  def getA: A
  def getB: B
}

AB 都有一个 commontIntField 属性,但它们也有一些非共享方法:

class A {
  val commonIntField: Int
  def getAStringField1: String
  def getAStringField2: String
}

class B {
  val commonIntField: Int
  def getBStringField: String
}

现在假设我要定义以下三个函数,它们看起来都非常相似:

def f1(c: Container) = {
  val a = c.getA
  (0 to a.commonIntField) foreach { println(a.getAStringField1) }
}

def f2(c: Container) = {
  val a = c.getA
  (0 to a.commonIntField) foreach { println(a.getAStringField2) }
}

def f2(c: Container) = {
  val b = c.getB
  (0 to b.commonIntField) foreach { println(a.getBStringField) }
}

我正在寻找某种方法来干燥它,例如像这样:

def f(getAOrB, getStringField)(c: Container) = {
  val aOrB = c.getAOrB
  (0 to aOrB.commonIntField) foreach { println(aOrB.getStringField) }
}
val f1 = f(_.getA, _.getAStringField1)
val f2 = f(_.getA, _.getAStringField2)
val f3 = f(_.getB, _.getBStringField)

我的问题:我可以为f 使用哪些类型参数来编译它? (这里假设我不能更改ContainerAB 的定义,当然如果需要我可以定义辅助类型。)

【问题讨论】:

    标签: scala types polymorphism duck-typing


    【解决方案1】:

    您应该向f 添加一个类型参数并对其进行约束。

    所以我们首先添加参数并进行相应的重构:

    def f[T](tGetter: Container => T, getStringField: T => String)(c: Container) = {
      val t: T = tGetter(c)
      val intField =  getIntField(t)
      (0 to intField) foreach { println(stringGetter(t)) }
    }
    

    还有一个问题——我们如何定义从容器中获取 T 的方式? 更简单的方法 - 只需添加 tGetter: Container => T 作为第一个参数。这是一个额外的参数,但它很简单,它将帮助您自动导出类型参数。

    def f[T](tGetter: Container => T, getStringField: T => String)(c: Container) = {
      val t: T = tGetter(c)
      val intField: Int = ??? // getIntField(t)
      (0 to intField).foreach { _ => println(getStringField(t)) }
    }
    // val f1 = f(_.getA, _.getAStringField1) // the old one
    val f1 = f[A](_.getA, _.getAStringField1)(_)
    // val f2 = f(_.getA, _.getAStringField2)
    val f2 = f[A](_.getA, _.getAStringField2)(_)
    // val f3 = f(_.getB, _.getBStringField)
    val f3 = f[B](_.getB, _.getBStringField)(_)
    

    但是有一个问题——我们怎样才能得到 int 字段呢? 我们有几种方法。

    1。结构类型

    您只需像这样{ def commonIntField: Int } 表达所需的结构类型并为 T 写上类型界限:

    def f[T <: { def commonIntField: Int }](tGetter: Container => T, getStringField: T => String)(c: Container) = {
    

    或类型别名更好的变体:

    type HaveIntField = { def commonIntField: Int }
    def f[T <: HaveIntField](tGetter: Container => T, getStringField: T => String)(c: Container) = {
    

    但是结构类型被认为是不好的做法,因为它使用反射并且不是很安全。

    2。类型类

    您需要表示 T 可以在没有通用超类型的情况下获取 int 字段。您可以在 scala 中使用类型类模式添加此功能。您可以轻松地通过 Google 指南了解如何编写类型类。我只是展示最终结果。 您将添加一个类型类,说明您的类型可以获取 int 字段。

    trait HaveIntField[X] {
      def getIntField(x: X): Int
    }
    

    然后,您为要在此类功能中支持的每个类创建该类型类的实例。

    object HaveIntField {
      implicit val haveIntField_A_Instance: HaveIntField[A] = new HaveIntField[A] { 
        def getIntField(a: A): Int = a.commonIntField
      }
      implicit val haveIntField_A_Instance: HaveIntField[A] = new HaveIntField[A] { 
          def getIntField(a: A): Int = a.commonIntField
      }
    }
    

    然后最后添加绑定在 T 上的上下文。

    def f[T: HaveIntField](tGetter: Container => T, getStringField: T => String)(c: Container) = {  
      val t: T = tGetter(c)
      val intField =  implicitly[HaveIntField[T]].getIntField(t)
      0 to intField) foreach { println(stringGetter(t)) }
    }
    

    这在 Scala 2 中看起来有点讨厌和压倒性,但它是 FP/Haskell 的一个很酷的概念。在即将到来的 Scala 3 中它会非常顺利。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-28
      • 1970-01-01
      • 2019-09-24
      • 1970-01-01
      • 1970-01-01
      • 2022-07-19
      • 1970-01-01
      相关资源
      最近更新 更多