根据这篇博客文章,在Scala中,“type classes”只是一个用特质和隐式适配器实现的“模式”。
正如该博客所说,如果我有特质A
和一个适配器B -> A
,那么我可以使用类型为B
的参数调用需要参数类型为A
的函数,而无需显式地调用此适配器。
我觉得这很好,但并不特别有用。你能否给出一个使用案例/示例,展示这个功能有什么用处?
根据这篇博客文章,在Scala中,“type classes”只是一个用特质和隐式适配器实现的“模式”。
正如该博客所说,如果我有特质A
和一个适配器B -> A
,那么我可以使用类型为B
的参数调用需要参数类型为A
的函数,而无需显式地调用此适配器。
我觉得这很好,但并不特别有用。你能否给出一个使用案例/示例,展示这个功能有什么用处?
一个使用案例,如要求...
想象一下你有一个东西列表,可能是整数、浮点数、矩阵、字符串、波形等。鉴于这个列表,你想要添加它们的内容。
其中一种方法是拥有一些必须被每个可以相加的类型继承的 Addable
特征,或者如果处理来自第三方库的对象,无法修改接口,则隐式转换为 Addable
。
当您还想开始添加其他可对对象列表执行的操作时,此方法很快变得不堪重负。如果您需要替代方案(例如:将两个波形相加会将它们串联起来还是覆盖在一起?)那么这也不起作用。解决方案是使用临时多态性,其中您可以选择和选择要适用于现有类型的行为。
对于原始问题,您可以实施一个 Addable
类型类:
trait Addable[T] {
def zero: T
def append(a: T, b: T): T
}
//yup, it's our friend the monoid, with a different name!
implicit object IntIsAddable extends Addable[Int] {
def zero = 0
def append(a: Int, b: Int) = a + b
}
implicit object StringIsAddable extends Addable[String] {
def zero = ""
def append(a: String, b: String) = a + b
}
//etc...
def sum[T](xs: List[T])(implicit addable: Addable[T]) =
xs.FoldLeft(addable.zero)(addable.append)
//or the same thing, using context bounds:
def sum[T : Addable](xs: List[T]) = {
val addable = implicitly[Addable[T]]
xs.FoldLeft(addable.zero)(addable.append)
}
顺便说一下,这正是2.8集合API采用的方法。 虽然sum
方法是在TraversableLike
上定义的,而不是在List
上定义的,类型类是Numeric
(它还包含一些不仅仅是zero
和append
的操作)。
case class Default[T](val default: T)
object Default {
implicit def IntDefault: Default[Int] = Default(0)
implicit def OptionDefault[T]: Default[Option[T]] = Default(None)
...
}
A
不必知道自己是某个类型类的成员,而且它可以被添加到新的类型类中,而不需要修改 A
本身。这与在 Java 中使用常规接口不同,你必须让 A
实现该接口。 - JesperMap[Int, List[String]]
或Map[String, Set[Int]]
。向这些集合添加内容可能会很冗长:map += key -> (value :: map.getOrElse(key, List()))
map +++= key -> value
trait Addable[C, CC] {
def add(c: C, cc: CC) : CC
def empty: CC
}
object Addable {
implicit def listAddable[A] = new Addable[A, List[A]] {
def empty = Nil
def add(c: A, cc: List[A]) = c :: cc
}
implicit def addableAddable[A, Add](implicit cbf: CanBuildFrom[Add, A, Add]) = new Addable[A, Add] {
def empty = cbf().result
def add(c: A, cc: Add) = (cbf(cc) += c).result
}
}
我定义了一个类型类Addable
,可以将元素C添加到集合CC中。我有两个默认实现:对于使用::
的列表和其他集合,使用构建器框架。
然后使用这个类型类是:
class RichCollectionMap[A, C, B[_], M[X, Y] <: collection.Map[X, Y]](map: M[A, B[C]])(implicit adder: Addable[C, B[C]]) {
def updateSeq[That](a: A, c: C)(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That = {
val pair = (a -> adder.add(c, map.getOrElse(a, adder.empty) ))
(map + pair).asInstanceOf[That]
}
def +++[That](t: (A, C))(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That = updateSeq(t._1, t._2)(cbf)
}
implicit def toRichCollectionMap[A, C, B[_], M[X, Y] <: col
adder.add
添加元素和 adder.empty
为新的键创建新集合。addElementToSubList
和 addElementToSet
等。这会在实现中创建大量样板,并污染命名空间。
2. 使用反射确定子集合是否为 List / Set。这很棘手,因为映射一开始是空的(当然,Scala 在这里也有帮助,使用 Manifests)。
3. 通过要求用户提供 adder 来拥有穷人版的类型类。因此,像 addToMap(map, key, value, adder)
这样的东西,非常丑陋。我发现这篇博客文章有帮助的另一种方式是它描述了类型类: Monads Are Not Metaphors
在文章中搜索“typeclass”,应该是第一个匹配项。在本文中,作者提供了一个Monad类型类的示例。
论坛帖子 "什么使类型类比特质更好?" 提出了一些有趣的观点:
- 类型类可以非常容易地表示在子类型存在时很难表示的概念,例如相等性和排序。
练习:创建一个小的类/特质层次结构,并尝试以这样一种方式实现.equals
,即对来自层次结构中任意实例的操作都是适当的反射性、对称性和传递性。- 类型类允许您提供证据表明类型在您“控制”之外符合某些行为。
别人的类型可以成为您的类型类成员。- 您无法用子类型来表达“此方法接受/返回与方法接收器相同类型的值”,但是使用类型类可以轻松实现此(非常有用的)约束。这是f-bounded types问题(其中F-bounded类型是参数化其自身的子类型)。
- 在特质上定义的所有操作都需要一个实例;始终存在一个
this
参数。因此,您无法以这样的方式在trait Foo
上定义一个fromString(s:String): Foo
方法,以便可以在没有Foo
实例的情况下调用它。
在Scala中,这表现为人们拼命尝试抽象伴生对象。
但是使用类型类很容易,正如此单子群示例中的零元素所示。- 类型类可以归纳定义;例如,如果您有一个
JsonCodec[Woozle]
,则可以免费获取JsonCodec[List[Woozle]]
。
上面的示例说明了“您可以相加的东西”。
无论是隐式转换还是类型类都用于类型转换。两者的主要用例是为您无法修改但需要继承类型多态的类提供特设多态。在隐式转换中,您既可以使用隐式def也可以使用隐式类(这是您的包装类,但对客户端隐藏)。类型类更强大,因为它们可以向已存在的继承链添加功能(例如:scala的sort函数中的Ordering[T])。 有关详细信息,请参见https://lakshmirajagopalan.github.io/diving-into-scala-typeclasses/
行为可以在以下情况下扩展:
Scala隐式参数:
方法的最后一个参数列表可以标记为implicit
编译器会填充隐式参数
实际上,你需要编译器提供的证据
例如,范围内存在类型类的存在
如果需要,您还可以显式指定参数
下面的示例对String类进行扩展,并使用类型类实现扩展该类的新方法,即使string是final也可以扩展 :)
/**
* Created by nihat.hosgur on 2/19/17.
*/
case class PrintTwiceString(val original: String) {
def printTwice = original + original
}
object TypeClassString extends App {
implicit def stringToString(s: String) = PrintTwiceString(s)
val name: String = "Nihat"
name.printTwice
}