我是使用Shapeless的新手,我正在尝试使用Shapeless进行自动类型生成和折叠HLists。
我的目标是使用scalaz.Show的类型类实现将HList
呈现为(a, b, c, d)
我的第一步是使用以下代码在REPL中进行实验
import shapeless._
import shapeless.ops.hlist._
object prettyPrint extends Poly2 {
implicit def defaultCase[A] = at((a:A, z:String)=>s", ${a.toString}$z")
}
def print[H, T<:HList](f: H :: T)(implicit folder:RightFolder.Aux[H :: T, String, prettyPrint.type, String]) = {
f.foldRight("")(prettyPrint)
}
val f = 1::'a::2::'b::HNil
val res = s"(${f.head}${print(f.tail)})" // Results res: String = (1, 'a, 2, 'b)
在此之后,我在LabelledTypeClassCompanion[...]
的实现中实现了以下方法。不幸的是,这段代码没有编译,因为编译器抱怨缺少implicits,即使我不知道REPL中的代码和下面的代码之间有什么区别。我的问题是下面的代码中的问题是什么,我该如何解决?
def showFold[H, T<:HList](f: H::T)(implicit folder:RightFolder.Aux[ H::T, String, prettyPrint.type, String]) = {
f.foldRight("")(prettyPrint)
}
override def product[H, T <: HList](name: String, ch: ScalazShow[H], ct: ScalazShow[T]): ScalazShow[H :: T] = {
new ScalazShow[H :: T] {
override def shows(ft: (H :: T)): String = {
showFold(ft) // This does not compile
}
}
}
错误:(49,18)找不到参数文件夹的隐含值:shapeless.ops.hlist.RightFolder.Aux [shapeless。:: [H,T],String,com.fpinscala.ninetynine.prettyPrint.type ,串] showFold(ft)//这不编译
以下是完整的实施
package com.fpinscala.ninetynine
import shapeless._
import shapeless.ops.hlist.RightFolder
import scalaz.{Show => ScalazShow}
object prettyPrint extends Poly2 {
implicit def defaultCase[A]:this.Case.Aux[A, String, String] = at[A, String]{
(a,z) => s", $a$z"
}
}
object ShowImpl extends LabelledTypeClassCompanion[ScalazShow] {
implicit def symbolShow : ScalazShow[Symbol] = new ScalazShow[Symbol] {
override def shows(f: Symbol): String = f.toString()
}
implicit def intShow : ScalazShow[Int] = new ScalazShow[Int] {
override def shows(f: Int): String = f.toString
}
override val typeClass: LabelledTypeClass[ScalazShow] = new LabelledTypeClass[ScalazShow] {
override def coproduct[L, R <: Coproduct](name: String, cl: => ScalazShow[L], cr: => ScalazShow[R]): ScalazShow[L :+: R] = new ScalazShow[L :+: R] {
override def shows(lr: (L :+: R)): String = lr match {
case Inl(l) => cl.shows(l)
case Inr(r) => cr.shows(r)
}
}
override def emptyCoproduct: ScalazShow[CNil] = new ScalazShow[CNil] {
override def shows(f: CNil): String = ""
}
def showFold[H, T<:HList](f: H::T)(implicit folder:RightFolder.Aux[ H::T, String, prettyPrint.type, String]) = {
f.foldRight("")(prettyPrint)
}
override def product[H, T <: HList](name: String, ch: ScalazShow[H], ct: ScalazShow[T]): ScalazShow[H :: T] = {
new ScalazShow[H :: T] {
override def shows(ft: (H :: T)): String = {
showFold(ft) // This does not compile
}
}
}
override def project[F, G](instance: => ScalazShow[G], to: (F) => G, from: (G) => F): ScalazShow[F] = new ScalazShow[F] {
override def shows(f: F): String = instance.shows(to(f))
}
override def emptyProduct: ScalazShow[HNil] = new ScalazShow[HNil] {
override def shows(f: HNil): String = ""
}
}
}
答案 0 :(得分:6)
您可以将类型类约束视为将某些类型的信息从具体上下文传递到泛型上下文(向后移动通过调用堆栈)的一种方法。在这种情况下,如果你想以这种方式编写实现,你实际上确实需要RightFolder
实例,但是LabelledTypeClass
中的方法签名不允许你通过这些信息,所以你和#39;运气不好(虽然基本的想法是可能的 - 你只需要稍微不同的方法)。
我刚刚意识到我误解了你的问题 - 因为你使用的是TypeClass
类型我假设你想要案例类和密封特征层次结构的实例以及hlists和coproducts。我的回答为你提供了所有这些(就像TypeClass
那样),所以你可以这样写:
scala> (123 :: "abc" :: HNil).shows
res2: String = (123, abc)
以及下面给出的案例类和密封特征示例。如果您不想要案例类和密封特征,则可以删除genericShow
定义。
这是一个更简单的案例。假设我们想使用Show
两次打印一个值。我们可以这样做:
scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._
scala> val x: Int = 123
x: Int = 123
scala> s"${ x.shows }${ x.shows }"
res0: String = 123123
这里x
有一个具体的类型,当我们在其上调用.shows
时,编译器将尝试为该具体类型找到Show
的实例。 Scalaz提供Show[Int]
,所以一切正常,我们得到了我们想要的结果。
接下来我们可以尝试编写通用版本:
def toStringTwice[X](x: X): String = s"${ x.shows }${ x.shows }"
但编译器会抱怨:
<console>:18: error: value shows is not a member of type parameter X
def toStringTwice[X](x: X): String = s"${ x.shows }${ x.shows }"
^
这是因为编译器无法证明X
有Show
个实例,因为它对X
一无所知。你可以写一堆重载的具体方法:
scala> def toStringTwice(x: String): String = s"${ x.shows }${ x.shows }"
toStringTwice: (x: String)String
scala> def toStringTwice(x: Int): String = s"${ x.shows }${ x.shows }"
toStringTwice: (x: Int)String
...
但这正是那种烦人的样板,类型类旨在帮助您避免。您可以通过为编译器提供所需的完全相同的信息来抽象它们,而不是枚举您拥有Show
个实例的所有类型:
scala> def toStringTwice[X: Show](x: X): String = s"${ x.shows }${ x.shows }"
toStringTwice: [X](x: X)(implicit evidence$1: scalaz.Show[X])String
现在您可以使用Int
或其他任何具有Show
实例的内容来调用它:
scala> toStringTwice(123)
res2: String = 123123
您可以做的是用另一种不受约束的泛型类型调用它:
def toStringFourTimes[X](x: X): String = s"${ toStringTwice(x) * 2 }"
相反,您必须再次添加约束:
scala> def toStringFourTimes[X: Show](x: X): String = s"${ toStringTwice(x) * 2 }"
toStringFourTimes: [X](x: X)(implicit evidence$1: scalaz.Show[X])String
依此类推 - 你必须一直带着Show
约束,直到你得到具体的类型。您只能以两种方式使用toStringTwice
:具有Show
实例的具体类型,或具有Show
约束的泛型类型。
请注意,以上都不是Shapeless特有的 - 这只是类型类的工作方式。
不幸的是,对于LabelledTypeClass
而言,这似乎不是一个非常好的用例,因为所需的实例并不真正适合构建TypeClass
的实例的方式类类支持。你可能会这样做,但我真的不想尝试。
您prettyPrint
的工作方式也存在问题 - 它实际上没有使用Show
的{{1}}实例(其中没有甚至可以使用一个),而是调用可怕的通用A
。
以下是我可能会写这篇文章的快速初稿:
toString
然后:
import scalaz.Show, scalaz.Scalaz._
import shapeless._
import shapeless.ops.coproduct.Folder
import shapeless.ops.hlist.RightReducer
object prettyPrint2 extends Poly2 {
implicit def defaultCase[A: Show]: Case.Aux[A, String, String] =
at[A, String]((a, z) => s"$a, $z")
}
object prettyPrint extends Poly1 {
implicit def defaultCase[A: Show]: Case.Aux[A, String] = at[A](_.shows)
}
implicit def hlistShow[L <: HList](implicit
reducer: RightReducer.Aux[L, prettyPrint2.type, String]
): Show[L] = Show.shows(l => "(" + l.reduceRight(prettyPrint2) + ")")
implicit def coproductShow[C <: Coproduct](implicit
folder: Folder.Aux[prettyPrint.type, C, String]
): Show[C] = Show.shows(_.fold(prettyPrint))
implicit def genericShow[A, R](implicit
gen: Generic.Aux[A, R],
reprShow: Show[R]
): Show[A] = reprShow.contramap(gen.to)
您可能遇到涉及嵌套案例类等的极端案例,因为编译器错误而无法工作(有关详细信息,请参阅我的slides here关于Scala中的泛型派生),但这种方法应该更多或者更少做你想做的事。