【问题标题】:Functional Programming Vs Declarative Programming Vs Imperative Programming函数式编程与声明式编程与命令式编程
【发布时间】:2012-06-07 04:32:28
【问题描述】:

我已经习惯了命令式编程,这是告诉计算机逐步执行程序以获得最终结果的常用方法。另一方面,声明式编程只是传递输入并期望输出,而不说明它是如何完成的过程。我感到困惑的是函数式编程。我知道函数式编程是一种编程范式,它将计算视为对数学函数的评估,并避免状态和可变数据,并且不是一种声明性语言。但是,我仍然无法理解它是如何工作的。

让我们举一个执行斐波那契数列的例子。

命令式编程:

#include<stdio.h> 
#include<conio.h> 
main() 
{ 
  int n,i,c,a=0,b=1; 
  printf("Enter Fibonacci series of nth term : "); 
  scanf("%d",&n); 
  printf("%d %d ",a,b); 
  for(i=0;i<=(n-3);i++) 
  { 
    c=a+b; 
    a=b; 
    b=c;    
  } 
  printf("%d ",c);
  getch(); 
} 

声明式编程:

Give the nth number and it will return the value of the nth number

功能程序如何工作?

如果我的定义有误,请纠正我。请随时发表评论..

【问题讨论】:

标签: functional-programming terminology imperative-programming declarative-programming


【解决方案1】:

你上面的声明式编程示例不是一个实际的程序,所以它不是一个很好的例子。

主要区别在于命令式和声明式。函数式是一种特殊的声明式。

C、C++、Java、Javascript、BASIC、Python、Ruby 和大多数其他编程语言势在必行。作为一项规则,如果它有显式循环(for、while、repeat)改变变量在每个循环中都有显式赋值操作,那么它是必要的。

SQL 和 XSLT 是 声明式 编程的两个著名示例。 HTML 和 CSS 等标记语言也是声明性的,尽管它们通常不足以描述任意算法。

这是一个示例计算(从合适的数据源按性别对收入求和),首先用命令式语言 (Javascript) 然后用声明性语言 (SQL) 编写。

命令式编程

var income_m = 0, income_f = 0;
for (var i = 0; i < income_list.length; i++) {
    if (income_list[i].gender == 'M')
        income_m += income_list[i].income;
    else
        income_f += income_list[i].income;
}

注意:

  • 显式初始化将包含运行总计的变量;
  • 显式循环数据,修改控制变量(i)和每次迭代的运行总数;
  • 条件 (ifs) 仅用于在每次迭代时选择代码路径

声明式编程

select gender, sum(income)
from income_list
group by gender;

注意:

  • 包含运行总计的内存单元暗示您声明所需的输出;
  • CPU 需要执行的任何循环(例如,在income_list 表上)都暗示由您声明所需的输出和源数据的结构;
  • 条件(例如,SQL 中的case)以函数式 方式根据输入值指定您想要的输出值,而不是选择一个代码路径。

函数式编程

如上所述,SQL 的case函数式 编程方式的一个很好的例子,它是声明式编程的一个受限子集,其中所需的计算由组合指定函数

函数是接受输入和返回输出的事物(例如casesum()...)

组合意味着将两个或多个组合链接在一起,具体方法是指定一个的输出如何作为下一个的输入(通常是通过将一个写入另一个内部)。最后整个组合,它仍然是本身就是一个很大的函数,应用于可用的输入以获得所需的输出。

在这个 sn-p 中,我通过组合函数sum()case声明我想要的输出。这称为函数式编程:

select 
    sum(case when some_flag = 'X' then some_column
        else some_other_column end)
from
    ...

如果两个或多个函数的组合及其对输入数据的应用是给定语言中唯一可用的构造,则该语言被称为纯函数式。在这些语言中,您会注意到完全没有循环、变量赋值和其他典型的命令式语句。


编辑:我建议观看 Anjana Vakil 的一些关于 Javascript 函数式编程的演讲,以便更好地了解它的内容。

【讨论】:

  • 今天仍然高度相关。太棒了!说得好。
  • 这是一个很好的答案,我终于知道什么是声明式编程了! @托比亚
  • 所以,在 C++ 中,std::sort(a.begin(),a.end());可以说是声明式程序吗?
【解决方案2】:

这是一个erroneous oversimplification to claim that 命令式编程与声明式编程的区别在于错误地假设后者缺乏排序。

纯函数式编程并不妨碍表达顺序和实现,而是在操作语义级别上表达随机偶然顺序的能力较差。它还具有“不要重复自己”(DRY)的优点,这是一种声明式风格(见下文)。

但是,纯函数式编程并不能保证声明性的高级语义。为此,您需要申请correct definition of declarative vs. imperative

【讨论】:

  • 更新:另请参阅我的other answer关于声明式编程定义的更详尽的解释。
【解决方案3】:

我在Pro XAML with C#找到的另一个有用的解释:

声明式

在声明式编程中,源代码的编写方式 表达了代码的期望结果,很少或没有强调 在实际执行中。

势在必行

命令式编程与声明式编程相反。如果 声明式编程可以被认为是声明 期望的结果是,命令式编程可以被视为写作 表示如何实现的指令的代码行 期望的结果。

【讨论】:

    【解决方案4】:

    在函数式编程中,我们使用纯函数构建不可变程序。这就是纯函数:(我提到纯函数是因为函数式编程基于纯函数)

    • 它仅取决于提供的输入,而不取决于在评估期间或调用之间可能发生变化的任何隐藏或外部状态。
    • 它不会造成超出其​​范围的更改,例如修改全局对象或通过引用传递的参数。

    与纯函数的简短描述一样,函数式编程是指对纯函数进行声明式评估以创建 通过避免外部可观察到的副作用来实现不可变程序。

    简单地说,函数式编程是声明式编程范式,它是一种表达一组操作的范式,而不会透露它们是如何实现的或数据如何流经它们。命令式编程处理一个 计算机程序只是一系列自上而下的语句,它改变了 系统状态以计算结果。

    这是一个命令式编程的例子:我们循环一个数组,计算每个元素的平方,然后将新值存储在同一个数组中。我们正在改变数组。另外,这个循环是不可复用的,对于每一个不同的数组,我们都要定义一个新的for循环。

    var array= [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
     for(let i= 0; i < array.length; i++) {
         array[i]=Math.pow(array[i], 2);
      }
    array; //-> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    

    在 javascript 中我会使用 Array.map()

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((i)=>i*i)
    

    在这个方法中,我们不知道 Array.map() 是如何实现的。程序的定义与评估是分开的。更重要的是,这段代码不会改变原始数组,而是创建一个新数组

    声明式编程的另一个好例子是编写 SQL 查询。我们只是写了一个简单的 SQL 语句来从数据库中提取数据,但我们不知道幕后发生了什么,所有的魔法都被抽象出来了。

    【讨论】:

      【解决方案5】:

      想想 c 过滤器。您从标准输入读取并写入标准输出的位置。代码可能是命令式的,但程序就像函数一样使用。假设你有一个程序'function,然后通过管道传递给它:

      cat foo |function |tee bar

      将通过函数过滤 foo 的内容,然后通过过滤器 tee 写入 stdout 并创建 bar 。 还可以考虑 grepawk 两者中的迭代器是隐含的,并且它们被用作函数。

      【讨论】: