Scala与F#问题:他们如何统一OO和FP范式?

Nik*_*ale 40 comparison paradigms f# functional-programming scala

Scala和F#采用的方法在统一OO和FP范例方面有哪些主要区别?

编辑

每种方法的相对优点和缺点是什么?如果,尽管支持子类型,F#可以推断函数参数的类型然后为什么不能Scala?

Dan*_*ral 43

我看过F#,做了低级教程,所以我对它的了解非常有限.然而,很明显,它的风格基本上是功能性的,OO更像是一个附加功能 - 更多的是ADT +模块系统而不是真正的OO.我得到的感觉最好被描述为好像其中的所有方法都是静态的(如Java静态).

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

[1 .. 10]
|> List.map     fib

(* equivalent without the pipe operator *)
List.map fib [1 .. 10]
Run Code Online (Sandbox Code Playgroud)

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

另一方面,Scala完全是OO.让我们首先使用与该代码相同的Scala:

List(1 to 10) map fib

// Without operator notation or implicits:
List.apply(Predef.intWrapper(1).to(10)).map(fib)
Run Code Online (Sandbox Code Playgroud)

这里map是一个关于实例的方法List.类似静态的方法,例如intWrapperon Predefapplyon List,更不常见.然后有一些功能,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)
}
Run Code Online (Sandbox Code Playgroud)

上面的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)
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,在Scala中,所有函数都是实现特征的对象FunctionX,它定义了一个名为的方法apply.如此处和上面的列表创建所示,.apply可以省略,这使得函数调用看起来就像方法调用一样.

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

那么,它的功能部分呢?F#属于最传统的函数式语言之一.虽然它没有一些人认为对功能语言很重要的功能,但事实是F#默认是功能,可以这么说.

另一方面,Scala的创建旨在统一功能和OO模型,而不是仅仅将它们作为语言的独立部分提供.它成功的程度取决于您认为的函数式编程.以下是Martin Odersky关注的一些事项:

  • 功能是价值.它们也是对象 - 因为所有值都是Scala中的对象 - 但是函数是一个可以被操作的值的概念是一个重要的概念,其根源一直回到原始的Lisp实现.

  • 对不可变数据类型的强大支持.函数式编程一直关注减少程序的副作用,可以将函数分析为真正的数学函数.所以Scala让事情变得容易变得容易,但它没有做FP纯粹主义者批评它的两件事:

    • 它没有使可变性变得更难.
    • 它没有提供一种效果系统,通过该系统可以静态地跟踪可变性.
  • 支持代数数据类型.代数数据类型(称为ADT,其中混淆地也代表抽象数据类型,是一种不同的东西)在函数式编程中非常常见,并且在通常使用OO语言中的访问者模式的情况下最有用.

    与其他所有内容一样,Scala中的ADT实现为类和方法,使用一些语法糖可以使它们无痛.但是,Scala在支持它们方面比F#(或其他功能语言)更加冗长.例如,|它使用的不是F#的case语句case.

  • 支持非严格性.非严格意味着只按需计算内容.它是Haskell的一个重要方面,它与副作用系统紧密集成.然而,在Scala中,非严格支持是非常胆怯和初期的.它是可用的和使用的,但是受到限制.

    例如,Scala的非严格列表,Stream不支持真正的非严格foldRight,例如Haskell.此外,非严格性的一些好处只有在它是语言的默认值而不是选项时才会获得.

  • 支持列表理解.实际上,Scala称之为理解,因为它的实现方式完全脱离了列表.简单来说,列表推导可以被认为是map示例中显示的函数/方法,尽管通常需要嵌套map语句(flatMap在Scala中支持)以及过滤(filterwithFilter在Scala中,取决于严格性要求).

    这是函数式语言中非常常见的操作,并且通常在语法上很简单 - 就像Python的in运算符一样.再一次,Scala比平常更冗长.

在我看来,Scala在结合FP和OO方面是无与伦比的.它来自朝向FP侧的光谱的OO侧,这是不寻常的.大多数情况下,我看到带有OO的FP语言在它上面得到了解决- 它让我感觉很好.我想Scala上的FP可能对函数式语言程序员来说也是一样的.

编辑

阅读其他一些答案我意识到还有另一个重要的主题:类型推断.Lisp是一种动态类型语言,它几乎设定了对函数式语言的期望.现代静态类型的函数语言都具有强类型推理系统,通常是Hindley-Milner 1算法,它使类型声明基本上是可选的.

