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

时间:2009-08-10 02:42:31

标签: scala haskell polymorphism type-inference

我现在正在尝试学习Scala,在Haskell中有一些经验。对我来说奇怪的一件事是Scala 中的所有函数参数必须用类型注释 - 这是Haskell不需要的东西。为什么是这样?试着把它作为一个更具体的例子:add函数写成如下:

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

但是,这只适用于双打(嗯,因为隐式类型转换,因此也可以工作)。但是,如果要定义自己的类型来定义自己的 + 运算符,该怎么办?您如何编写适用于定义 + 运算符的任何类型的添加函数?

5 个答案:

答案 0 :(得分:68)

Haskell使用Hindley-Milner类推理算法,而Scala为了支持面向对象的方面,现在不得不放弃使用它。

为了轻松编写所有适用类型的添加功能,您需要使用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

答案 1 :(得分:18)

为了巩固我自己使用隐式的概念,我写了一个不需要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中的类型类一样使用。然后实现这个特定类型的泛型类,你会写(这里的例子是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

此时,您可以使用以下三种类型调用 add 函数:

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那么好,它基本上可以为你做所有这些。但是,这就是权衡所在。

答案 2 :(得分:3)

我认为Scala需要对新定义的函数的参数进行类型注释的原因来自于Scala使用比Haskell中使用的更局部的类型推断分析。

如果你的所有类混合了一个特性,比如Addable[T],它声明了+运算符,你可以将你的通用添加函数编写为:

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

这将add函数限制为实现Addable trait的类型T.

不幸的是,当前的Scala库中没有这样的特性。但是你可以通过查看类似的Ordered[T]特征来了解它是如何完成的。此特征声明了比较运算符,并由RichIntRichFloat等类混合。然后,您可以编写一个排序函数,例如,List[T],其中[T <: Ordered[T]]可以对在有序特征中混合的元素列表进行排序。由于隐式类型转换(例如FloatRichFloat),您甚至可以在IntFloatDouble列表中使用排序功能。

正如我所说,遗憾的是,+运营商没有相应的特征。所以,你必须自己写出一切。您将执行Addable [T]特征,创建AddableIntAddableFloat等等,扩展Int,Float等类并混合使用Addable trait,最后添加隐式转换函数以转向例如,Int和AddableInt,以便编译器可以实例化并使用你的add函数。

答案 3 :(得分:3)

Haskell使用Hindley-Milner类型推断。这种类型推断功能强大,但限制了语言的类型系统。据推测,例如,子类化与H-M不兼容。

无论如何,Scala类型系统对于H-M来说太强大了,因此必须使用更有限的类型推断。

答案 4 :(得分:1)

函数本身非常简单:

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

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

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

但是有一个缺失的部分,这是类型参数本身。如上所述,该方法缺少其类。最可能的情况是你在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 = { ... }

  ...
}

这可能比你现在真正想知道的更多,但它确实展示了如何定义泛型参数以及如何实现它们。

最终,不需要特定类型,但编译器确实需要至少知道类型边界。