为什么Smalltalk不是一门函数式编程语言?

22

随着对函数式编程语言的持续关注,我发现 Smalltalk 和 FPL 之间存在一些相似之处,比如闭包(Smalltalk 中的 BlockClosures)。但是,Smalltalk 不是一种 FPL?

那么,要将其视为一种 FPL,需要什么条件呢?


17
因为Smalltalk是面向对象语言的代表? - CurtainDog
6
能够将函数用作一等对象并不会让一门语言成为“函数式”语言,就像能够编写过程并不会让一门语言成为“过程式”语言一样。 - Gabe
4
有人认为,仅凭这一个特性就足以让一种编程语言被称为“函数式编程语言”。@Gabe说。 - missingfaktor
14
"OO和函数式编程不是互相排斥的范式。" - Frank Shearar
5
@CurtainDog提出的学术观点很好,但在实际情况下并不合理:我所说的并不是某个人写的论文,而是Smalltalk程序员实际上是如何编写代码的,非常注重函数式编程方法,使用对象。这些方法没有副作用,方法/闭包作为一等公民实体(对象),就像另一种函数式编程语言Common Lisp一样。这也不奇怪,因为Smalltalk强烈受到Lisp的影响。 - Frank Shearar
显示剩余13条评论
9个回答

28

关于函数式编程语言并没有被广泛接受的定义。

如果你将支持一级函数作为定义函数式语言的标准,那么,Smalltalk *就是* 一种函数式语言。

如果你还考虑了不可变性支持、代数数据类型、模式匹配、部分应用等因素,那么,Smalltalk *不是* 一种函数式语言。


我建议你阅读以下相关博客文章(以及它们下面的评论):


3
确实,Smalltalk几乎是一种函数式语言。有扩展程序能够提供函数式模式匹配功能,例如http://map.squeak.org/package/3772b420-ba02-4fbd-ae30-8eadfc323b7b。此外,Newspeak(http://newspeaklanguage.org/)是Self和Smalltalk传统上的一种编程语言,支持不可变性作为其核心概念之一。 - Lukas Renggli
1
这个定义不是让C#,Javascript和几乎所有本世纪的语言都变成了函数式编程吗? - Diego Mijelshon
1
@Diego:正如我所说,“函数式编程语言”的定义并没有被广泛接受。因此,按第一种定义,是的;按第二种定义,不是。 - missingfaktor
1
@Missing Faktor - Smalltalk是否提供了声明命名函数的方法? - igouy
3
@AngelO'Sphere,我对一级函数非常熟悉。(Scala和Haskell是我的主要语言。)无论如何,感谢您对该术语的详细解释。现在,将类放置在顶层与具有一级函数是正交的。Scala、Smalltalk等许多语言都同时具备这两个特点。或者你现在要修改你的定义,加上“必须支持顶层函数定义”吗? - missingfaktor
显示剩余9条评论

24

Smalltalk不一定是一个“纯函数式语言”(无论那是什么)。然而,自然使用Smalltalk通常会导致函数式代码。(我猜Scala的人们会称之为“对象-函数式”)。