由于Scala支持继承2,Scala无法使用Hindley-Milner算法.所以Scala必须采用功能强大的类型推断算法 - 实际上,Scala中的类型推断在规范中有意未定义,并且正在进行改进(它的改进是即将推出的2.8版本的最大特征之一)例如Scala).

但是,最后,Scala要求所有参数在定义方法时声明其类型.在某些情况下,例如递归,还必须声明方法的返回类型.

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

同样,变量的类型声明通常是不必要的,但继承可能需要它.例如,Some(2)并且None有一个共同的超类Option,但实际上属于不同的子类.因此,人们通常会声明var o: Option[Int] = None确保分配了正确的类型.

这种类型推断的有限形式比静态类型的OO语言通常提供的要好得多,这使得Scala具有轻盈感,并且比静态类型的FP语言通常提供的更糟糕,这给Scala带来了沉重感.:-)

笔记:

  1. 实际上,根据维基百科,该算法起源于Damas和Milner,后者称其为"算法W" .

  2. 马丁·奥德斯基在评论中提到这里是:

    Scala没有Hindley/Milner类型推断的原因是很难将其与诸如重载(ad-hoc变体,而不是类型类),记录选择和子类型等功能结合起来

    他接着说,这可能并非实际上是不可能的,而是归结为权衡.请进入该链接以获取更多信息,如果您确实提出了更明确的陈述,或者更好的是,某种方式的论文,我将非常感谢您的参考.

    让我感谢Jon Harrop对此的看法,因为我认为这是不可能的.好吧,也许是,我找不到合适的链接.但请注意,它不是单独的继承导致问题.

  • F#也有一个语法糖版本.`让rec fib =功能| 0 - > 0 | 1 - > 1 | n - > fib(n - 1)+ fib(n - 2)` (5认同)
  • @Damiel,正如@grandbot所说,这只是一种语法糖.请参阅F#规范的6.6.5节(http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.pdf). (3认同)
  • @Daniel,我认为你试图做的一点是**接近**是假的.是的,所有Scala函数都是对象,但默认情况下方法不是它们自己的对象,术语区别很微妙.(我很想把自己的编辑笔带到帖子的那一部分.) (3认同)
  • @Daniel,如果你打算这样做,那么你应该把`fib`做成val.将它作为`def`创建为每个递归调用创建一个新的函数对象.这是很多垃圾. (2认同)
  • "Scala不能使用Hindley-Milner算法,因为Scala支持继承".鉴于OCaml和F#都具有继承性并使用HM,你能否详细说明这一点?此外,它是Hindley-Milner型系统和Damas-Milner型推理算法. (2认同)
  • 我不能为我的生活找到F#不支持的五点之一.虽然我同意答案的第一部分,但我看不出Odersky关注的重点在Scala和F#之间有任何区别.我错过了什么? (2认同)

Dar*_*rio 14

F#是功能性的 - 它允许 OO很好,但设计和哲学仍然是功能性的.例子:

  • Haskell风格的功能
  • 自动currying
  • 自动泛型
  • 键入参数的推断

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

Scala是一种多范式,专注于灵活性.您可以选择真正的FP,OOP和程序样式,具体取决于当前最适合的样式.它实际上是关于统一OO和函数式编程.

  • -1:F#的OOP远远超过Scala的FP(例如TCO不足),因此调用Scala多范例,但F#并不荒谬. (8认同)
  • 这完全是非常不真实的,它作为一种脚本语言非常有用. (7认同)
  • 谁说功能语言不能用作脚本语言?这并不矛盾,实际上最有用的脚本语言显示出重要的功能或声明特征.或者你在F#脚本中编写大类和继承树? (4认同)
  • 就个人而言,我没有发现在F#中使用OO比在C#中使用OO更加痛苦,而且我真的希望C#有一些OO特性(例如对象表达式).不过,我同意在Scala中OO更清晰. (4认同)
  • F#不是一种功能语言,它是一种真正的多范式语言,支持程序,功能和OO(尽管我同意OO方面的笨拙). (3认同)
  • 我喜欢F#的OO,因为它不是被另一个C#项目所消耗的.它有一种非常轻松的感觉.如果你在不使用prototype关键字的情况下执行一些JavaScript OO,它将为您提供有关Functional/OO混合代码的不同视角. (3认同)
  • 它在理论上是多范式的,但事实上只在主要用于功能时才有用. (2认同)

Tom*_*cek 13

