斯卡拉与F#问题:他们如何统一面向对象和计划范式?

Scala和F#采取的统一面向对象和面向对象的方法之间的主要区别是什么?

编辑

每种方法的优点和缺点是什么? 如果,尽pipe支持子types,F#可以推断函数参数的types,那么为什么不能Scala?

我已经看过F#,做低级别的教程,所以我的知识是非常有限的。 然而,对我来说显而易见,它的风格基本上是function性的,OO更像是一个ADT +模块系统的附加,而不是真正的OO。 我得到的感觉可以被最好地描述为它的所有方法都是静态的(如在Java静态中)。

例如,请参阅使用pipe道运算符( |> )的任何代码。 从F#上的维基百科条目中摘取这个片段:

 [1 .. 10] |> List.map fib (* equivalent without the pipe operator *) List.map fib [1 .. 10] 

函数map不是列表实例的一个方法。 相反,它就像一个List模块上的一个静态方法,它将一个列表实例作为其参数之一。

另一方面,斯卡拉完全是面向对象。 首先,让我们开始使用该代码的Scala等价物:

 List(1 to 10) map fib // Without operator notation or implicits: List.apply(Predef.intWrapper(1).to(10)).map(fib) 

这里, mapList实例的一个方法。 类似于静态的方法,比如intWrapper上的Predef或者apply List ,更为罕见。 然后有function,如上面的fib 。 这里, fib不是int一个方法,但也不是一个静态的方法。 相反,它是一个对象 – 我在F#和Scala之间看到的第二个主要区别。

我们来考虑维基百科的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的方法。 如上所示,在上面创build的列表中,可以省略.apply ,这使得函数调用看起来就像方法调用一样。

最后,Scala中的所有东西都是一个对象 – 一个类的实例 – 每一个这样的对象都属于一个类,所有的代码都属于一个方法,并以某种方式被执行。 即使在上面的例子中match 曾经是一种方法,但已被转换成关键字,以避免一些问题前一段时间。

那么,function部分呢? F#属于function语言最传统的家族之一。 虽然它没有某些function,但有些人认为function语言很重要,事实上F#在默认情况下是可用的,可以这么说。

另一方面,Scala是为了统一function和OO模型而创build的,而不是仅仅把它们作为语言的独立部分提供。 成功的程度取决于你认为的function性程序devise。 以下是Martin Odersky关注的一些事情:

  • 函数是值。 它们也是对象 – 因为所有的值都是Scala中的对象 – 但是函数是一个可以被操纵的值的概念是一个重要的概念,它的根源一直回到原来的Lisp实现。

  • 强大的支持不可变的数据types。 函数式编程一直关注减less程序的副作用,函数可以分析为真正的math函数。 所以斯卡拉很容易让事情变得不可改变,但是它并没有做两件事情,那就是FP纯粹主义者批评它:

    • 它并没有使可变性更难
    • 它不提供一个效果系统 ,通过这个系统可以静态追踪可变性。
  • 支持代数数据types。 代数数据types(称为ADT,它也代表抽象数据types,不同的东西)在函数式编程中是非常常见的,在通常使用面向对象语言的访问者模式的情况下是非常有用的。

    和其他所有东西一样,Scala中的ADT实现为类和方法,使用一些语法糖使它们无痛使用。 但是,Scala比F#(或其他function语言)在支持它们方面要冗长得多。 例如,而不是F#的| 对于case语句,它使用case

  • 支持非严格。 非严格意味着只按需要计算东西。 这是Haskell的一个重要方面,它与副作用系统紧密集成。 然而,在斯卡拉,非严格的支持是相当胆小和刚开始的。 它是可用的,但使用受限制。

    例如,Scala的非严格清单Stream并不支持真正的非严格的foldRight ,比如Haskell。 此外,非严格的一些好处只有在语言的默认值而不是选项时才会获得。

  • 支持列表理解。 实际上,Scala把它称为理解 ,因为它被实现的方式完全脱离了列表。 简单来说,列表parsing可以被看作是示例中所示的map函数/方法,虽然嵌套语句(在Scala中支持flatMap ),而且过滤(根据严格性要求在Scala中filter或者使用withFilter )通常是预期的。

    这在函数式语言中是一个非常常见的操作,并且在语法上通常很简单 – 就像Python中的operator。 同样,斯卡拉比平常更加冗长。

在我看来,Scala并没有把FP和OO结合起来。 它来自于光谱方面的OO方面,这是不寻常的。 大多数情况下,我看到面向对象方法的FP编程语言,并且对它感到困惑。 我想Scala上的FP可能对于函数式语言程序员来说也是一样的。

编辑

读了一些其他的答案,我意识到还有另一个重要的话题:types推断。 Lisp是一种dynamictypes的语言,这几乎是function语言的期望。 现代静态types函数语言都有强types推理系统,最常见的是Hindley-Milner 1algorithm,它使得types声明本质上是可选的。

