Scala 中的类型类有什么用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5408861/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 02:55:33  来源:igfitidea点击:

What are type classes in Scala useful for?

scalaimplicit

提问by Michael

As I understand from this blog post"type classes" in Scala is just a "pattern" implemented with traits and implicit adapters.

正如我从这篇博客文章中了解到的 ,Scala 中的“类型类”只是一个用特征和隐式适配器实现的“模式”。

As the blog says if I have trait Aand an adapter B -> Athen I can invoke a function, which requires argument of type A, with an argument of type Bwithout invoking this adapter explicitly.

正如博客所说,如果我有 traitA和一个适配器,B -> A那么我可以调用一个函数,该函数需要 typeA参数,B而不显式调用这个适配器。

I found it nice but not particularly useful. Could you give a use case/example, which shows what this feature is useful for ?

我发现它很好,但不是特别有用。你能给出一个用例/例子,说明这个功能有什么用吗?

回答by Kevin Wright

One use case, as requested...

一个用例,根据要求...

Imagine you have a list of things, could be integers, floating point numbers, matrices, strings, waveforms, etc. Given this list, you want to add the contents.

想象你有一个列表,可以是整数、浮点数、矩阵、字符串、波形等。给定这个列表,你想添加内容。

One way to do this would be to have some Addabletrait that must be inherited by every single type that can be added together, or an implicit conversion to an Addableif dealing with objects from a third party library that you can't retrofit interfaces to.

一种方法是拥有一些Addable必须由可以添加在一起的每个单一类型继承的特征,或者隐式转换为Addableif 处理来自第三方库的对象,您无法对其进行接口改造。

This approach becomes quickly overwhelming when you also want to begin adding other such operations that can be done to a list of objects. It also doesn't work well if you need alternatives (for example; does adding two waveforms concatenate them, or overlay them?) The solution is ad-hoc polymorphism, where you canpick and chose behaviour to be retrofitted to existing types.

当您还想开始添加其他可以对对象列表执行的此类操作时,这种方法很快就会变得难以抗拒。如果您需要替代方案(例如,添加两个波形是将它们连接起来还是叠加它们?),它也不能很好地工作?解决方案是即席多态性,您可以在其中挑选行为以改进现有类型。

For the original problem then, you could implement an Addabletype class:

对于最初的问题,您可以实现一个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!

You can then create implicit subclassed instances of this, corresponding to each type that you wish to make addable:

然后,您可以创建它的隐式子类实例,对应于您希望添加的每种类型:

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...

The method to sum a list then becomes trivial to write...

对列表求和的方法然后变得很容易写......

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)
}

The beauty of this approach is that you can supply an alternative definition of some typeclass, either controlling the implicit you want in scope via imports, or by explicitly providing the otherwise implicit argument. So it becomes possible to provide different ways of adding waveforms, or to specify modulo arithmetic for integer addition. It's also fairly painless to add a type from some 3rd-party library to your typeclass.

这种方法的美妙之处在于您可以提供某个类型类的替代定义,或者通过导入控制您想要的隐式范围,或者通过显式提供其他隐式参数。因此,可以提供不同的波形相加方式,或者为整数相加指定模运算。将某个 3rd 方库中的类型添加到您的类型类中也相当轻松。

Incidentally, this is exactly the approach taken by the 2.8 collections API. Though the summethod is defined on TraversableLikeinstead of on List, and the type class is Numeric(it also contains a few more operations than just zeroand append)

顺便说一下,这正是 2.8 集合 API 所采用的方法。虽然sum方法是在 onTraversableLike而不是 on定义的List,并且类型类是Numeric(它还包含一些比zeroand 的更多操作append

回答by Alexey Romanov

Reread the first comment there:

重读那里的第一条评论:

A crucial distinction between type classes and interfaces is that for class A to be a "member" of an interface it must declare so at the site of its own definition. By contrast, any type can be added to a type class at any time, provided you can provide the required definitions, and so the members of a type class at any given time are dependent on the current scope. Therefore we don't care if the creator of A anticipated the type class we want it to belong to; if not we can simply create our own definition showing that it does indeed belong, and then use it accordingly. So this not only provides a better solution than adapters, in some sense it obviates the whole problem adapters were meant to address.

类型类和接口之间的一个重要区别是,要使类 A 成为接口的“成员”,它必须在其自己定义的位置进行声明。相比之下,任何类型都可以随时添加到类型类中,前提是您可以提供所需的定义,因此在任何给定时间类型类的成员都依赖于当前作用域。因此,我们不关心 A 的创建者是否预料到了我们希望它属于的类型类;如果不是,我们可以简单地创建我们自己的定义,表明它确实属于,然后相应地使用它。因此,这不仅提供了比适配器更好的解决方案,而且在某种意义上它消除了适配器要解决的整个问题。

I think this is the most important advantage of type classes.

我认为这是类型类最重要的优势。

Also, they handle properly the cases where the operations don't have the argument of the type we are dispatching on, or have more than one. E.g. consider this type class:

此外,它们可以正确处理操作没有我们正在调度的类型的参数或有多个参数的情况。例如考虑这个类型类:

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)

  ...
}

回答by IttayD

I think of type classes as the ability to add type safe metadata to a class.

我认为类型类是向类添加类型安全元数据的能力。

So you first define a class to model the problem domain and then think of metadata to add to it. Things like Equals, Hashable, Viewable, etc. This creates a separation of the problem domain and the mechanics to use the class and opens up subclassing because the class is leaner.

因此,您首先定义一个类来为问题域建模,然后考虑要添加到其中的元数据。诸如 Equals、Hashable、Viewable 之类的东西。这将问题域和使用类的机制分开,并开放子类化,因为类更精简。

Except for that, you can add type classes anywhere in the scope, not just where the class is defined and you can change implementations. For example, if I calculate a hash code for a Point class by using Point#hashCode, then I'm limited to that specific implementation which may not create a good distribution of values for the specific set of Points I have. But if I use Hashable[Point], then I may provide my own implementation.

除此之外,您可以在范围内的任何位置添加类型类,而不仅仅是定义类的位置,您还可以更改实现。例如,如果我使用 Point#hashCode 计算 Point 类的哈希码,那么我仅限于该特定实现,它可能无法为我拥有的特定 Points 集创建良好的值分布。但是如果我使用 Hashable[Point],那么我可以提供我自己的实现。

[Updated with example] As an example, here's a use case I had last week. In our product there are several cases of Maps containing containers as values. E.g., Map[Int, List[String]]or Map[String, Set[Int]]. Adding to these collections can be verbose:

[更新示例] 举个例子,这是我上周的一个用例。在我们的产品中,有几种 Maps 包含容器作为值的情况。例如,Map[Int, List[String]]Map[String, Set[Int]]。添加到这些集合可能很冗长:

map += key -> (value :: map.getOrElse(key, List()))

So I wanted to have a function that wraps this so I could write

所以我想有一个函数来包装它,这样我就可以写

map +++= key -> value

The main issue is that the collections don't all have the same methods for adding elements. Some have '+' while others ':+'. I also wanted to retain the efficiency of adding elements to a list, so I didn't want to use fold/map which create new collections.

主要问题是集合并不都具有相同的添加元素的方法。有些有“+”,而另一些有“:+”。我还想保留将元素添加到列表的效率,所以我不想使用创建新集合的 fold/map。

The solution is to use type classes:

解决方案是使用类型类:

  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
    }
  }

Here I defined a type class Addablethat can add an element C to a collection CC. I have 2 default implementations: For Lists using ::and for other collections, using the builder framework.

这里定义了一个类型类Addable,可以将元素C添加到集合CC中。我有 2 个默认实现:对于列表使用::和其他集合,使用构建器框架。

Then using this type class is:

然后使用这个类型类是:

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