您可以使用相当多的点来比较两个(或三个).首先,我可以想到一些值得注意的要点:

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

  • 整合OO和FP
    两个F#Scala的非常顺利整合OO与FP(因为这两种!之间没有矛盾),你可以声明类来一成不变的数据(功能方面),并提供相关的数据工作的成员,可以还使用接口进行抽象(面向对象的方面).我对OCaml并不熟悉,但我认为它更强调OO方面(与F#相比)

  • 在F#编程风格
    我认为,在使用通常的编程风格F# (如果你不需要编写.NET库,并没有其他的限制)可能是更多的功能,你会只使用OO功能,当您需要.这意味着您可以使用函数,模块和代数数据类型对功能进行分组.

  • Scala中的编程风格
    在Scala中,默认编程风格更加面向对象(在组织中),但是您仍然(可能)编写功能程序,因为"标准"方法是编写避免突变的代码.


Jon*_*rop 13

Scala和F#采用的方法在统一OO和FP范例方面有哪些主要区别?

关键的区别在于Scala尝试通过牺牲(通常在FP方面)来混合范例,而F#(和OCaml)通常在范例之间画一条线,让程序员为每个任务在它们之间做出选择.

斯卡拉必须做出牺牲才能统一范式.例如:

  • 一流的功能是任何功能语言(ML,Scheme和Haskell)的基本特征.所有功能都是F#的一流功能.成员函数在Scala中是二等的.

  • 重载和子类型阻碍了类型推断.F#提供了一个大的子语言,牺牲了这些OO特性,以便在不使用这些特征时提供强大的类型推断(在使用它们时需要类型注释).Scala在任何地方都推出这些功能,以便以各种不良类型推理为代价来维持一致的OO.

另一个结果是F#基于久经考验的想法,而Scala在这方面是开创性的.这是项目背后的动机的理想选择:F#是商业产品,Scala是编程语言研究.

另外,由于其选择的VM(JVM)的限制,Scala还牺牲了FP的其他核心功能,例如尾部调用优化,这是出于实际原因.这也使得Scala比FP更多OOP.请注意,有一个项目将Scala引入.NET,它将使用CLR进行真正的TCO.

每种方法的相对优点和缺点是什么?如果,尽管支持子类型,F#可以推断函数参数的类型然后为什么不能Scala?

类型推断与OO为中心的特征(如重载和子类型)不一致.对于重载,F#选择了类型推断而不是一致性.Scala选择了无处不在的重载和子类型而不是类型推断.这使得F#更像OCaml,Scala更像C#.特别是,Scala不再是C#的函数式编程语言.

当然,哪个更好是完全主观的,但我个人更喜欢在一般情况下来自强大类型推断的巨大简洁性和清晰度.OCaml是一种很棒的语言,但是一个痛点就是缺少运算符重载,需要程序员+用于整数,+.浮点数,+/有理数等等.再一次,F#选择实用主义而不是通过在数值的上下文中牺牲类型推断来实现重载,不仅仅是算术运算符,还包括算术函数,如sin.F#语言的每个角落都是经过精心挑选的务实权衡取舍的结果.尽管导致了不一致,我相信这使得F#更有用.


小智 7

这个文章的编程语言:

Scala是Java的坚固,富有表现力,严格优越的替代品.Scala是我用于编写Web服务器或IRC客户端等任务的编程语言. 与OCaml [或F#]相比,它是一种面向对象系统的函数式语言,Scala感觉更像是面向对象和函数式编程的真正混合体.(也就是说,面向对象的程序员应该能够立即开始使用Scala,只在他们选择的时候选择功能部件.)

我第一次在POPL 2006上学习了Scala,当时Martin Odersky发表了邀请演讲.当时我认为函数式编程严格优于面向对象编程,所以我没有看到需要融合功能和面向对象编程的语言.(那可能是因为我当时所写的都是编译器,口译员和静态分析器.)

在我从头开始编写并发HTTPD以支持yaplet的长轮询AJAX之前,对Scala的需求并不明显.为了获得良好的多核支持,我用Java编写了第一个版本.作为一种语言,我认为Java并不是那么糟糕,我可以享受完善的面向对象编程.然而,作为一名函数式程序员,当我使用Java编程时,缺乏(或不必要的冗长)函数编程功能(如高阶函数)的支持会对我产生影响.所以,我给了Scala一个机会.

Scala在JVM上运行,因此我可以逐步将现有项目移植到Scala中.这也意味着Scala除了拥有自己相当大的库外,还可以访问整个Java库.这意味着您可以在Scala中完成实际工作.

当我开始使用Scala时,我对功能性和面向对象的世界如何巧妙地融合在一起感到印象深刻.特别是,Scala有一个强大的案例类/模式匹配系统,它解决了我从标准ML,OCaml和Haskell的经历中挥之不去的困扰:程序员可以决定一个对象的哪些字段应该是匹配的(而不是强制匹配)在所有这些上),允许变量arity参数.事实上,Scala甚至允许程序员定义的模式.我编写了许多在抽象语法节点上运行的函数,并且能够仅匹配语法子节点,但仍然具有诸如原始程序中的注释或行之类的字段的字段.案例类系统允许跨多个文件或跨同一文件的多个部分分割代数数据类型的定义,这非常方便.