由于Scala支持inheritance 2 ,Scala不能使用Hindley-Milneralgorithm。 所以Scala必须采用一个function强大得多的types推断algorithm – 事实上,Scala中的types推理在规范中是有意未定义的,并且正在进行改进的主题(它的改进是即将到来的2.8版本的最大特性之一斯卡拉,例如)。

但是,最后,Scala要求所有参数在定义方法时声明它们的types。 在某些情况下,如recursion,方法的返回types也必须声明。

但是,Scala中的函数通常可以推断出它们的types,而不是声明的types。 例如,在这里不需要types声明: List(1, 2, 3) reduceLeft (_ + _) ,其中_ + _实际上是Function2[Int, Int, Int]types的匿名函数。

同样,variables的types声明通常是不必要的,但是inheritance可能需要它。 例如, Some(2)None都有一个通用的超类Option ,但实际上属于不同的子类。 所以通常会声明var o: Option[Int] = None ,以确保分配的types正确。

这种有限的types推理forms比静态types的OO语言通常提供的要好得多,这给Scala带来了轻松的感觉,比静态types的FP语言通常提供的要糟糕得多,这给Scala带来了沉重的感觉。 🙂

笔记:

  1. 实际上,根据维基百科 ,algorithm来自Damas和Milner,他们称之为“algorithmW”。

  2. 马丁·奥德斯基在这里评论说:

    Scala之所以没有Hindley / Milnertypes推理,是因为它很难与超载(ad-hoc变体,不是types类),loggingselect和子types

    他继续说,这可能并不是不可能的,而是取决于交易。 请确实去链接了解更多的信息,如果你提出了一个更清晰的声明,或者更好的是某种纸张,我会很感激的参考。

    让我感谢乔恩·哈罗普 ( Jon Harrop) ,因为我认为这是不可能的 。 那么也许是,我找不到合适的链接。 但请注意,这不是单独造成问题的遗传。

F#是function性的 – 它允许 OO很好,但devise和哲学是function不过。 例子:

  • Haskell风格的函数
  • 自动咖喱
  • 自动generics
  • 为参数input推理

以主要面向对象的方式使用F#感觉相对笨拙,所以可以描述将OO集成到函数式编程中的主要目标。

斯卡拉是多重范式,重点在于灵活性。 你可以select正宗的FP,OOP和程序风格取决于目前最适合的。 这实际上是关于统一面向对象和函数式编程

有很多点可以用来比较两个(或三个)。 首先,我可以想到一些值得注意的点:

  • 句法
    在语法上, F#OCaml基于函数式编程传统(空间分离和更轻量级),而Scala基于面向对象的风格(尽pipeScala使其更加轻量级)。

  • 集成OO和FP
    F#Scala都非常顺利地将OO与FP集成在一起(因为这两者之间没有矛盾!!)可以声明类来保存不可变数据(function方面)并提供与数据相关的成员,还可以使用接口抽象(面向对象方面)。 我不太熟悉OCaml ,但是我认为它更强调OO方面(与F#相比)

  • F#编程风格
    我认为在F#中使用的常用编程风格(如果您不需要编写.NET库,并且没有其他限制)可能更具function性,只有在需要时才使用OOfunction。 这意味着您可以使用函数,模块和代数数据types对function进行分组。

  • Scala中的编程风格
    在Scala中,默认的编程风格更多是面向对象的(在组织中),但是你仍然(可能)编写function程序,因为“标准”方法是编写避免变异的代码。

Scala和F#采取的统一OO和FP范式的方法之间的主要区别是什么?

主要区别在于Scala试图通过牺牲(通常在FP方面)来混合范例,而F#(和OCaml)通常在范例之间划一条线,并让程序员为每个任务select它们。

斯卡拉必须作出牺牲,以统一范例。 例如:

  • 第一类函数是任何函数式语言(ML,Scheme和Haskell)的基本特征。 所有function在F#中都是一stream的。 成员函数是Scala中的第二类。

  • 重载和子types阻碍types推断。 F#提供了一种大型的子语言,当这些function不被使用时(为了在使用时需要types注释),它们牺牲了这些OOfunction以提供强大的types推断。 Scala推动这些function到处都是为了保持一致的面向对象以代价差的types推断为代价。

另一个结果就是F#是基于经过validation的testing思路,而Scala是这方面的开拓者。 这对于项目背后的动机是理想的:F#是一个商业产品,Scala是编程语言研究。

另外,Scala还牺牲了FP的其他核心function,例如由于其select虚拟机(JVM)的局限性而出于实际的原因进行了尾部调用优化。 这也使Scala比FP更多的OOP。 请注意,有一个将Scala带入.NET的项目,它将使用CLR来完成真正的TCO。