The special bit is using adder.addto add the elements and adder.emptyto create new collections for new keys.

特殊位adder.add用于添加元素并adder.empty为新键创建新集合。

To compare, without type classes I would have had 3 options: 1. to write a method per collection type. E.g., addElementToSubListand addElementToSetetc. This creates a lot of boilerplate in the implementation and pollutes the namespace 2. to use reflection to determine if the sub collection is a List / Set. This is tricky as the map is empty to begin with (of course scala helps here also with Manifests) 3. to have poor-man's type class by requiring the user to supply the adder. So something like addToMap(map, key, value, adder), which is plain ugly

为了比较,如果没有类型类,我将有 3 个选择: 1. 为每个集合类型编写一个方法。例如,addElementToSubListaddElementToSet等,这创造了很多样板的实施和污染命名空间2.使用反射来确定该子集是一个列表/设置键。这很棘手,因为地图一开始是空的(当然,scala 在这里也有助于 Manifests) 3. 通过要求用户提供加法器来拥有穷人的类型类。所以像addToMap(map, key, value, adder),这很丑陋

回答by Bradford

Yet another way I find this blog post helpful is where it describes typeclasses: Monads Are Not Metaphors

我发现这篇博客文章有用的另一种方式是它描述了类型类: Monads Are Not Metaphors

Search the article for typeclass. It should be the first match. In this article, the author provides an example of a Monad typeclass.

在文章中搜索 typeclass。这应该是第一场比赛。在本文中,作者提供了一个 Monad 类型类的示例。

回答by VonC

The forum thread "What makes type classes better than traits?" makes some interesting points:

论坛主题“是什么让类型类比特征更好?”提出了一些有趣的观点:

  • Typeclasses can very easily represent notions that are quite difficult to represent in the presence of subtyping, such as equalityand ordering.
    Exercise: create a small class/trait hierarchy and try to implement .equalson each class/trait in such a way that the operation over arbitrary instances from the hierarchy is properly reflexive, symmetric, and transitive.
  • Typeclasses allow you to provide evidence that a type outside of your "control" conforms with some behavior.
    Someone else's type can be a member of your typeclass.
  • You cannot express "this method takes/returns a value of the same type as the method receiver" in terms of subtyping, but this (very useful) constraint is straightforward using typeclasses. This is the f-bounded types problem(where an F-bounded type is parameterized over its own subtypes).
  • All operations defined on a trait require an instance; there is always a thisargument. So you cannot define for example a fromString(s:String): Foomethod on trait Fooin such a way that you can call it without an instance of Foo.
    In Scala this manifests as people desperately trying to abstract over companion objects.
    But it is straightforward with a typeclass, as illustrated by the zero element in this monoid example.
  • Typeclasses can be defined inductively; for example, if you have a JsonCodec[Woozle]you can get a JsonCodec[List[Woozle]]for free.
    The example above illustrates this for "things you can add together".
  • 类型类可以很容易地表示在存在子类型的情况下很难表示的概念,例如等式排序
    练习:创建一个小的类/特征层次结构,并尝试.equals在每个类/特征上实现,以使对层次结构中任意实例的操作具有适当的自反性、对称性和可传递性。
  • 类型类允许您提供证据证明“控制”之外的类型符合某些行为。
    其他人的类型可以是您的类型类的成员。
  • 你不能在子类型方面表达“这个方法接受/返回一个与方法接收者相同类型的值”,但是这个(非常有用的)约束使用类型类很简单。这是f 有界类型问题(其中 F 有界类型在其自己的子类型上参数化)。
  • 在 trait 上定义的所有操作都需要一个实例;总是有this争论。因此,您不能定义例如fromString(s:String): Foo方法 ontrait Foo的方式,即您可以在没有 的实例的情况下调用它Foo
    在 Scala 中,这表现为人们拼命地尝试对伴生对象进行抽象。
    但是对于类型类来说它很简单,如这个 monoid 示例中的零元素所示
  • 可以归纳地定义类型类;例如,如果您有一个,JsonCodec[Woozle]您可以JsonCodec[List[Woozle]]免费获得一个。
    上面的例子说明了“你可以添加在一起的东西”。

