Scala:如何定义“通用”函数参数?

我现在想学习Scala,在Haskell有一点经验。 有一件事对我来说很奇怪,那就是Scala中的所有函数参数都必须用一个types进行注释 – 这是Haskell不需要的。 为什么是这样? 试图把它作为一个更具体的例子:添加函数是这样写的:

def add(x:Double, y:Double) = x + y 

但是,这只适用于双打(因为隐式types转换,所以intts工作也是如此)。 但是如果你想要定义自己的types来定义自己的+运算符,该怎么办呢? 你将如何编写一个适用于任何定义了+运算符的types的add函数?

Haskell使用Hindley-Milnertypes推理algorithm,而Scala为了支持面向对象的事物,现在不得不放弃使用它。

为了方便地为所有适用types编写添加函数,您将需要使用Scala 2.8.0:

 Welcome to Scala version 2.8.0.r18189-b20090702020221 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_15). Type in expressions to have them evaluated. Type :help for more information. scala> import Numeric._ import Numeric._ scala> def add[A](x: A, y: A)(implicit numeric: Numeric[A]): A = | numeric.plus(x, y) add: [A](x: A,y: A)(implicit numeric: Numeric[A])A scala> add(1, 2) res0: Int = 3 scala> add(1.1, 2.2) res1: Double = 3.3000000000000003 

为了巩固我自己使用隐式的概念,我写了一个不需要scala 2.8的例子,但是使用了相同的概念。 我认为这可能对一些人有帮助。 首先,你定义一个通用抽象类Addable

 scala> abstract class Addable[T]{ | def +(x: T, y: T): T | } defined class Addable 

现在你可以写这样的add函数:

 scala> def add[T](x: T, y: T)(implicit addy: Addable[T]): T = | addy.+(x, y) add: [T](T,T)(implicit Addable[T])T 

这在Haskell中就像一个types类一样使用。 然后为了实现这个generics类的特定types,你会写(例如Int,Double和String的例子):

 scala> implicit object IntAddable extends Addable[Int]{ | def +(x: Int, y: Int): Int = x + y | } defined module IntAddable scala> implicit object DoubleAddable extends Addable[Double]{ | def +(x: Double, y: Double): Double = x + y | } defined module DoubleAddable scala> implicit object StringAddable extends Addable[String]{ | def +(x: String, y: String): String = x concat y | } defined module StringAddable 

在这一点上,你可以调用所有三种types的添加function:

 scala> add(1,2) res0: Int = 3 scala> add(1.0, 2.0) res1: Double = 3.0 scala> add("abc", "def") res2: java.lang.String = abcdef 

当然不像Haskell那么好,基本上这些都是为你做的。 但是,这就是权衡所在。

Haskell使用Hindley-Milnertypes推断。 这种types推理function强大,但是限制了语言的types体系。 据说,例如,子类化不能很好地处理HM。

无论如何,Scalatypes系统对于HM来说太强大了,因此必须使用更为有限的types推断。

我认为Scala需要对新定义的函数的参数进行types注释的原因来自于Scala使用比Haskell中使用的更多本地types推断分析的事实。

如果所有的类都混合在一个特性中,比如声明了+运算符的Addable[T] ,那么你可以把你的genericsadd函数写成:

 def add[T <: Addable[T]](x : T, y : T) = x + y 

这将add函数限制在实现了Addable特性的typesT中。

不幸的是,在当前的Scala库中没有这样的特性。 但是你可以通过查看一个类似的例子, Ordered[T]特征来看看它是如何完成的。 这个特性声明了比较运算符,并且被RichIntRichFloat等类混合使用。 然后你可以编写一个sorting函数,例如,可以使用一个List[T] ,其中[T <: Ordered[T]]用于sorting混合在sorting特征中的元素列表。 由于像FloatRichFloat这样的隐式types转换,你甚至可以在IntFloat或者Double列表上使用你的sorting函数。

正如我所说的,不幸的是, +运营商没有相应的特征。 所以,你必须自己写出所有的东西。 你可以做Addable [T]特性,创buildAddableIntAddableFloat等,扩展Int,Float等的类并混合Addable特性,最后添加隐式转换函数来将Int和Int转换成AddableInt ,以便编译器可以实例化并使用你的add函数。

该函数本身将非常简单:

 def add(x: T, y: T): T = ... 

更好的是,你可以重载+方法:

 def +(x: T, y: T): T = ... 

有一个缺失的部分,但是,这是types参数本身。 正如所写的,该方法缺less它的类。 最有可能的情况是,你正在对T的一个实例调用+方法,并将其传递给T的另一个实例。我最近做了这个,定义了一个特征:“一个附加组由一个加操作加上颠倒元素“

 trait GroupAdditive[G] extends Structure[G] { def +(that: G): G def unary_- : G } 

之后,我定义了一个Real类,它知道如何添加自己的实例(Field extends GroupAdditive):

 class Real private (s: LargeInteger, err: LargeInteger, exp: Int) extends Number[Real] with Field[Real] with Ordered[Real] { ... def +(that: Real): Real = { ... } ... } 

这可能比你现在想知道的要多,但它确实显示了如何定义generics参数以及如何实现它们。

最终,特定types不是必需的,但编译器确实需要知道至less是types边界。