53

我现在正在尝试学习 Scala,在 Haskell 方面有一点经验。对我来说很奇怪的一件事是 Scala 中的所有函数参数都必须使用类型进行注释——这是 Haskell 不需要的。为什么是这样?试着把它作为一个更具体的例子:一个 add 函数是这样写的:

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

但是,这仅适用于双精度数(好吧,由于隐式类型转换,整数也可以工作)。但是,如果您想定义自己的类型来定义自己的+运算符怎么办。您将如何编写适用于任何定义+运算符的类型的 add 函数?

4

5 回答 5

70

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
于 2009-08-10T03:40:12.577 回答
19

为了巩固自己使用implicit的概念,我写了一个例子,不需要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 好,它基本上会为你做所有这些。但是,这就是权衡所在。

于 2009-08-11T16:02:03.017 回答
3

我认为 Scala 要求对新定义函数的参数进行类型注释的原因在于 Scala 使用比 Haskell 中使用的更本地化的类型推断分析这一事实。

如果您的所有类都混合在一个特征中,例如Addable[T]声明+运算符,您可以将通用添加函数编写为:

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

这将 add 函数限制为实现 Addable 特征的类型 T。

不幸的是,当前的 Scala 库中没有这样的特性。但是您可以通过查看类似的情况,即Ordered[T]特征来了解它是如何完成的。该特征声明了比较运算符,并由RichInt,RichFloat等类混合。然后,您可以编写一个排序函数,例如,可以对混合在有序特征中的元素列表进行排序List[T][T <: Ordered[T]]由于像Floatto这样的隐式类型转换RichFloat,您甚至可以对Int、 orFloat或的列表使用排序函数Double

正如我所说,不幸的是,操作员没有相应的特征+。因此,您必须自己写出所有内容。您将执行 Addable[T] 特征,创建AddableInt,AddableFloat等,扩展 Int、Float 等的类并混合 Addable 特征,最后添加隐式转换函数以将例如和 Int 转换为AddableInt,所以编译器可以实例化并使用您的 add 函数。

于 2009-08-10T03:36:48.013 回答
3

Haskell 使用Hindley-Milner类型推断。这种类型推断功能强大,但限制了语言的类型系统。例如,假设子类化不适用于 HM。

无论如何,Scala 类型系统对于 HM 来说太强大了,所以必须使用一种更有限的类型推断。

于 2009-08-10T18:12:15.583 回答
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 = { ... }

  ...
}

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

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

于 2009-08-10T03:31:48.023 回答