函数式编程语言的独特特点

19

众所周知,所有功能型语言都具有一些基本属性,例如将函数用作程序的基本构建块并使用递归代替迭代等,这些属性会产生各种影响。然而,也存在一些根本性的差异。Lisp使用单一表示法来表示Lisp代码和数据,而ML没有ML代码的标准表示形式。Erlang具有内置的基于Actor的并发性。Haskell有Monad。 Haskell在静态类型系统中区分纯函数和不纯函数;而ML则没有。

其他功能型语言(如Clojure、F#、Arc或其他语言)之间有哪些独特的根本差异?我指的是对开发方式产生影响的东西,而不是它是否与某个广泛运行时集成的因素。


大多数函数式编程语言确实区分代码和数据。在这方面,Lisp是不寻常的。 - Iraimbilanja
我的错,没有深入研究就得出了 Lisp 和 Erlang 的错误结论。 - Sergey Mikhanov
我不清楚你的问题是什么。你是在问“F#与Clojure有何不同”之类的问题,还是其他的问题?你是在寻找特定编程语言的信息,还是函数式编程语言的总体信息? - Brian
是的,就像“F#与Clojure的区别”一样。例如,Haskell通过具有单子将其与Lisp区分开来。 - Sergey Mikhanov
单子是独特的,但我将它们视为更基本特征的副产品:单子在惰性、纯语言中变得有用,并需要高阶数据类型。 - Chris Conway
非常好的问题,但是:“ML”指的是跨越四十年的语言系列,而OCaml(它是最新的ML)实际上确实有OCaml代码的标准表示。短语“Haskell有单子”是误导性的:单子在许多语言中都可以找到,但在Haskell中更为普遍,作为规避副作用禁止的一种解决方法。 - J D
7个回答

26

我能够从脑海中想到:

只有前两项真正适用于函数式语言(即,几乎所有命令式语言都是及早和不纯的)。


不要把“强/弱”类型和“静态/动态”类型混为一谈,它应该是一个独立的概念。 - Svante
1
强类型和弱类型的定义不太明确。我认为静态语言是强类型,动态语言是弱类型。当然,你的看法可能不同。 - Chris Conway
23
静态类型指的是类型在编译时确定,动态类型指的是类型在运行时确定。强类型意味着类型不匹配总是错误的,而弱类型允许隐式转换。这些区别是正交的。例如,C具有弱静态类型,Common Lisp具有强动态类型,Perl具有弱动态类型,Haskell具有强静态类型。 - Svante
@Svante 我已经修正了答案中的术语 - 一旦有人批准它,它就会出现。 - Lambda Fairy

14

我喜欢 Chris Conway 的回答,他列出了一些有助于区分不同函数式语言的重要轴线。

就特定语言的特性而言,我会选择 F# 来介绍一些其他函数式编程语言中没有的特性:

  • Active Patterns: 许多函数式编程语言都有代数数据类型和模式匹配,但 F# 的“活动模式”功能让您定义新模式,使您能够对任意数据使用模式匹配语法。
  • Computation expressions: F# 为编写单子代码提供了一些美观的语法糖;虽然类型系统无法表达高种类多态(不能对类型构造器进行抽象),所以您无法为任意单子 M 编写代码,但您可以为固定单子编写非常酷的代码,在 seq{} 或 async{} 单子中编写一些很棒的推导。
  • Quotations: 常规的“代码作为元编程数据”部分,尽管 F# 具有表达能力强的静态类型系统和丰富的语法,但我不确定有多少非 Lisp 语言可以做到这一点。

就一般分类而言,F# 是:

  • eager (严格的按值调用;但“lazy”是一个关键字和库,使用 seq/IEnumerable 实现一些惰性是常见策略)
  • impure (尽管语法倾向于默认更纯净的风格)
  • static (具有类型推断,所以 F# 通常“感觉像脚本”,只不过带有类型安全性)

你的问题提出了明显的偏见,反对某些额外的语言实用性(例如它集成了哪个运行时),但你也问了“影响您开发方式的因素”,这些东西确实会影响到那些:

  • Visual Studio 集成意味着出色的编辑体验(例如 IntelliSense)。
  • Visual Studio集成意味着出色的调试体验(例如断点/跟踪点,局部变量,立即窗口等)
  • REPL 用于脚本编写或现场UI操作(fsi.exe命令行或VS中集成的“F# Interactive”)
  • .NET集成意味着对于大多数“X”,已经有了一个库来完成该任务
  • 辅助工具如FsLex/FsYacc以及与MSBuild的集成使得“构建系统”易于管理

Objective Caml和Haskell都有非常有趣的准引用机制。 - Norman Ramsey
Scala对自定义模式匹配有出色的支持。Scala、Clojure和Haskell分别拥有构建系统sbt、Leiningen和Cabal。MetaOCaml和Template Haskell支持元编程。除了Visual Studio集成外,F#的功能并不是很独特。 - Lambda Fairy

9
  1. 非严格 vs 严格求值。

  2. 静态 vs 动态类型。

  3. 结构化 vs 名义化静态类型。OCaml是我所知道的唯一具有结构化类型(在对象和多态变体中)的语言,它通过消除定义许多类型(例如变体类型)的需要来弥合与动态类型之间的差距。

  4. Hindley-Milner导数 vs 其他静态类型推断算法。SML、OCaml、Haskell和F#使用基于Hindley-Milner的类型推断算法,而Scala只有本地类型推断(类似于C# 3),需要更多的注释才能编译。(Haskell代码通常在函数级别上充满了类型注释,但大多数都是不必要的,并且添加这些注释是为了文档和在存在错误时帮助编译器)。

  5. 模式匹配 vs 手动解构。SML、OCaml、F#、Haskell、Mathematica和Scheme自动解构值。

  6. 封闭和开放的总和类型。SML、OCaml、F#和Haskell允许定义封闭/密封的代数类型,以通过隐含更具体的约束来加强静态类型。OCaml和F#还允许开放的总和类型,而SML不允许,并且Haskell需要一个复杂的解决方法(由Oleg Kiselyov描述)。

  7. 有界时间模式。在SML中,模式匹配非常快,(普通的)OCaml也很快,但由于活动模式,在F#中的性能未知,甚至在Mathematica中的渐近复杂度也未知。

  8. 即时编译成本地代码。F#、Lisp和Scheme允许在运行时高效地生成、编译和执行代码。

  9. 宏。OCaml、Mathematica、Lisp和Scheme是可扩展的语言。

  10. 标准化 vs 私有化。SML、Haskell 2010、Common Lisp和Scheme是标准化的语言,而OCaml、Erlang、F#和Mathematica是私有化的。


1
什么?OCaml和Erlang是专有的吗? - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
3
我认为Jon的意思是“没有发布标准”,而不是在他们的实现中专有。 - Lambda Fairy
1
“专有”并不等同于“非标准化”。这些是正交属性。许多开源语言是非专有的(根据定义),但没有公布的标准。您也可以拥有具有公布标准的专有语言(.Net上的C#就是一个著名的例子)。 - mikera

5
有很多不同之处,但只有两个差异我认为是根本的,因为它们对你的开发有很大的影响:
1. 动态类型与静态、多态类型系统具有代数数据类型和类型推断。静态类型系统在某种程度上限制了代码,但有许多优点:
- 类型是编译器检查的文档。 - 类型系统帮助您选择下一步要编写的代码,当您不确定要编写什么代码时,类型系统可以帮助您轻松快速地排除许多选择。 - 强大、现代、多态的类型系统非常擅长检测小的、愚蠢的、浪费时间的错误。
2. 惰性求值作为默认值以及惰性求值受到精心控制结构的限制。
- 惰性与急切对于您能够预测和理解程序的时间和空间成本有巨大的影响。 - 在完全惰性的语言中,您可以完全将数据的生产与决定如何处理生成的数据分离。这对于搜索问题尤其重要,因为这样可以更容易地模块化和重用代码。

3

函数式编程是一种风格,而非语言结构

大多数函数式语言都有一些共同的原则:

  • 不可变对象
  • 闭包和匿名函数
  • 通用算法
  • 延续

但最重要的原则是它们通常会强制你以函数式风格编写代码。在大多数语言中都可以使用函数式风格编程。如果您像这样编写代码,C#也可以被认为是“函数式”的,其他任何语言也是如此。


模块和函子不是必需的,但也很常见。 - nlucaroni
我会将函数作为一个值包含进去。 - rampion
是的,但我想知道不同函数式语言之间的独特属性,而不是函数式和非函数式语言之间的区别。 - Sergey Mikhanov
当然,像Rampion提到的那样,函数作为值是非常重要的。类型系统、模式匹配和列表推导也是其他好的特性。 - nlucaroni
这取决于你问的人。我会说,一流的词法闭包使语言具有功能性。Haskell程序员经常使用完全不同的想法,即函数式编程仅意味着纯函数式,即今天基本上只是Haskell。 - J D

2

基本属性?

  • 功能纯度(无副作用)
  • 作为上述的补充,缺乏状态。
  • 函数中的模式匹配

第一个是美丽的,第二个是前者的难看的副作用(双关语)。

缺乏状态的现实补偿是我发现函数式语言之间最大的区别。

这些东西提供了很多免费的好处。大多数时候,语言处理备忘录。


但是大多数函数式编程语言并不是纯的。 - Mauricio Scheffer
一种语言能够被认为是促进函数式编程的程度与其纯度成正比。 - Apocalisp
并不是缺少状态,只是状态存储在其他地方,通常是在堆栈上。 - apg
1
哦,根据纯定义,一种语言的“函数式”程度主要与其高阶函数/值能力相关。如果一种语言难以定义和返回函数,那么它就不太具有函数式特性,对吧? - MichaelGG

2

当你说代码就是数据时,你指的是一种将代码表示为数据结构的语言。这被称为同像性,通常只适用于Lisp方言或与之相似的语言。Haskell、Erlang和Scala不具备同像性,而Clojure则是。

Clojure的基本差异在于:

  1. 它拥有一个软件事务内存系统,使共享状态并发编程更加容易。

  2. 它是一种Lisp,不像Haskell或Erlang,因此所有代码都是数据,这允许您通过宏系统在运行时对语言本身进行所需的更改。

  3. 它运行在JVM上,这意味着您可以直接访问所有Java库。

  4. Clojure数据结构实现了Java接口,例如Collection、List、Map、Runnable和Callable等。字符串只是Java字符串,数字是Java整数和浮点数。这意味着Clojure数据结构可以直接传递给Java库,无需任何桥接或翻译。


2
代码即数据!但这并不等同于同源性。例如,camlp4提供了代码作为数据的功能,但它并不一定是同源的,因为它支持任意语法。 - J D
Haskell确实有用于STM的模块:http://en.wikipedia.org/wiki/Software_transactional_memory#Haskell - mb21

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接