使用 Shapeless 的通用类型转换
Generic type transformations with 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 实际上是一个案例 class 并且找不到隐式的 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)
}
}
在这里,实际的转换被移动到一个单独的类型class中,它将实际案例class输入和输出类型作为参数。
我目前开始尝试使用 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 实际上是一个案例 class 并且找不到隐式的 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)
}
}
在这里,实际的转换被移动到一个单独的类型class中,它将实际案例class输入和输出类型作为参数。