scala - 使用 Shapeless 进行泛型类型转换

标签 scala generic-programming shapeless

我目前开始尝试 Shapeless。我的第一次尝试是以下代码示例。 Shapeless 版本是 2.3.0,Scala 版本是 2.11.7:

import org.scalatest._
import shapeless._

sealed trait Dog {
  def favoriteFood: String
}

sealed trait Cat{
  def isCute: Boolean
}

sealed trait Green

sealed trait Blue[G <: Green]{
  def makeGreen(): G = {
    val blueGen = LabelledGeneric[this.type]
    val greenGen = LabelledGeneric[G]
    val blue = blueGen.to(this)
    val green = greenGen.from(blue)
    green
  }
}

case class BlueDog(override val favoriteFood: String) extends Dog with Blue[GreenDog]
case class GreenDog(override val favoriteFood: String) extends Dog with Green

case class GreenCat(override val isCute: Boolean) extends Cat with Green
case class BlueCat(override val isCute: Boolean) extends Cat with Blue[GreenCat]

class ShapelessExperimentsTest extends FlatSpec with Matchers {

  "Make green" should "work" in {
    val blueDog = new BlueDog("Bones")
    val greenDog: GreenDog = blueDog.makeGreen
    assert(greenDog.favoriteFood == "Bones")

    val blueCat = new BlueCat(true)
    val greenCat: GreenCat = blueCat.makeGreen
    assert(greenCat.isCute)
  }
}

此代码无法编译,因为我没有为 LabelledGenerics 的隐式参数 lgen 提供值。因此编译错误为

...ShapelessExperimentsTest.scala:16: could not find implicit value for parameter lgen: shapeless.LabelledGeneric[Blue.this.type]

...ShapelessExperimentsTest.scala:17: could not find implicit value for parameter lgen: shapeless.LabelledGeneric[G]

我的问题是我找不到提供这些隐式以使示例正常工作的正确方法。谁能帮我解决这个问题吗?

最佳答案

可悲的是,在 Scala 中,每一个“通用的位” -ness”必须在调用站点修复,这基本上意味着:

  val blueGen = LabelledGeneric[this.type]
  val greenGen = LabelledGeneric[G]

函数体内无法编译,因为编译器无法确定“this.type”和“G”。幸运的是,语言中的隐式正是为了解决这个问题:

def makeGreen[T](implicit blueGen: LabelledGeneric.Aux[this.type, T], greenGen: LabelledGeneric.Aux[G, T]):

(Aux 只是一种对类型进行计算的模式,如果您不熟悉的话,我强烈建议您阅读 this 文章)

不过,此代码无法编译可能是因为编译器无法推断 this.type 实际上是一个 case 类,并且无法找到隐式 LabelledGeneric 实例。

相反,您可以将代码重构为如下所示:

import org.scalatest._
import shapeless._

sealed trait Animal

sealed trait Dog extends Animal {
  def favoriteFood: String
}

sealed trait Cat extends Animal {
  def isCute: Boolean
}

sealed trait Color
sealed trait Green extends Color
sealed trait Blue extends Color

trait GreenColorable[A <: Animal, G <: Green] {
  def makeGreen[T](animal: A)(implicit animalGen: LabelledGeneric.Aux[A, T], greenGen: LabelledGeneric.Aux[G, T]): G = {
    val blue = animalGen.to(animal)
    val green = greenGen.from(blue)

    green
  }
}

object Colorables {
  def GreenColorable[A <: Animal, G <: Green] = new GreenColorable[A, G] {}
}

case class BlueDog(override val favoriteFood: String) extends Dog with Blue
case class GreenDog(override val favoriteFood: String) extends Dog with Green

case class GreenCat(override val isCute: Boolean) extends Cat with Green
case class BlueCat(override val isCute: Boolean) extends Cat with Blue

class ShapelessExperimentsTest extends FlatSpec with Matchers {

  "Make green" should "work" in {
    val blueDog = new BlueDog("Bones")
    val greenDogColorable= Colorables.GreenColorable[BlueDog, GreenDog]
    val greenDog = greenDogColorable.makeGreen(blueDog)
    assert(greenDog.favoriteFood == "Bones")

    val blueCat = new BlueCat(true)
    val greenCatColorable = Colorables.GreenColorable[BlueCat, GreenCat]
    val greenCat: GreenCat = greenCatColorable.makeGreen(blueCat)
    assert(greenCat.isCute)
  }
}

在这里,实际的转换被移动到一个单独的类型类中,该类型类将实际的案例类输入和输出类型作为参数。

关于scala - 使用 Shapeless 进行泛型类型转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37355852/

相关文章:

c# - 为什么 "as T"会出错,但使用 (T) 进行转换不会出错?

java - 为什么我不能将 B 父类(super class)的对象放入 Container<? super B>?

azure - az synapse Spark 作业提交

haskell - 使用 Typeable 在运行时部分应用函数(任何类型匹配的时间)

scala - Scala 中类型不相等的证据

scala - 由类的方法返回类型参数化的方法

scala - 如何使用 shapeless 在 Scala 列表中拆分

scala - 带有多对多表的光滑嵌套外连接

scala - 如何将()注入(inject)到具有构造函数参数的类中?

scala - opencv 3.0.0 java imread_0 未定义