是什么使函数式编程语言声明式而不是命令式?


17

在许多描述函数式编程优势的文章中,我看到函数式编程语言(例如Haskell,ML,Scala或Clojure)被称为“声明性语言”,与命令式语言(例如C / C ++ / C#/ Java)不同。我的问题是什么使函数式编程语言具有声明性而非命令性。

一个经常遇到的描述声明式和命令式编程之间差异的解释是,在命令式编程中,您告诉计算机“如何做某事”,而不是声明性语言中的“做什么”。我在此说明中遇到的问题是,您在所有编程语言中都经常这样做。即使您进入最低级别的程序集,您仍在告诉计算机“该怎么做”,您告诉CPU将两个数字相加,但并没有指示它如何执行加法。如果我们走到另一端,像Haskell这样的高级纯函数式语言,实际上就是在告诉计算机如何完成特定任务,这就是您的程序所要执行的一系列指令,计算机无法单独实现这些任务。我了解到,Haskell,Clojure等语言显然比C / C ++ / C#/ Java高,并提供诸如惰性求值,不可变数据结构,匿名函数,currying,持久数据结构等功能,所有这些使得函数式编程是可能且高效的,但我不会将它们归类为声明性语言。

对我而言,纯声明性语言将是仅由声明完全构成的语言,此类语言的示例将是CSS(是的,我知道CSS从技术上讲不是编程语言)。CSS只包含页面的HTML和Javascript使用的样式声明。CSS除了作声明外,不能做任何其他事情,它不能创建类函数,即,基于某些参数确定要显示的样式的函数,您无法执行CSS脚本等。对于我来说,它描述了声明性语言(注意,我没有说声明性语言编程语言)。

更新:

我最近一直在使用Prolog,对我而言,Prolog是最接近完全声明式语言的编程语言(至少在我看来),如果它不是唯一的完全声明式编程语言。要详细说明Prolog中的编程,请进行声明,声明要说明事实(规则(对于特定输入返回true的谓词函数))或规则(对基于输入的给定条件/模式返回true的谓词函数),规则使用模式匹配技术定义。要在序言中做任何事情,您都可以通过用变量替换谓词的一个或多个输入来查询知识库,而序言会尝试查找谓词成功的变量的值。

我的观点是在序言中没有强制性指令,您基本上是在告诉(声明)计算机知道的信息,然后询问(查询)有关知识的信息。在函数式编程语言中,即使您不是直接操作内存位置或不逐步编写计算,也仍在给出指令,即获取值,调用函数X并为其添加1等。从这个意义上讲,我不会说用Haskell,ML,Scala或Clojure进行编程是声明性的,尽管我可能是错的。在我上面所描述的意义上,它是正确,真实,纯函数式的声明式编程。


当您需要他时,@ JimmyHoffa在哪里?

2
1. C#和现代 C ++是非常实用的语言。2.考虑编写SQL和Java达到类似目的的区别(也许是一些带有联接的复杂查询)。您还可以认为LINQ在C#中与编写简单的foreach循环有何不同...
AK_

差异

1
认识到差异的关键之一是使用赋值运算符与定义/声明运算符的比较-例如,在Haskell中,没有赋值运算符。这两个运算符的行为非常不同,因此迫使您以不同的方式设计代码。
吉米·霍法

1
不幸的是,即使没有赋值运算符,我也可以做到这一点(let [x 1] (let [x (+ x 2)] (let [x (* x x)] x)))(希望您理解,Clojure)。我最初的问题是,这与int的不同之处是什么?x = 1; x += 2; x *= x; return x;在我看来,它大体相同。
ALXGTV

Answers:


16

您似乎在声明和指导机器之间划清界限。没有如此严格的分离。由于命令式编程中指示的机器不需要是物理硬件,因此有很大的解释自由。几乎所有内容都可以看作是正确的抽象机的显式程序。例如,您可以将CSS视为一种高级语言,用于对机器进行编程,该机器主要解析选择器并设置由此选择的DOM对象的属性。

问题是这样的观点是否合理,相反,指令序列与正在计算的结果的声明有多相似。对于CSS,声明性观点显然更有用。对于C来说,命令式观点显然占主导地位。至于Haskell这样的语言,那么……

该语言已指定了具体的语义。也就是说,当然可以将程序解释为一连串的操作。选择原始操作甚至不需要花费太多精力,就可以将它们很好地映射到商品硬件上(这就是STG机器和其他模型的作用)。

但是,以编写Haskell程序的方式,通常可以明智地将它们理解为要计算的结果的描述。例如,以计算前N个阶乘的和的程序为例:

sum_of_fac n = sum [product [1..i] | i <- ..n]

您可以对此进行解糖并将其作为STG操作序列阅读,但更自然的是将其阅读为结果描述(我认为这是声明性编程的一种比“计算什么”更有用的定义):结果是[1..i]所有i= 0,…,n 的乘积之和。这几乎比几乎所有C程序或函数都更具声明性。


1
我问这个问题的原因是,许多人试图促进函数式编程,因为一些新的独特范例与C / C ++ / C#/ Java甚至Python范例完全分离,而实际上函数式编程只是C / C ++ / C#/ Java的另一种形式。命令式编程。
ALXGTV 2015年

为了解释我的意思,您正在定义sum_of_fac,但sum_of_fac本身几乎没有用,除非那是您的Hole应用程序所做的,您需要使用该结果来计算其他一些结果,这些结果最终会积累起来以实现特定任务,您的程序旨在解决的任务。
ALXGTV 2015年

6
@ALXGTV函数式编程一个非常不同的范例,即使您坚持将其视为必须的(它是一个非常广泛的分类,编程范例还不止于此)。基于此代码的其他代码也可以声明式读取:例如,map (sum_of_fac . read) (lines fileContent)。当然,有时I / O会起作用,但是正如我所说,这是一个连续的过程。当然,Haskell程序的某些部分更势在必行,就像C具有排序声明式表达式语法(x + y,not load x; load y; add;

1
@ALXGTV您的直觉是正确的:声明式编程“只是”在某些命令性细节上提供了更高级别的抽象。我认为这很重要!
安德烈斯·F

1
@Brendan我不认为函数式编程是命令式编程的子集(不变性也不是FP的强制特性)。可以说,就纯静态类型的FP而言,它是命令式编程的超集,其中,作用在类型中是明确的。您知道哈斯克尔是“世界上最好的命令性语言”的嘲讽断言;)
安德烈斯·F

21

命令式程序的基本单元是语句。执行语句是出于副作用。他们修改收到的状态。语句序列是命令序列,表示先执行然后再执行。程序员指定执行计算的确切顺序。这就是人们告诉计算机如何做的意思。

声明式程序的基本单元是表达式。表达式没有副作用。它们指定输入和输出之间的关系,从其输入创建新的单独的输出,而不是修改其输入状态。表达式序列是没有意义的,没有一些包含表达式的表达式可以指定它们之间的关系。程序员指定数据之间的关系,然后程序从这些关系中推断执行计算的顺序。这就是人们告诉计算机要做什么的意思。

命令式语言具有表达式,但是它们使事情完成的主要手段是语句。同样,声明性语言的某些表达式具有与语句相似的语义,例如Haskell do表示法中的monad序列,但从本质上讲,它们是一个重要的表达式。这使初学者可以编写看起来非常命令式的代码,但是当您摆脱这种范例时,语言的真正力量就会显现。


1
如果包含示例,这将是一个完美的答案。我所知道的说明声明式与命令式的最佳例子是Linq vs foreach.。
罗伯特·哈维

7

将声明式与命令式编程区分开的真正定义特征是声明式,您没有给出顺序的指令。在较低级别,是的,CPU以这种方式运行,但这是编译器所关心的。

您建议CSS是一种“声明性语言”,我完全不会将其称为语言。它是一种数据结构格式,例如JSON,XML,CSV或INI,只是一种定义某种解释器所知道的数据的格式。

当您使赋值运算符脱离某种语言时,会发生一些有趣的副作用,这是丢失声明性语言中所有这些step1-step2-step3命令式指令的真正原因。

您如何在函数中使用赋值运算符?您创建中间步骤。这就是问题的关键,赋值运算符用于逐步更改数据。一旦无法再更改数据,您将失去所有这些步骤并最终得到:

每个函数只有一个语句,该语句是单个声明

现在有很多方法可以使一个语句看起来像很多语句,但是,这只是一个骗局:例如,1 + 3 + (2*4) + 8 - (7 / (8*3))这显然是一个语句,但是如果您将其编写为...

1 +
3 +
(
  2*4
) +
8 - 
(
  7 / (8*3)
)

它可以呈现一系列操作的外观,使大脑更容易识别作者想要的分解。我经常在C#中使用类似代码的方法来执行此操作。

people
  .Where(person => person.MiddleName != null)
  .Select(person => person.MiddleName)
  .Distinct();

这是一条语句,但显示了被分解为许多语句的外观-请注意,没有分配。

还要注意,在上述两种方法中-实际执行代码的方式均与您立即读取代码的顺序不同;因为此代码说明了该怎么做,但没有规定如何做。注意,上面的简单算法显然不会按照从左到右的顺序执行,在上面的C#示例中,这3种方法实际上都是可重入的,并且没有按顺序执行,实际上编译器会生成代码会做什么声明希望,但不一定怎么你承担。

我相信习惯于这种无中间步骤的声明式编码方法确实是所有过程中最困难的部分。以及为什么Haskell如此棘手,因为很少有语言像Haskell那样真正禁止它。您必须开始做一些有趣的体操运动,才能完成通常借助中间变量(例如求和)完成的一些事情。

在声明性语言中,如果希望中间变量执行某项操作,则意味着将其作为参数传递给执行该操作的函数。这就是为什么递归变得如此重要的原因。

sum xs = (head xs) + sum (tail xs)

您无法创建resultSum变量并在遍历时将其添加到其中xs,必须简单地获取第一个值,并将其添加到其他所有值的总和中,并访问必须传递xs给函数的其他所有内容,tail因为您不能只为xs创建一个变量并突然退出,这将是一种必要的方法。(是的,我知道您可以使用解构,但是这个示例只是为了说明)


根据您的回答,我的理解是,在纯函数式编程中,整个程序由许多单表达式函数组成,而在命令式编程函数(过程)中,包含多个具有定义的操作序列的表达式
ALXGTV 2015年

1 + 3 +(2 * 4)+ 8-(7 /(8 * 3))实际上是多个语句/表达式,当然这取决于您将单个表达式视为什么。综上所述,函数式编程基本上是没有分号的编程。
ALXGTV

1
@ALXGTV声明和命令式风格之间的区别在于,如果该数学表达式为语句,则必须按书面形式执行它们。但是,由于它不是命令性语句的序列,而是定义所需内容的表达式,因为它没有说明how,因此不必按书面形式执行。因此,编译可以减少表达式,甚至可以将其记忆为文字。命令式语言也可以这样做,但前提是您将其放在单个语句中。声明。
吉米·霍法

@ALXGTV声明定义关系,关系具有延展性;如果x = 1 + 2那么x = 3x = 4 - 1。顺序语句定义了指令,因此当x = (y = 1; z = 2 + y; return z;)您不再具有可塑性时,编译器可以通过多种方式进行操作-而是必须由编译器按照您的指示进行操作,因为编译器无法了解指令的所有副作用,因此可以”改变他们。
吉米·霍法

你有一个公平的观点。
ALXGTV

6

我知道我参加晚会很晚,但是前几天我顿悟了,所以就这样了...

我认为,在解释声明式与命令式之间的差异或解释声明式编程的含义时,有关功能,不可变且没有副作用的评论会错失良机。另外,正如您在问题中提到的那样,整个“做什么”与“如何做”都太模糊了,实际上也没有解释。

让我们以简单的代码a = b + c为基础,并以几种不同的语言查看该语句以了解主意:

当我们a = b + c以命令式语言(如C)编写代码时,我们将把当前b + c赋给变量a,仅此而已。我们没有对什么做任何基本声明a。相反,我们只是在流程中执行一个步骤。

当我们写a = b + c的声明性语言,如Microsoft Excel,(是的,Excel中一种编程语言,可能是最陈述他们所有,)我们断言的关系之间ab并且c这样的,它始终是该案件a的总和另外两个。这不是过程中的一步,而是不变,保证和真理的声明。

函数式语言也是声明性的,但几乎是偶然的。例如,在Haskel中a = b + c也断言一个不变的关系,但这仅仅是因为bc是不可变的。

因此,是的,当对象是不可变的且函数没有副作用时,代码将变为声明性的(即使它看起来与命令性代码相同),但这不是重点。也不是避免分配。声明式代码的重点是对关系做出基本声明。


0

你是正确的,没有告诉计算机之间没有明显的区别是什么做的,怎么做。

但是,在频谱的一侧,您几乎只考虑如何操作内存。也就是说,要解决问题,您可以采用“将此内存位置设置为x,然后将该内存位置设置为y,再跳至内存位置z ...”的形式将其显示给计算机,然后最终以某种方式拥有结果放在其他内存位置。

在Java,C#等托管语言中,您不再可以直接访问硬件内存。现在,当务之急是程序员关心的是静态变量,类实例的引用或字段,所有这些在某种程度上都是内存位置的抽象。

在像Haskell,OTOH这样的语言中,内存已完全消失。根本不是这样的

f a b = let y = sum [a..b] in y*y

必须有两个记忆单元,分别容纳辩护词a和b,另一个记忆单元,其保留中间结果y。可以肯定的是,编译器后端可以发出以这种方式工作的最终代码(从某种意义上说,只要目标体系结构是Neumann机器,它就必须这样做)。

但是关键是我们不需要内部化Neumann架构来了解上述功能。我们也不需要现代计算机来运行它。例如,将纯FP语言的程序转换为可以在SKI演算基础上运行的假设机器很容易。现在使用C程序尝试相同的操作!

对我而言,纯声明性语言将是仅由声明组成的一种语言

恕我直言,这还不够强大。甚至C程序也只是一系列声明。我认为我们必须进一步限定这些声明。例如,他们在告诉我们什么东西(声明),或者是什么(必要)。


我没说过函数式编程和用C语言编程没什么不同,实际上,我说过像Haskell这样的语言比C处于更高的水平,并且提供了更大的抽象性。
ALXGTV 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.