是否有可能使通用函数采用不同的案例类

时间:2019-06-20 22:08:10

标签: scala

最终更新/判决

令我惊讶的是,如果不使用第三方库,Scala就无法轻松解决此类问题。请注意,链接的重复问题无法满足以下要求。


原始问题

我是一个团队的成员,该团队最近继承了Scala项目,并希望使代码更加干燥。我有2个相同的函数,但是采用并返回不同的case类。 一个函数是否可以使用任一案例类?

我正在寻找使用标准Scala的解决方案,而无需安装第三方库。

我已经尝试了很多方法来抽象该函数,但都没有碰到运气(即使用泛型类型,使用Either的{​​{1}}类型接受两个案例类)。

这是一个虚拟的例子来说明我的问题:

asInstanceOf

更新

根据以下Mario的答案进行详细说明。如何消除重复条件逻辑的需要?:

trait Bird {
  val avgHeight: Int
}

case class Pigeon(avgHeight: Int) extends Bird
case class Ostrich(avgHeight: Int) extends Bird

def updateHeight(bird: ?): ? = {
  bird.copy(avgHeight = 2)
}

/*
def updateHeight[T <: Bird](bird: T): T = {
  val chosenBird = bird match {
    case _: Pigeon => bird.asInstanceOf[Pigeon]
    case _: Ostrich => bird.asInstanceOf[Ostrich]
  }

  chosenBird.copy(avgHeight = 2)
}
 */

println(updateHeight(Pigeon(1)))
println(updateHeight(Ostrich(1)))

我正在寻找一个真正的DRY示例,其中的主代码不需要重复:这是打字稿中的一个示例:https://repl.it/repls/PlayfulOverdueQuark

sealed trait Bird {
  val avgHeight: Int
  val avgWidth: Int
  val wingSpan: Int
}

case class Pigeon(avgHeight: Int, avgWidth: Int, wingSpan: Int) extends Bird
case class Ostrich(avgHeight: Int, avgWidth: Int, wingSpan: Int) extends Bird

def updateBird(bird: Bird, height: Int, span: Int): Bird = {
  bird match {
    case p: Pigeon =>
      var newPigeon = p.copy(avgHeight = height)

      if (p.avgWidth.equals(0)) {
        newPigeon = newPigeon.copy(avgWidth = 100)
      }

      if (span > 0) {
        newPigeon = newPigeon.copy(wingSpan = span * 2)
      }

      newPigeon
    case o: Ostrich =>
      var newOstrich = o.copy(avgHeight = height)

      if (o.avgWidth.equals(0)) {
        newOstrich = newOstrich.copy(avgWidth = 100)
      }

      if (span > 0) {
        newOstrich = newOstrich.copy(wingSpan = span * 2)
      }

      newOstrich
  }
}

updateBird(Pigeon(1, 2, 3), 2, 0) // Pigeon(2,2,3)
updateBird(Ostrich(1, 0, 3), 2, 4) // Ostrich(2,100,8)

1 个答案:

答案 0 :(得分:2)

以下是根据Harald Gliebe和Thilo的建议使用shapeless lenses的示例:

import shapeless._

object Hello extends App {
  sealed trait Bird {
    val avgHeight: Int
  }

  case class Pigeon(avgHeight: Int) extends Bird
  case class Ostrich(avgHeight: Int) extends Bird

  implicit val pigeonLens = lens[Pigeon].avgHeight
  implicit val ostrichLens = lens[Ostrich].avgHeight

  def updateHeight[T <: Bird](bird: T, height: Int)(implicit birdLense: Lens[T, Int]): T =
    birdLense.set(bird)(height)

  println(updateHeight(Pigeon(1), 2))
  println(updateHeight(Ostrich(1), 2))
}

输出

Pigeon(2)
Ostrich(2)

链接的打字稿示例使用可变状态来实现updateHeight,但是case类是不可变的结构。我们可以达到类似的目的

sealed trait Bird {
  val avgHeight: Int
}

case class Pigeon(avgHeight: Int) extends Bird
case class Ostrich(avgHeight: Int) extends Bird

def updateHeight(bird: Bird, height: Int): Bird =
  bird match {
    case _: Pigeon => Pigeon(height)
    case _: Ostrich => Ostrich(height)
  }

updateHeight(Pigeon(1), 2)
updateHeight(Ostrich(1), 2)

输出

res0: Bird = Pigeon(2)
res1: Bird = Ostrich(2)

请注意编译时间类型是Bird,而运行时类型是特殊的PigeonOstrich

如果问题实际上是关于如何对不可变的案例类进行突变,那么我们可以简单地使用copy来创建一个高度变化的新实例,

Pigeon(1).copy(avgHeight = 2)
Ostrich(1).copy(avgHeight = 2)

输出

res2: Pigeon = Pigeon(2)
res3: Ostrich = Ostrich(2)

但是,如果您想像打字稿示例中那样使用不可变状态,请尝试

class Bird(var avgHeight: Int)
class Pigeon(avgHeight: Int) extends Bird(avgHeight)
class Ostrich(avgHeight: Int) extends Bird(avgHeight)

def updateHeight(bird: Bird, height: Int): Bird = {
  bird.avgHeight = height
  bird
}