每种方法的优点和缺点是什么? 如果,尽pipe支持子types,F#可以推断函数参数的types,那么为什么不能Scala?

types推断与OO中心的特性(如重载和子types)不一致。 F#select重载types推断的一致性。 斯卡拉select无处不在的超载和亚型进行types推断。 这使得F#更像OCaml和Scala更像C#。 特别是,Scala不再是C#的函数式编程语言。

当然,哪个更好是完全主观的,但我个人更喜欢在一般情况下来自强有力的types推断的巨大的简洁和清晰。 OCaml是一个很好的语言,但是一个痛点是缺less操作符重载,需要程序员使用+来input整数+. 对于花车, +/理性等等。 再一次,F#select实用主义超过痴迷,牺牲types推理的重载,特别是在数值的背景下,不仅在算术运算符,而且在算术function,如sin 。 F#语言的每一个angular落都是这样精心select的实用平衡的结果。 尽pipe产生了不一致,我相信这使得F#更有用。

从这篇关于编程语言的文章:

Scala是Java的一个坚固,富有performance力,严格优越的替代品。 Scala是编程语言,我将用它来编写Web服务器或IRC客户端。 与OCaml [或者F#]相比,Scala感觉更像是面向对象和函数式编程的真正混合。 (也就是说,面向对象的程序员应该能够立即开始使用Scala,只是selectfunction部件。)

我首先在POPL 2006上了解了Scala,Martin Odersky就此发表了一个邀请。 当时我看到函数式编程严格优于面向对象编程,所以我没有看到需要融合函数式和面向对象编程的语言。 (这可能是因为我当时写的全部是编译器,解释器和静态分析器。)

直到我从头开始编写一个并发的HTTPD来支持针对yaplet的长时间轮询的AJAX之前,我对Scala的需求并不明显。 为了获得良好的多核支持,我使用Java编写了第一个版本。 作为一种语言,我不认为Java是那么糟糕,而且我可以享受完美的面向对象编程。 但是,作为一个函数式编程人员,当我使用Java编程时,缺乏(或者不必要的详细)支持函数式编程特性(比如高阶函数)。 所以,我给了Scala一个机会。

Scala运行在JVM上,所以我可以逐渐将现有的项目移植到Scala中。 这也意味着除了它自己的相当大的库之外,Scala还可以访问整个Java库。 这意味着你可以在Scala中完成真正的工作。

在我开始使用Scala的时候,function和面向对象的世界巧妙融合在一起,给我留下了深刻的印象。 特别是,Scala有一个强大的案例类/模式匹配系统,解决了我在标准ML,OCaml和Haskell方面的经验:程序员可以决定哪个对象的字段应该匹配(而不是被迫匹配所有这些),并且可变参数参数是允许的。 事实上,Scala甚至允许程序员定义的模式。 我写了很多在抽象语法节点上运行的函数,能够仅匹配句法子代,但仍然有原始程序中的注释或行等字段。 case类系统允许将代数数据types的定义跨多个文件或同一文件的多个部分进行拆分,这非常方便。

Scala还支持通过称为traits的类设备支持定义明确的多重inheritance。

斯卡拉也允许相当程度的超载; 甚至函数应用程序和数组更新都可能被重载。 根据我的经验,这往往会使我的Scala程序更加直观和简洁。

其中一个特性就是保存大量的代码,就像types类在Haskell中保存代码一样。 您可以将implicits想象为types检查器的错误恢复阶段的API。 简而言之,当types检查器需要一个X但是得到一个Y时,它将检查是否有一个隐式函数将Y转换为X; 如果它find一个,它使用隐式“投射”。 这使得看起来像扩展了几乎所有types的Scala,并且允许更紧密地embeddedDSL。

从上面的摘录可以明显看出,Scala统一面向对象和FP范式的方法远远优于OCaml或F#。

HTH。

问候,
埃里克。

F#的语法取自OCaml,而F#的对象模型取自.NET。 这使F#具有function性编程语言的特点,简洁明了,同时允许F#通过对象模型非常顺利地与现有.NET语言和.NET库进行互操作。

Scala在F#上的CLR上执行类似的工作。 不过Scala已经select了更类似于Java的语法。 这可能有助于面向对象程序员的采用,但对function程序员来说可能会感觉有些沉重。 它的对象模型类似于Java允许与Java无缝的互操作,但是有一些有趣的差异,例如支持特性。

如果函数式编程意味着使用函数进行编程,那么Scala会稍微弯曲一点。 在斯卡拉,如果我理解正确,你用方法而不是函数编程。

当方法背后的类(和该类的对象)无关紧要时,Scala会让你假装它只是一个函数。 也许一个斯卡拉语言的律师可以详细说明这个区别(如果它甚至是一个区别),以及任何后果。