我现在正在尝试学习Scala,在Haskell中有一些经验。对我来说奇怪的一件事是Scala 中的所有函数参数必须用类型注释 - 这是Haskell不需要的东西。为什么是这样?试着把它作为一个更具体的例子:add函数写成如下:
def add(x:Double, y:Double) = x + y
但是,这只适用于双打(嗯,因为隐式类型转换,因此也可以工作)。但是,如果要定义自己的类型来定义自己的 + 运算符,该怎么办?您如何编写适用于定义 + 运算符的任何类型的添加函数?
答案 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]
特征来了解它是如何完成的。此特征声明了比较运算符,并由RichInt
,RichFloat
等类混合。然后,您可以编写一个排序函数,例如,List[T]
,其中[T <: Ordered[T]]
可以对在有序特征中混合的元素列表进行排序。由于隐式类型转换(例如Float
到RichFloat
),您甚至可以在Int
或Float
或Double
列表中使用排序功能。
正如我所说,遗憾的是,+
运营商没有相应的特征。所以,你必须自己写出一切。您将执行Addable [T]特征,创建AddableInt
,AddableFloat
等等,扩展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 = { ... }
...
}
这可能比你现在真正想知道的更多,但它确实展示了如何定义泛型参数以及如何实现它们。
最终,不需要特定类型,但编译器确实需要至少知道类型边界。