【问题标题】:In Scala, how to deal with heterogeneous list of the same parameterized type在Scala中,如何处理相同参数化类型的异构列表
【发布时间】:2019-01-05 10:32:53
【问题描述】:

我有一个 Any 数组(在现实生活中,它是一个 Spark Row,但足以隔离问题)

object Row {
  val buffer : Array[Any] = Array(42, 21, true)
}

我想对其元素应用一些操作。 所以,我定义了一个简单的 ADT 来定义一个 compute 对类型 A 的操作

  trait Op[A] {
    def cast(a: Any) : A = a.asInstanceOf[A]
    def compute(a: A) : A
  }

  case object Count extends Op[Int] {
    override def compute(a: Int): Int = a + 1
  }

  case object Exist extends Op[Boolean] {
    override def compute(a: Boolean): Boolean = a
  }

鉴于我有一个所有操作的列表,并且我知道要对每个元素应用哪个操作,让我们使用这些操作。

object GenericsOp {
  import Row._

  val ops = Seq(Count, Exist)

  def compute() = {
    buffer(0) = ops(0).compute(ops(0).cast(buffer(0)))
    buffer(1) = ops(0).compute(ops(0).cast(buffer(1)))
    buffer(2) = ops(1).compute(ops(1).cast(buffer(2)))
  }
}

根据设计,对于给定的操作,类型在 castcombine 之间对齐。但不幸的是,以下代码无法编译。错误是

Type mismatch, expected: _$1, actual: AnyVal

有没有办法让它工作?

我找到了一种解决方法,即使用抽象类型成员而不是类型参数。

object AbstractOp extends App {
  import Row._

  trait Op {
    type A
    def compute(a: A) : A
  }

  case object Count extends Op {
    type A = Int
    override def compute(a: Int): Int = a + 1
  }

  case object Exist extends Op {
    type A = Boolean
    override def compute(a: Boolean): Boolean = a
  }

  val ops = Seq(Count, Exist)

  def compute() = {
    val op0 = ops(0)
    val op1 = ops(1)
    buffer(0) = ops(0).compute(buffer(0).asInstanceOf[op0.A])
    buffer(1) = ops(0).compute(buffer(1).asInstanceOf[op0.A])
    buffer(2) = ops(1).compute(buffer(2).asInstanceOf[op1.A])
  }
}

有没有更好的办法?

【问题讨论】:

  • 我不确定你在那里尝试实现什么,但你知道 Shapeless' HLists
  • 嗨@AndreyTyukin。我已经改写了这个问题,以使我想要实现的目标更加清晰。我知道 Shapeless,但我想了解原始 Scala 的可能性。

标签: scala


【解决方案1】:

看来你的代码可以通过使Op[A]扩展Any => A来简化:

trait Op[A] extends (Any => A) {
  def cast(a: Any) : A = a.asInstanceOf[A]
  def compute(a: A) : A
  def apply(a: Any): A = compute(cast(a))
}

case object Count extends Op[Int] {
  override def compute(a: Int): Int = a + 1
}

case object Exist extends Op[Boolean] {
  override def compute(a: Boolean): Boolean = a
}

object AbstractOp {

  val buffer: Array[Any] = Array(42, 21, true)
  val ops: Array[Op[_]] = Array(Count, Count, Exist)

  def main(args: Array[String]): Unit = {
    for (i <- 0 until buffer.size) {
      buffer(i) = ops(i)(buffer(i))
    }
    println(buffer.mkString("[", ",", "]"))
  }
}

因为无论如何它都是asInstanceOf,所以它不会使代码比以前的安全性低。


更新

如果不能更改Op接口,那么调用castcompute会比较麻烦,但还是可以的:

trait Op[A] {
  def cast(a: Any) : A = a.asInstanceOf[A]
  def compute(a: A) : A
}

case object Count extends Op[Int] {
  override def compute(a: Int): Int = a + 1
}

case object Exist extends Op[Boolean] {
  override def compute(a: Boolean): Boolean = a
}

object AbstractOp {

  val buffer: Array[Any] = Array(42, 21, true)
  val ops: Array[Op[_]] = Array(Count, Count, Exist)

  def main(args: Array[String]): Unit = {
    for (i <- 0 until buffer.size) {
      buffer(i) = ops(i) match {
        case op: Op[t] => op.compute(op.cast(buffer(i)))
      }
    }
    println(buffer.mkString("[", ",", "]"))
  }
}

注意模式中带有类型参数的ops(i) match { case op: Opt[t] =&gt; ... } 部分:这使我们能够确保cast 返回tcompute 接受。

【讨论】:

    【解决方案2】:

    作为比Andrey Tyukin的更通用的解决方案,可以在Op之外定义方法,所以即使Op不能修改也可以:

    def apply[A](op: Op[A], x: Any) = op.compute(op.cast(x))
    
    buffer(0) = apply(ops(0), buffer(0))
    

    【讨论】:

    • 其实你根本不需要任何额外的方法。查看更新。
    • 是的,这是另一种选择。
    猜你喜欢
    • 2017-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-25
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多