我看过 F#,做过低级教程,所以我对它的了解非常有限。然而,对我来说很明显,它的风格本质上是函数式的,OO 更像是一个附加组件——更像是一个 ADT + 模块系统,而不是真正的 OO。我得到的感觉可以最好地描述为好像其中的所有方法都是静态的(如 Java 静态)。
例如,请参阅使用管道运算符的任何代码 (|>
)。从以下内容中获取此片段F# 上的维基百科条目 http://en.wikipedia.org/wiki/F_Sharp_(programming_language):
[1 .. 10]
|> List.map fib
(* equivalent without the pipe operator *)
List.map fib [1 .. 10]
功能map
不是列表实例的方法。相反,它的工作方式就像一个静态方法List
模块将列表实例作为其参数之一。
另一方面,Scala 是完全面向对象的。首先,我们从该代码的 Scala 等效版本开始:
List(1 to 10) map fib
// Without operator notation or implicits:
List.apply(Predef.intWrapper(1).to(10)).map(fib)
Here, map
是实例上的方法List
。类似静态的方法,例如intWrapper
on Predef
or apply
on List
,则更为罕见。然后还有一些函数,比如fib
多于。这里,fib
不是一个方法int
,但它都不是静态方法。相反,它是一个object——我认为 F# 和 Scala 之间的第二个主要区别。
让我们考虑一下 Wikipedia 中的 F# 实现以及等效的 Scala 实现:
// F#, from the wiki
let rec fib n =
match n with
| 0 | 1 -> n
| _ -> fib (n - 1) + fib (n - 2)
// Scala equivalent
def fib(n: Int): Int = n match {
case 0 | 1 => n
case _ => fib(n - 1) + fib(n - 2)
}
上面的 Scala 实现是一个方法,但是 Scala 将其转换为一个函数,以便能够将其传递给map
。下面我将对其进行修改,使其成为返回函数的方法,以展示函数在 Scala 中的工作原理。
// F#, returning a lambda, as suggested in the comments
let rec fib = function
| 0 | 1 as n -> n
| n -> fib (n - 1) + fib (n - 2)
// Scala method returning a function
def fib: Int => Int = {
case n @ (0 | 1) => n
case n => fib(n - 1) + fib(n - 2)
}
// Same thing without syntactic sugar:
def fib = new Function1[Int, Int] {
def apply(param0: Int): Int = param0 match {
case n @ (0 | 1) => n
case n => fib.apply(n - 1) + fib.apply(n - 2)
}
}
因此,在 Scala 中,所有函数都是实现该特征的对象FunctionX
,它定义了一个名为apply
。如此处和上面的列表创建所示,.apply
可以省略,这使得函数调用看起来就像方法调用一样。
最后,Scala 中的所有内容都是一个对象——以及类的实例——并且每个这样的对象都属于一个类,并且所有代码都属于一个方法,该方法以某种方式执行。甚至match
在上面的例子中以前是一个方法,但很早之前已经被转换为关键字以避免一些问题。
那么,它的功能部分又如何呢? F# 属于最传统的函数式语言家族之一。虽然它没有一些人们认为对函数式语言很重要的功能,但事实是 F# 的功能是default, 可以这么说。
另一方面,Scala 的创建目的是unifying函数式模型和面向对象模型,而不是仅仅将它们作为语言的单独部分提供。它的成功程度取决于您认为什么是函数式编程。以下是 Martin Odersky 关注的一些事情:
函数就是值。它们也是对象——因为在 Scala 中所有值都是对象——但是函数是可以操作的值这一概念是一个重要的概念,其根源可以追溯到最初的 Lisp 实现。
-
对不可变数据类型的强大支持。函数式编程始终关注减少程序的副作用,函数可以作为真正的数学函数进行分析。因此,Scala 很容易使事物变得不可变,但它没有做 FP 纯粹主义者批评的两件事:
- 它没有产生可变性harder.
- 它不提供效果系统,通过它可以静态跟踪可变性。
-
支持代数数据类型。代数数据类型(称为 ADT,它也代表抽象数据类型,这是一种不同的东西)在函数式编程中非常常见,并且在 OO 语言中通常使用访问者模式的情况下最有用。
与其他所有事物一样,Scala 中的 ADT 是作为类和方法实现的,并带有一些语法糖,使它们可以轻松使用。然而,Scala 在支持它们方面比 F#(或其他函数式语言)要详细得多。例如,代替 F#|
对于 case 语句,它使用case
.
-
支持非严格性。非严格性意味着仅按需计算内容。它是 Haskell 的一个重要方面,它与副作用系统紧密集成。然而,在 Scala 中,非严格性支持相当胆怯且处于初级阶段。它可用并使用,但方式受到限制。
例如,Scala 的非严格列表,Stream
,不支持真正的非严格foldRight
,就像哈斯克尔所做的那样。此外,只有当非严格性是语言中的默认值而不是选项时,才能获得非严格性的一些好处。
-
支持列表理解。实际上,Scala 称之为用于理解,因为它的实现方式完全脱离了列表。用最简单的术语来说,列表推导式可以被认为是map
示例中显示的函数/方法,尽管嵌套了映射语句(支持 withflatMap
在 Scala 中)以及过滤(filter
or withFilter
在 Scala 中,取决于严格要求)通常是预期的。
这是函数式语言中非常常见的操作,并且语法通常很简单——就像在 Python 中一样in
操作员。同样,Scala 比平常更加冗长。
在我看来,Scala 在结合 FP 和 OO 方面是无与伦比的。它从频谱的 OO 一侧朝向 FP 一侧,这是不寻常的。大多数情况下,我看到 FP 语言都在处理 OO 问题——而且它feels向我解决了这个问题。我想 Scala 上的 FP 对于函数式语言程序员来说可能有同样的感觉。
EDIT
Reading some other answers I realized there was another important topic: type inference. Lisp was a dynamically typed language, and that pretty much set the expectations for functional languages. The modern statically typed functional languages all have strong type inference systems, most often the Hindley-Milner http://en.wikipedia.org/wiki/Hindley-Milner#algorithm1 algorithm, which makes type declarations essentially optional.
Scala can't use the Hindley-Milner algorithm because of Scala's support for inheritance2. So Scala has to adopt a much less powerful type inference algorithm -- in fact, type inference in Scala is intentionally undefined in the specification, and subject of on-going improvements (it's improvement is one of the biggest features of the upcoming 2.8 version of Scala, for instance).
然而,最终,Scala 要求所有参数在定义方法时声明其类型。在某些情况下,例如递归,还必须声明方法的返回类型。
Scala 中的函数通常有其类型inferred不过,而不是声明。例如,这里不需要类型声明:List(1, 2, 3) reduceLeft (_ + _)
, where _ + _
实际上是一个类型的匿名函数Function2[Int, Int, Int]
.
同样,变量的类型声明通常是不必要的,但继承可能需要它。例如,Some(2)
and None
有一个共同的超类Option
,但实际上属于不同的子类。所以人们通常会声明var o: Option[Int] = None
以确保分配了正确的类型。
这种有限形式的类型推断比静态类型的 OO 语言通常提供的要好得多,这给了 Scala 一种轻盈的感觉,但也比静态类型的 FP 语言通常提供的要差得多,这给了 Scala 一种沉重的感觉。 :-)
Notes:
实际上,该算法起源于Damas和Milner,他们将其称为“算法W”,根据维基百科 http://en.wikipedia.org/wiki/Type_inference#Hindley.E2.80.93Milner_type_inference_algorithm.
-
马丁·奥德斯基在评论中提到here http://www.codecommit.com/blog/scala/universal-type-inference-is-a-bad-thing that:
Scala 没有 Hindley/Milner 类型推断的原因是
很难与诸如
重载(临时变体,而不是类型类),记录
选择和子类型化
他接着表示,这实际上可能并非不可能,这归结为一个权衡。请访问该链接以获取更多信息,并且,如果您确实提出了更清晰的声明,或者更好的是,以某种方式提出了一些论文,我将不胜感激。
让我感谢乔恩·哈洛普 https://stackoverflow.com/users/13924/jon-harrop查找这个,正如我所假设的那样不可能的。好吧,也许是这样,但我找不到合适的链接。但请注意,这不是继承alone导致问题。