回答by Jonathan Warden

One way to look at type classes is that they enable retroactive extensionor retroactive polymorphism. There are a couple of great posts by Casual Miraclesand Daniel Westheidethat show examples of using Type Classes in Scala to achieve this.

查看类型类的一种方法是它们启用追溯扩展追溯多态性Casual MiraclesDaniel Westheide发表了几篇很棒的文章,展示了在 Scala 中使用类型类来实现这一目标的示例。

Here's a post on my blogthat explores various methods in scala of retroactive supertyping, a kind of retroactive extension, including a typeclass example.

这是我博客上的一篇文章,探讨了追溯超类型的Scala 中的各种方法,这是一种追溯扩展,包括一个类型类示例。

回答by lisak

I don't know of any other use case than Ad-hoc polymorhismwhich is explained herethe best way possible.

我不知道除Ad-hoc 多态性之外的任何其他用例,此处以最佳方式对其进行了解释。

回答by Lakshmi Rajagopalan

Both implicitsand typeclassesare used for Type-conversion. The major use-case for both of them is to provide ad-hoc polymorphism(i.e) on classes that you can't modify but expect inheritance kind of polymorphism. In case of implicits you could use both an implicit def or an implicit class (which is your wrapper class but hidden from the client). Typeclasses are more powerful as they can add functionality to an already existing inheritance chain(eg: Ordering[T] in scala's sort function). For more detail you can see https://lakshmirajagopalan.github.io/diving-into-scala-typeclasses/

无论implicits类型类用于类型转换。它们的主要用例是在您无法修改但期望继承类型的多态性的类上提供临时多态性(即)。在隐式的情况下,您可以同时使用隐式 def 或隐式类(这是您的包装类,但对客户端隐藏)。类型类更强大,因为它们可以向已经存在的继承链添加功能(例如:scala 的排序函数中的 Ordering[T])。有关更多详细信息,您可以查看https://lakshmirajagopalan.github.io/diving-into-scala-typeclasses/

回答by Nihat Hosgur

In scala type classes

在 Scala 类型类中

  • Enables ad-hoc polymorphism
  • Statically typed (i.e. type-safe)
  • Borrowed from Haskell
  • Solves the expression problem
  • 启用临时多态性
  • 静态类型(即类型安全)
  • 从 Haskell 借来的
  • 解决表达问题

Behavior can be extended - at compile-time - after the fact - without changing/recompiling existing code

行为可以扩展 - 在编译时 - 事后 - 无需更改/重新编译现有代码

Scala Implicits

Scala 隐式

The last parameter list of a method can be marked implicit

方法的最后一个参数列表可以标记为隐式

  • Implicit parameters are filled in by the compiler

  • In effect, you require evidence of the compiler

  • … such as the existence of a type class in scope

  • You can also specify parameters explicitly, if needed

  • 隐式参数由编译器填充

  • 实际上,您需要编译器的证据

  • ...例如在作用域中存在类型类

  • 如果需要,您还可以显式指定参数

Below Example extension on String class with type class implementation extends the class with a new methods even though string is final :)

下面的示例扩展具有类型类实现的 String 类使用新方法扩展了类,即使字符串是最终的 :)

/**
* 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
}

回答by Glenn

I like to use type classes as a lightweight Scala idiomatic form of Dependency Injection that still works with circular dependencies yet doesn't add a lot of code complexity. I recently rewrote a Scala project from using the Cake Pattern to type classes for DIand achieved a 59% reduction in code size.

我喜欢使用类型类作为依赖注入的轻量级 Scala 惯用形式,它仍然适用于循环依赖,但不会增加很多代码复杂性。我最近重写了一个Scala 项目,使用 Cake Pattern 为 DI 键入类,并实现了 59% 的代码大小减少。