Scala 中的类型类有什么用?
据我了解,这篇博文“类型类” Scala 只是一个用特征和隐式适配器实现的“模式”。
正如博客所说,如果我有特征 A
和适配器 B -> A
然后我可以调用一个函数,该函数需要 A
类型的参数,以及 B
类型的参数,而无需显式调用此适配器。
我发现它很好但不是特别有用。您能否提供一个用例/示例来说明此功能的用途?
As I understand from this blog post "type classes" in Scala is just a "pattern" implemented with traits and implicit adapters.
As the blog says if I have trait A
and an adapter B -> A
then I can invoke a function, which requires argument of type A
, with an argument of type B
without invoking this adapter explicitly.
I found it nice but not particularly useful. Could you give a use case/example, which shows what this feature is useful for ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
根据要求,一个用例...
假设您有一个事物列表,可以是整数、浮点数、矩阵、字符串、波形等。给定此列表,您想要添加内容。
实现此目的的一种方法是拥有一些
Addable
特征,该特征必须由可以添加在一起的每个单个类型继承,或者在处理时隐式转换为Addable
来自第三方库的对象,您无法对其接口进行改造。当您还想开始添加其他可以对对象列表执行的此类操作时,这种方法很快就会变得难以承受。如果您需要替代方案(例如,添加两个波形是否将它们连接起来,或者重叠它们?),它也不能很好地工作。解决方案是临时多态性,您可以在其中选择行为对现有类型进行改造。
对于最初的问题,您可以实现一个
Addable
类型类:然后您可以创建该类型的隐式子类实例,对应于您希望可添加的每种类型:
对列表求和的方法将变为编写起来很简单...
这种方法的优点在于,您可以提供某些类型类的替代定义,可以通过导入控制作用域中所需的隐式定义,也可以通过显式提供其他隐式参数来控制。因此,可以提供不同的波形相加方式,或者指定整数加法的模运算。将某个第三方库中的类型添加到您的类型类中也相当轻松。
顺便说一句,这正是 2.8 集合 API 所采用的方法。虽然
sum
方法是在TraversableLike
上定义的,而不是在List
上定义的,并且类型类是Numeric
(它也包含更多操作,而不仅仅是zero
和append
)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
Addable
trait that must be inherited by every single type that can be added together, or an implicit conversion to anAddable
if dealing with objects from a third party library that you can't retrofit interfaces to.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 can pick and chose behaviour to be retrofitted to existing types.
For the original problem then, you could implement an
Addable
type class:You can then create implicit subclassed instances of this, corresponding to each type that you wish to make addable:
The method to sum a list then becomes trivial to write...
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.
Incidentally, this is exactly the approach taken by the 2.8 collections API. Though the
sum
method is defined onTraversableLike
instead of onList
, and the type class isNumeric
(it also contains a few more operations than justzero
andappend
)重读那里的第一条评论:
我认为这是类型类最重要的优点。
此外,它们还可以正确处理操作没有我们正在分派的类型的参数或有多个参数的情况。例如考虑这个类型类:
Reread the first comment there:
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:
我认为类型类是向类添加类型安全元数据的能力。
因此,您首先定义一个类来对问题域进行建模,然后考虑要添加到其中的元数据。像 Equals、Hashable、Viewable 等。这创建了问题域和使用类的机制的分离,并开放了子类化,因为类更精简。
除此之外,您可以在范围内的任何位置添加类型类,而不仅仅是定义类的位置,并且您可以更改实现。例如,如果我使用 Point#hashCode 计算 Point 类的哈希码,那么我仅限于该特定实现,该实现可能无法为我拥有的特定 Point 集创建良好的值分布。但如果我使用 Hashable[Point],那么我可以提供我自己的实现。
[更新示例]
作为一个例子,这是我上周的一个用例。在我们的产品中,有几种 Maps 包含容器作为值的情况。例如,
Map[Int, List[String]]
或Map[String, Set[Int]]
。添加到这些集合可能会很冗长:所以我想要一个函数来包装它,这样我就可以编写
主要问题是集合并不都具有相同的添加元素的方法。有些有“+”,而另一些则有“:+”。我还想保留向列表添加元素的效率,所以我不想使用创建新集合的折叠/映射。
解决方案是使用类型类:
这里我定义了一个类型类
Addable
,它可以将元素C添加到集合CC中。我有 2 个默认实现:对于使用::
的列表,对于其他集合,使用构建器框架。然后使用这个类型类是:
特殊的一点是使用 adder.add 来添加元素,并使用 adder.empty 为新键创建新集合。
相比之下,如果没有类型类,我将有 3 个选择:
1. 为每个集合类型编写一个方法。例如,addElementToSubList 和 addElementToSet 等。这会在实现中创建大量样板文件并污染命名空间
2.使用反射来判断子集合是否是List/Set。这很棘手,因为地图一开始就是空的(当然 scala 在这里也可以通过清单提供帮助)
3. 通过要求用户提供加法器来具有穷人的类型类别。像
addToMap(map, key, value, adder)
这样的东西,简直丑陋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.
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.
[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]]
orMap[String, Set[Int]]
. Adding to these collections can be verbose:So I wanted to have a function that wraps this so I could write
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.
The solution is to use type classes:
Here I defined a type class
Addable
that 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.Then using this type class is:
The special bit is using
adder.add
to add the elements andadder.empty
to create new collections for new keys.To compare, without type classes I would have had 3 options:
1. to write a method per collection type. E.g.,
addElementToSubList
andaddElementToSet
etc. This creates a lot of boilerplate in the implementation and pollutes the namespace2. 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我发现这篇博文有用的另一种方式是它描述了类型类: Monads 是不是隐喻
在文章中搜索类型类。这应该是第一场比赛。在本文中,作者提供了一个 Monad 类型类的示例。
Yet another way I find this blog post helpful is where it describes typeclasses: 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.
论坛主题“是什么让类型类比特征更好?”提出了一些有趣的观点:
The forum thread "What makes type classes better than traits?" makes some interesting points:
查看类型类的一种方法是它们启用追溯扩展或追溯多态性。 休闲奇迹和丹尼尔·韦斯特海德 显示了在 Scala 中使用类型类来实现此目的的示例。
这是 在我的博客上发帖
探索了 scala 中的各种方法追溯超类型,一种追溯扩展,包括类型类示例。
One way to look at type classes is that they enable retroactive extension or retroactive polymorphism. There are a couple of great posts by Casual Miracles and Daniel Westheide that show examples of using Type Classes in Scala to achieve this.
Here's a post on my blog
that explores various methods in scala of retroactive supertyping, a kind of retroactive extension, including a typeclass example.
除了临时多态性之外,我不知道还有任何其他用例,它的解释是这里可能是最好的方法。
I don't know of any other use case than Ad-hoc polymorhism which is explained here the best way possible.
隐式和类型类都用于类型转换。它们的主要用例是为您无法修改但期望继承类型的多态性的类提供临时多态性(即)。如果是隐式,您可以使用隐式 def 或隐式类(这是您的包装类,但对客户端隐藏)。类型类更强大,因为它们可以向现有的继承链添加功能(例如:scala 排序函数中的 Ordering[T])。
有关更多详细信息,您可以参阅 https://lakshmirajagopalan.github.io/diving-into -scala-类型类/
Both implicits and typeclasses are 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/
在 scala 类型类中
行为可以扩展
- 在编译时
-事后
- 无需更改/重新编译现有代码
Scala 隐式 方法
的最后一个参数列表可以标记为隐式
隐式参数由编译器填充
实际上,您需要编译器的证据
...例如作用域中存在类型类
指定参数。
式
In scala type classes
Behavior can be extended
- at compile-time
- after the fact
- without changing/recompiling existing code
Scala Implicits
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 :)
这是一个重要的区别(函数式编程所需):
考虑
inc:Num a=>一个-> a
:收到的
a
与返回的相同,这不能通过子类型来完成This is an important difference (needed for functional programming):
consider
inc:Num a=> a -> a
:a
received is the same that is returned, this cannot be done with subtyping我喜欢使用类型类作为依赖注入的轻量级 Scala 惯用形式,它仍然可以处理循环依赖,但不会增加很多代码复杂性。我最近重写了一个 Scala 项目,使用 Cake 模式为 DI 键入类 并实现了代码大小减少 59%。
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 DI and achieved a 59% reduction in code size.