例如,Squeak的Point类具有函数式API:像1@1 translateBy: 1@1这样的方法返回具有新状态的新Points,而不是改变内部状态。(这使得Point在实践中是不可变的,因为改变对象的内部状态的唯一方式是通过诸如#instVarAt:之类的反射机制。)

在Smalltalk中使用高阶函数(如maps、filters、folds等)是很正常的。鉴于这些被嵌入到Collections API中,编写使用Collections的函数式代码通常比其他方式更容易。

因此,许多人对“函数式编程语言”的定义都有很多定义,因此“Foo是否是FPL”这个问题与“Foo是否是面向对象语言”一样没有用。

话虽如此,以下是我的想法:函数式编程语言是一种自然且习惯性地运用函数式技术的语言:一级函数、避免可变状态、副作用自由函数和高阶函数。

按照这个描述,Smalltalk是一种函数式编程语言。它将一级函数称为“方法”或“块”,具体取决于它们是否具有名称或匿名。对象在实例变量中存储状态。高阶函数只是接受块作为参数的方法。

话虽如此,是的,您可以以非函数式的方式编写Smalltalk。它确实允许可变状态。这是否阻止Smalltalk被称为函数式语言?如果是这样,那么这些语言也不是函数式的:Common Lisp、Erlang(通过进程字典、ets/dets共享状态)。

因此,Smalltalk是一种FPL,因为:

  • 函数是一级实体(对象,在Smalltalk中是CompiledMethods或BlockClosures,要精确些)。
  • Smalltalk支持闭包(它将它们称为块)。
  • Smalltalk允许以自然、惯用的方式进行函数式编程。

而且,Smalltalk不是FPL,因为:

  • 作为程序员,你必须确保你的数据结构(对象)是不可变的(通过使setter/mutator返回具有突变状态的对象的副本来实现)。
  • 作为程序员,你必须确保你的方法(函数)没有副作用。

(一些Smalltalk显然支持不可变对象。我的经验有限,仅限于不支持VM级别的不可变性的Squeak。)

编辑:我不理解igouy需要命名函数定义的需求���除了在对象上定义方法。但无论如何,我们来试试:

Smalltalk at: #func put: [:x | x + 1].
func value: 1.

3
在Smalltalk中,程序是由对象组织而成的,但在函数式编程语言中,程序是由函数组织而成的。在面向对象编程(OOP)和函数式编程(FP)中,我们在程序结构上有明显的基本差异。一个例子可以参考"Synthesizing Object-Oriented and Functional Design to Promote Re-Use"这篇论文。 - igouy
2
“组织成对象”并不是反对Smalltalk不是一种函数式语言的论据。请参见Common Lisp(特别是CLOS)。你说“对象”,我说“一组在自己的命名空间中处理相同类型结构的函数集合”。 - Frank Shearar
1
@iguoy: >> Squeak的Point类具有功能API<< 你读过这个类吗?行为由功能方法提供(即没有副作用,没有可变状态)。它在一组我们称之为方法的函数族中定义。 - Frank Shearar
你读过Point类吗?看到返回值而不是改变状态的方法,你希望我有什么启示? - igouy
显然不行 - 你可以用C语言做到这一点。我不明白的是,为什么你不从一个强有力的角度攻击“Smalltalk是FPL”的立场 - 如果你说“嘿,它不支持不可变数据。你必须通过约定确保不可变性,而Smalltalk甚至不会警告你某些东西具有副作用,就像在Common Lisp中你至少看到nconc或在Scheme中你看到set!,那么我可以理解你的观点。我看不出将函数/方法组织在类中与将它们组织在模块/包中有任何区别。 - Frank Shearar
显示剩余31条评论

13

采用面向对象的编程范式是通过将问题领域实体建模为对象,并使它们相互协作来解决每个问题实例,从而创建程序。 采用函数式编程范式是将问题建模为数学问题,并创建一个数学函数(通过组合其他函数),该函数针对每个问题实例计算问题的解决方案。

我认为,函数式编程语言是一种语言,在使用函数式编程范式解决问题后,语言可以完全按照思考时的方式精确表达该解决方案。如果需要“转换”解决方案的某些部分以适应语言可以表达的内容,则该语言不完全支持您用于思考解决方案的范式。

Smalltalk在大多数情况下可以表达使用面向对象编程范式创建的所有解决方案,但无法原始地表达使用函数式编程范式创建的许多解决方案。这就是为什么我不认为它是函数式编程语言的原因。 尽管不能原始地表达FPL可以的每个解决方案,但Smalltalk非常可扩展,您可以扩展它以能够表达FPL可以的所有解决方案。


3
你能列举一些在Smalltalk中无法“原生”表达的FPL事物的例子吗? - Frank Shearar
5
尽管这是一个旧的讨论串,我对此处提出的OO的定义持有不同意见。OO仅仅是以实体交换消息和封装状态的形式来表达程序;直接将问题域建模为对象不在定义中,而只是-依我之见-是一种工具或甚至是一个技巧。从这个意义上讲,函数式程序可以被看作是封装零状态并通过非常简单的消息("apply")进行交互的实体。这将使函数式模型成为OO的一个子集。 - cdegroot

7

Smalltalk是一种纯面向对象的语言,几乎所有代码都是基于对象之间相互交换消息而进行的。而函数式编程则是基于函数调用和函数组合来创建更复杂的行为(避免为数据保存状态,而不像面向对象语言中的对象那样拥有内部状态)。


3
“几乎全部”指的是除了“1”或“a:= b”这样的表达式之外的所有表达式。函数的组合恰好是我所说的“Smalltalk程序员自然以函数式编程方式编写Smalltalk代码”。内部状态(实例变量的集合)与外部定义的结构相同,就像CONS单元格、列表或哈希映射一样。(重要说明:你必须像我给出的Point类示例那样使用函数式方式编写类的方法。) - Frank Shearar
内部状态(实例变量的集合)与外部定义的结构体相同。除了它们的可访问性之外,例如 - 它们的不同之处。 - igouy
是的,你可以在Smalltalk中编写可变状态的程序。你也可以在Common Lisp中编写,而它通常被认为是一种函数式编程语言。因此,也许你对FPL的定义是“只能使用函数式编程写作的语言”?这将排除大量大多数人认为是函数式的语言:Common Lisp,Scheme,Erlang等。 - Frank Shearar
@Frank Shearar - 我没有提到可变状态,所以你的评论是针对Manuel的吗? - igouy
@igouy 抱歉,我的评论不是很清楚。 "可变状态" 是针对Manuel的,但 "你对FPL的定义" 是针对你的。 - Frank Shearar

5
一个编程语言并不仅仅因为有命名函数就可以成为一个函数式编程语言 - 按照这个定义,C语言也能算是函数式编程语言!更重要的是,在数学意义上,函数式编程语言具有函数式的语义,即结果仅依赖于参数(尤其是没有副作用)。按照这一定义,可以通过setter修改的对象与函数式编程风格相悖。然而,正如已经指出的,如果禁止副作用,甚至可以以函数式风格使用对象(例如矩形示例)。 顺便说一句,对象方法和闭包中定义的一组函数之间存在对偶关系,它们可以互相模拟(请参见SICP了解细节)。
(def (make_foo initVal1 ... initValN)
   (let ((instVar1 initVal1) ... (instVarN initValN))
      (define (method1 args) ...)
      ...
      (define (methodN args) ...)
      (define (lookup selector)
          (cond ((eq? selector 'method1) method1)
             ...
             ((eq? selector 'methodN) methodN)
             (true doesNotUnderstandCode)))
      lookup)) ; returns the lookup function (provides "access into the closure") !

(defMacro (method1 receiver.args)
    ((receiver selector) args))

(define foo (make_foo 1 2 ...))
(method1 foo ...)

上面的内容是“纯”函数对象的模拟,语义上与许多Smalltalk代码相同!然而,要实现一个setter方法,你需要添加一个执行(set! instVar newValue)的方法。并且,因为set!是非函数式的,所以会破坏scheme的“功能性”。
总结:看语义,不看源代码,卢克!

1
只要你的setter返回一个具有所需状态的新对象而不是改变接收器,那么你就可以拥有setter。 - Frank Shearar
1
Smalltalk确实有命名函数。它们被称为方法。它们是绑定到类本地字典中符号的lambda表达式。您还没有解释这与绑定到包中符号的lambda表达式有什么不同。 - Frank Shearar
3
冷静下来:让我们尝试定义一些我们大家都同意的共同基础: 1)函数式编程是使用无副作用函数的方式; 2)函数式语言是指函数式编程是最自然或首选的编程方式的语言。这使得Lisp成为一种函数式语言,尽管它支持非函数式操作(例如set!)和对象(CLOS或类似)。这也使Smalltalk成为一种非函数式语言,尽管它也支持所有函数式编程的东西。 3)你可以在Lisp中以面向对象的风格编程,也可以在Smalltalk中以函数式风格编程。 - blabla999
@blabla999,根据您的说法,Prolog是一种函数式语言。例如, x(Y):- some_func(56, 78, L), some_func2(L, 98, M), some_func3(M, 345, Y)。 - Dmitry Ovchinnikov
@blabla999,相反地,在Scala中,只有实现者才能保证不可变性。你不能说(不查看代码或scaladocs)给定的函数是否无副作用。此外,在Smalltalk中任何人都可以以函数式风格编写程序:到处使用新实例,传递对象的新实例,不共享对象等等。我想说的是,Smalltalk可以模拟函数式行为,并且任何人都可以在Smalltalk中以函数式风格编写程序。 - Dmitry Ovchinnikov
显示剩余3条评论

5

纯函数式语言通常具有不可变的变量,使它们更像数学意义上的变量。


在数学意义上,变量的不可变性是什么?例如,在方程x^2 - 1 = 0中,x是不可变的吗?通过迭代x的所有可能值({-1,1}),是否会使其变得可变?例如,在Scala中的以下代码:val results: Set[Double] = solve("x^2 - 1 = 0")将所有可能的x值返回给results,但函数“solve”可以对内部变量进行一些变异,因此在Smalltalk中,任何实例都可以改变其内部状态以阐述答案。任何人也可以编写一个代码,在Smalltalk中用一个对象回应多个结果。 - Dmitry Ovchinnikov

2
谢谢大家提供的所有答案。
我会在这里补充我的理解,这基本上是我对你们的(可能有误的)理解。
我认为称呼某些东西为面向对象或函数式编程的标准是使用语言构建“东西”的方式;而不是它做起来容易还是难,我的意思是使用的心理范式。
例如,正如链接所示,用Scala打字可能比用OCaml难,但这并不使它不那么函数式(可能不完整或不纯,但绝对是函数式的),因为Scala的目的是使用函数作为主要构建块,而不是对象。
另一方面,如果风格或目的是使用其他工件,使得在语言中轻松编写函数并不使其成为函数式的。例如,Smalltalk使编写匿名块或提供可插入排序比较器非常容易,但这并不使它完全是函数式的,因为重点是使用对象和传递消息。块闭包只是一种编码这些消息(而不是函数)的机制,即使它们看起来像函数。
混淆是因为OO和FP是正交的(如Rahul 在Twitter上所述),因此,在Smalltalk中看起来像编码消息的内容,看起来非常像Scala中的匿名函数。但是,类似的做法并不能将一个范例的语言转换成另一个范例的语言。
更糟糕的是,Scala还使用了OO范例,以使主流(Java、C++、C#)程序员更容易入门,并且如果你看一下,它比其他任何FPL都要成功得多。当然,这要归功于Java(在我看来,如果Clojure有任何成功,将是出于完全相同的原因,即JVM)。
总之:编程语言可以根据其用于构建“东西”的范例进行分类。有些语言是纯的,例如[Smalltalk:OO,Haskell:Functional,C:Procedural],或者是混合的,例如Scala或多范例的,例如C ++、Ruby、Python。
事实上,您可以使用另一种语言编写一种风格,但这并不意味着该语言属于该风格。例如,用Lisp模拟对象并不使其成为OO,也不是使用Java函数使其成为Functional。
回答这个问题,要被认为是功能性的,Smalltalk需要使用函数作为构建块,但它不这样做,因为它使用对象。

你最后一句话已经说得很清楚了。使用一个设计为多范式的语言(我认为 Ruby 可能不是,Python也许也不是,C++ 则太庞大了)来尝试编程,会让你深刻感受到一门语言具有主导风格的含义。我发现使用 Mozart/Oz 很有趣,因为将一个程序从一种风格重新改造成另一种风格非常互动 - 你可以从面向对象开始,将其改写成命令式风格,迭代地去掉破坏性赋值,并将其改写成声明式风格,然后通过并行逻辑编程进行更加复杂的改造。 - igouy

1
我看到Smalltalk和FPL之间有一些相似之处,即闭包。
更基本的相似之处在于 - 每个Smalltalk消息总是返回一个值。
但现在看看Smalltalk背后的设计原则
[Smalltalk]将所需操作的名称以及任何参数作为消息发送到数字中,理解为接收方最了解如何执行所需操作。
这是否描述了您认为的函数式编程?
@Frank Shearar - 这也描述了Erlang。Erlang现在不再是非函数式的吗?
Erlang有点像嵌合体 - 顺序编程基于函数,而并发编程基于进程之间的消息传递。因此,Erlang不是多范式或混合语言,以其他语言允许使用不同的风格来实现相同的目的方式 - 它使用不同的风格来实现不同的目的。

这也描述了Erlang。现在的Erlang是非函数式的吗? - Frank Shearar
我的上一条评论有点讽刺的意味。我不是故意要这样(太)讽刺。上面也描述了晚期绑定,我认为这与FP并不矛盾。 - Frank Shearar
@Frank Shearar >> Erlang现在不可用了吗?<< 您是否已经引用了Joe Armstrong的话,他说“我可能认为,尽管我不确定自己是否相信这一点,但Erlang可能是唯一的面向对象语言?” - igouy
@jgouy,我不明白你的意思。我像头骡子一样固执,或者你是说Emacs的多语言扩展可以教我些什么? - Frank Shearar
@igouy 感谢您的澄清。我很高兴看到您已经回答了我的下一个问题,关于 Erlang :) 我认为在 Erlang 的情况下,基于规模有一个很好的面向对象和函数式的划分:在一个进程内,你有一个纯函数式的范式,而进程之间的通信可能最好被视为 Kay 风格的面向对象范式。(Kay 经常说人们错过了 Smalltalk 的要点 - 宝石是消息传递,而不是对象:http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html) - Frank Shearar
显示剩余3条评论

0
需要考虑什么才能将其视为这样的呢?
  • 声明函数的方式而不是在类中声明方法的方式

  • 围绕函数而不是对象来构建程序的结构

@Missing Faktor - 但在这个例子中,func不是一个命名函数吗?

|func v|
func := [:x | x + 1].
v := func value: 5.

不是 func 是一个本地变量。你已经将一个匿名函数绑定到本地变量func

|func v|
func := [:x | x + 1].
func := 2.
v := func value: 5.

为了看到区别,请查看这个Erlang示例,其中展示了匿名函数和命名函数。


1
你很快就会接触到图灵等价性。我的观点是,当一个语言没有提供命名函数时,谈论它是一种函数式编程语言是愚蠢的。就像谈论C是一种面向对象编程语言时,却没有提供对象一样,也是愚蠢的。 - igouy
1
Smalltalk中确实有命名函数,只不过它们被称为方法。请解释一下方法与命名函数的区别。是的,方法可以访问状态。在Point类的情况下,这些状态是无法访问的、不可变的实例变量。就像任何你想提到的函数式编程语言一样,都有状态,以ADT或其他形式存在。 - Frank Shearar
1
@Frank Shearar - 在面向对象编程(OOP)和函数式编程(FP)中,我们在程序结构上有一个被广泛理解的基本差异 - 它们是对偶的。 - igouy
2
正如您在实践中所知道的,使用Smalltalk工作与其说是提取结构(定义结构体、元组、类)一样重要,还更多地涉及到寻找操作(动词、函数、方法)的共性。FP强调动词而不是名词,而OO则强调名词而不是动词。我认为一种语言可以支持这两种范式,并且我断言,在编写代码时,Smalltalk程序员确实同时实践OO和FP。您上面对FPL的定义包括C,但我不认为它是OOPL或FPL。 - Frank Shearar
2
刚刚一个小时前,你写道:“@igouy,你还没有定义什么是FPL的概念……”,但是现在你却说:“你上面对FPL的定义是……” - igouy
显示剩余4条评论

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