我有一组adt,如下面提到的Person
和一组隐式类实现(在我的情况下)。这一切都很好,直到我试图在一个方法中抽象一个共同的代码块,比如在本例中是display
。然而,不确定如何实现这一目标。我确实理解以下错误,但我想知道是否有办法实现这一目标,如果可能的话。请建议。TIA。
隐式类实现
case class Person(firstName: String, lastName: String)
trait CustomStringifier {
def stringify(): String
}
implicit class PersonCustomStringifier(person: Person) extends CustomStringifier {
def stringify(): String = s"My name is ${person.firstName} ${person.lastName}."
}
def display[T](obj: T): Unit = {
println(obj.stringify())
}
display(Person("John", "Doe"))
这一次我正确地得到编译时错误在行println(obj.stringify)
:Cannot resolve symbol stringify
。
类型类实现
case class Person(firstName: String, lastName: String)
trait CustomStringifier[T] {
def stringify(x: T): String
}
implicit object PersonCustomStringifier extends CustomStringifier[Person] {
def stringify(person: Person): String =
s"My name is ${person.firstName} ${person.lastName}."
}
implicit class Stringifier[T](x: T) {
def stringify(implicit stringifier: CustomStringifier[T]): String =
stringifier.stringify(x)
}
def display[T](obj: T): Unit = {
println(obj.stringify)
}
println(Person("John", "Doe").stringify)
我正确地在println(obj.stringify)
:NO implicits found for parameter stringifier: CustomStringifier[T]
行得到编译时错误。
注:如果我需要重新定义这个问题,让它更有意义,请告诉我。虽然,我尝试添加多个工作代码片段来指示我真正需要实现的目标。
类型类是在编译时解析的,所以当你看到这个:
Person("John", "Doe").stringify
编译器生成如下:
new Stringifier(Person("John", "Doe")).stringify(PersonCustomStringifier)
因为它知道T=Person
,它需要CustomStringifier[T]
从隐式作用域,并且PersonCustomStringifier
是这样隐式的。
在stringify
方法你有:
def stringify(implicit stringifier: CustomStringifier[T]): String =
stringifier.stringify(x)
编译器不知道T
类型,但它知道它有一些CustomStringifier[T]
可以在隐式作用域中找到-因为您将其作为隐式参数传递。然后,学习T
和解析相关CustomStringfier[T]
的责任被委托/延迟给调用者(如果它还使用类型参数,调用者可能会进一步委托)。
当你写:
def display[T](obj: T): Unit = {
println(obj.stringify)
}
T
是未知的。范围内没有CustomSerializer[T]
,只有PersonCustomStringifier
-但是我们没有证据证明T=Person
,所以我们不能使用它。这就是编译失败的原因。
要解决这个问题,你必须采用隐式参数,将解析推迟给调用者
def display[T](obj: T)(implicit stringify: CustomStringifier[T]): Unit =
println(obj.stringify)
可以缩写为
def display[T: CustomStringifier](obj: T): Unit =
println(obj.stringify)
那么当你打电话的时候
display(Person("John", "Doe"))
编译器将知道T=Person
,并且能够计算出PersonCustomStringifier
是作为隐式实参传递的正确隐式。
首先,简而言之,typeclass是一个泛型函数,它通过某种机制(在我们的例子中是隐式搜索机制)从有限数量的具体函数中解析其实现。在scala中,它由4个组件组成:
- 函数接口声明(trait)
- 对象,它提供了类似func(smith)的语法,是trait 的伴侣。
- 有限实现集-隐式值
- (可选)扩展,提供smth。函数的语法。
隐式的一般用法如下:
trait YourTypeclass[T] {
def doStuff(t: T): R
}
object YourTypeclass {
def apply[T](t: T)(implicit tInstance: YourTypeclass[T]): R =
tInstance.doStuff(t)
}
implicit YourTypeclassSyntax[T](val t: T) {
def doStuff[T](implicit tInstance: YourTypeclass[T]): R = tInstance.doStuff(t)
}
实现:
case class YourClass()
object YourClass {
implicit val yourTypeclassInstance: YourTypeclass[T] = {t => ???: R}
}
用法:
val x: YourClass = ???
YourTypeclass(x)
x.doStuff
implicitly[YourTypeclass[T]].doStuff(t)
那么为什么把实例放在伴侣对象上很重要呢?因为隐含的搜索顺序。在同伴对象中定义的实例,如果它们不受private[package]
(隐式优先级)的限制,则可以从项目的每个地方使用。
你也可以通过把隐式实例放在trait中来控制你想要使用的隐式实例,然后更深的隐式是,然后优先级更低(对于泛型和覆盖很有用,比如List[T]
vsList[Int]
)。
第二,对于您的特殊场合,Show[T]
from cats存在。
也有简短的解释,有例子:https://typelevel.org/cats/typeclasses.html