Scala还通过称为traits的类类设备支持定义良好的多重继承.

Scala也允许相当程度的重载; 甚至函数应用程序和数组更新都可以重载.根据我的经验,这往往使我的Scala程序更直观和简洁.

事实证明,保存大量代码的一个特性与类型类在Haskell中保存代码的方式相同.您可以将implicits想象为类型检查器的错误恢复阶段的API.简而言之,当类型检查器需要一个X但得到一个Y时,它将检查是否有一个隐式函数在范围内将Y转换为X; 如果它找到一个,它使用隐式"强制转换".这使得您可以看起来像是在扩展Scala中的任何类型,并且它允许更紧密地嵌入DSL.

从上面的摘录中可以清楚地看出,Scala统一OO和FP范式的方法远远优于OCaml或F#.

HTH.

此致,
埃里克.

  • "很明显,Scala统一OO和FP范式的方法优于OCaml或F#." 我担心,为了支持这一陈述,你必须提供证据证明,嫁接的FP的OO优于FP,并且嫁接了OO. (13认同)
  • 我甚至不确定将F#视为FP与OO嫁接时的目标总是CIL完全是OO. (4认同)

sim*_*ins 5

F#的语法取自OCaml,但F#的对象模型取自.NET.这为F#提供了一种轻巧而简洁的语法,这是函数式编程语言的特征,同时允许F#通过其对象模型非常顺利地与现有的.NET语言和.NET库进行互操作.

Scala在JVM上做了类似的工作,F#在CLR上做了类似的工作.然而,Scala选择采用更像Java的语法.这可能有助于面向对象程序员采用它,但对功能程序员来说,它可能会感觉有点沉重.它的对象模型类似于Java,允许与Java无缝互操作,但有一些有趣的差异,如对特性的支持.

  • @Charles:如果这是真的,Scala会做真正的TCO,但事实并非如此,所以用Scala编写的任何实际功能代码都只是堆栈溢出.几乎没有"更好地考虑"...... (5认同)
  • @Jon:"F#中OO出了什么问题?" 没什么,真的:你几乎可以在其中做C#,而且由于Don Syme正在研究mixin,我不会做任何事情.相反,问题是FP使用值,OO适用于类:例如,你在F#中的字符串列表和列表<string>之间有区别(在OCaml中根本没有泛型,所以你甚至无法投射字符串列表到对象).同时在Scala中,所有类型都是类; 类型系统源于对象的Abadi-Cardelli理论,由Odersky扩展,这是Scala FP建立的基础. (4认同)
  • *Scala在JVM上做了类似的工作,F#在CLR*上做了 - 好吧,只是在盒子里滴答作响.Scala确实是Ocaml/F#的功能性和OO风格的更好的结合. (2认同)
  • @Jon:这是一个很好的观点,因为有人说如果没有完整的TCO,你还没有真正的函数式编程.为了保护Scala,他们遇到了问题,因为他们在JVM上并希望与本机Java代码进行完全,强大的交互,并且他们会尽可能地优化尾部调用.请注意,.NET上的Scala确实支持完整尾调用. (2认同)
  • @Charles:我不明白:`string list` vs`list <string>`纯粹是语法,OCaml显然有泛型(它的语言系列发明了泛型!).所有类型都不是OCaml中的类,但我认为它们都在F#中,不是吗?例如,您可以在F#中执行`(1).GetHashCode()`和`[1; 2; 3] .GetHashCode()`. (2认同)
  • @Charles:`list <T>`和`'list`只是同一事物的不同语法(通用列表).它们在语义上是相同的.您在F#,Ada和OCaml中引用的语言功能是相同的功能:参数多态.也许你把它与OOP中的子类型多态性混淆了?当然,F#和OCaml都具有亚型多态性,作为OOP语言. (2认同)