Scala中的types类有用吗?
正如我从这篇博文所理解的,Scala中的“types类”只是一个用特征和隐式适配器实现的“模式”。
正如博客所说,如果我具有特质A
和适配器B -> A
那么我可以调用一个函数,该函数需要typesA
的参数,而typesB
的参数不显式调用该适配器。
我发现它很好,但不是特别有用。 你可以给一个用例/例子,它显示了这个function对于什么有用吗?
一个用例,根据要求…
想象一下,你有一个列表的东西,可以是整数,浮点数,matrix,string,波形等给定这个列表,你想添加的内容。
一种方法是创build一些可Addable
特性, Addable
特性必须由可以添加到一起的每种typesinheritance,或者如果处理来自第三方库的对象的隐式转换,则不能将接口改为接口。
当你也想开始添加其他可以完成对象列表的操作时,这种方法变得非常难以应付。 如果您需要替代scheme(例如,是否将两个波形连接起来,还是将它们叠加在一起?),它也不能很好地工作。解决scheme是ad-hoc多态,您可以select和select要对现有types进行改进的行为。
对于原来的问题,那么你可以实现一个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!
然后,您可以创build隐含的子实例,对应于您希望添加的每种types:
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) }
这种方法的优点是你可以提供一些types类的另一个定义,或者通过导入来控制你想要的隐式,或者通过明确地提供其他隐含的参数。 因此,可以提供不同的方式来添加波形,或者指定用于整数相加的模运算。 从一些第三方库中添加一个types到你的typestypes也是很容易的。
顺便说一句,这正是2.8集合API采用的方法。 尽pipesum
方法是在TraversableLike
上定义的,而不是在List
上定义的,而types类是Numeric
(它还包含一些zero
和append
更多的操作)
重读那里的第一个评论:
类类和接口之间的一个重要的区别是,类A是一个接口的“成员”,它必须在自己的定义的站点上声明。 相比之下,任何types都可以随时添加到types类中,前提是您可以提供所需的定义,并且types类的成员在任何给定时间都依赖于当前范围。 因此,我们并不在乎A的创造者是否期待我们想要它属于的types类; 如果不是,我们可以简单地创build自己的定义,表明它确实属于,然后相应地使用它。 所以这不仅比适配器提供了一个更好的解决scheme,从某种意义上说,它消除了适配器所要解决的全部问题。
我认为这是types类最重要的优点。
此外,他们正确处理的情况下,操作没有我们正在派遣types的论点,或有不止一个。 例如考虑这种types:
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) ... }
我认为types类能够将types安全的元数据添加到类中。
因此,您首先要定义一个类来模拟问题域,然后考虑添加元数据。 像Equals,Hashable,Viewable等东西。这就创build了问题域和机制的分离,使用类并打开子类化,因为类更加精简。
除此之外,您可以在范围内的任何位置添加types类,而不仅仅是定义类的位置,您可以更改实现。 例如,如果我使用Point#hashCode计算一个Point类的散列码,那么我只限于那个特定的实现,它可能不会为我具有的一组特定的值创build一个很好的值分布。 但是如果我使用Hashable [Point],那么我可以提供自己的实现。
[以例子更新]举个例子,这里是我上周的一个用例。 在我们的产品中,有几个包含容器值的地图。 例如, Map[Int, List[String]]
或Map[String, Set[Int]]
。 添加到这些集合可以是冗长的:
map += key -> (value :: map.getOrElse(key, List()))
所以我想有一个包装这个function,所以我可以写
map +++= key -> value
主要的问题是收集并不都具有相同的添加元素的方法。 有的有“+”,有的则有“+”。 我也想保留添加元素到列表的效率,所以我不想使用折叠/地图来创build新的集合。
解决scheme是使用types类:
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 } }
在这里我定义了一个可以将元素C添加到集合CC的types类Addable
。 我有2个默认实现:对于使用::
和其他集合的列表,使用生成器框架。
然后使用这个类的类是:
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
以为新密钥创build新的集合。
为了比较,没有types的类,我会有3个选项:1.每个集合types写一个方法。 例如, addElementToSubList
和addElementToSet
等。这在实现中创build了很多样板,并且污染了命名空间2.使用reflection来确定子集合是否是List / Set。 这是非常棘手的,因为地图是空的(当然scala在Manifests也有帮助)3.通过要求用户提供加法器来让穷人的types类。 所以像addToMap(map, key, value, adder)
,这是丑陋的
另一种方式,我觉得这个博客文章有帮助的地方是它描述的types: monads不是隐喻
searchtypes的文章。 这应该是第一场比赛。 在本文中,作者提供了一个Monadtypes类的例子。
查看types类的一种方法是使用追溯扩展或追溯多态 。 Casual Miracles和Daniel Westheide有几篇很好的文章,展示了在Scala中使用Type Classes来实现这个function的例子。
这里是我的博客上的一篇文章 ,探讨追溯超types的各种方法,一种追溯扩展,包括一个types的例子。
论坛主题“ 什么使得types比特性更好? ”提出了一些有趣的观点:
- types类可以很容易地表示在子types的存在下很难表示的概念,例如平等和sorting 。
练习:创build一个小的类/特征层次结构,并尝试在每个类/特征上实现.equals
,以便对来自层次结构的任意实例的操作是正确的自反,对称和传递的。- types类允许你提供证据,certificate你的“控制”之外的types符合某些行为。
别人的types可以是你types类的成员。- 在子types方面你不能expression“这个方法需要/返回与方法接收者相同types的值”,但是这个(非常有用)的约束是使用types类来直接的。 这是f-boundtypes问题 (其中F-boundtypes通过它自己的子types被参数化)。
- 在特征上定义的所有操作都需要一个实例 ; 总是有
this
说法。 所以你不能在trait Foo
上定义例如fromString(s:String): Foo
方法,这样你就可以在没有Foo
实例的情况下调用它。
在斯卡拉这performance为人们拼命试图抽象伴侣对象。
但是对于一个typestypes来说很简单,就像这个monoid例子中的零元素所示 。- 类特定可以被归纳定义 ; 例如,如果你有一个
JsonCodec[Woozle]
你可以免费得到一个JsonCodec[List[Woozle]]
。
上面的例子说明了这个“可以加在一起的东西”。
我不知道任何其他的用例,而不是在这里解释的最佳方式Ad-hoc polymorhism 。
types转换使用implicits和typeclass 。 两者的主要用例是为不能修改的类提供临时的多态性 (即),但期望inheritancetypes的多态性。 在implicits的情况下,你可以同时使用隐式def或隐式类(这是你的包装类,但从客户端隐藏)。 types类更强大,因为它们可以将function添加到已经存在的inheritance链(例如:Scalasorting函数中的Ordering [T])。 有关更多详细信息,请参阅https://lakshmirajagopalan.github.io/diving-into-scala-typeclasses/
在scala类中
- 启用ad-hoc多态性
- 静态types(即types安全)
- 从Haskell借来的
- 解决expression问题
行为可以延伸 – 在编译时 – 在事实之后 – 不改变/重新编译现有的代码
斯卡拉的含义
方法的最后一个参数列表可以标记为隐式
-
隐式参数由编译器填写
-
实际上,你需要编译器的证据
-
例如在范围内存在一个types类
-
如果需要,您也可以明确指定参数
下面的示例对types实现的String类进行扩展,即使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 }
这是一个重要的区别(function编程需要):
考虑inc:Num a=> a -> a
:
收到的是一样的返回,这是不能用子types来完成的