首页 文章

功能和命令式编程语言有什么区别?

提问于
浏览
129

大多数主流语言,包括 object-oriented 编程(OOP)语言,如 C#,Visual Basic,C 和 Java,主要用于支持命令式(程序性)编程,而 Haskell/gofer 类语言纯粹是功能性的。任何人都可以详细说明这两种编程方式之间的区别吗?

我知道这取决于用户要求选择编程方式,但为什么建议学习函数式编程语言?

8 回答

  • 136

    **定义:**命令式语言使用一系列语句来确定如何达到某个目标。据说这些语句会改变程序的状态,因为每个程序依次执行。

    示例: Java 是一种命令式语言。例如,可以创建一个程序来添加一系列数字:

    int total = 0;
     int number1 = 5;
     int number2 = 10;
     int number3 = 15;
     total = number1 + number2 + number3;
    

    每个语句都会更改程序的状态,从为每个变量赋值到最终添加这些值。使用五个语句的序列,程序被明确告知如何将数字 5,10 和 15 一起添加。

    **函数式语言:**明确创建函数式编程范例,以支持解决问题的纯函数式方法。函数式编程是一种声明式编程。

    **纯函数的优点:**将函数转换实现为纯函数的主要原因是纯函数是可组合的:即 self-contained 和无状态。这些特性带来许多好处,包括:增加可读性和可维护性。这是因为每个函数都是为了在给定参数的情况下完成特定任务而设计的。该功能不依赖于任何外部状态。

    更容易重复开发。由于代码更容易重构,因此设计更改通常更容易实现。例如,假设您编写了一个复杂的转换,然后意识到某些代码在转换中会重复多次。如果您通过纯方法重构,您可以随意调用纯方法而不必担心副作用。

    更容易测试和调试。由于纯函数可以更容易地单独测试,因此您可以编写使用典型值,有效边缘情况和无效边缘情况调用纯函数的测试代码。

    对于 OOP People 或 Imperative 语言:

    当你对事物有一套固定的操作时,Object-oriented 语言是很好的,随着你的代码的发展,你主要添加新东西。这可以通过添加实现现有方法的新类来实现,并且现有类是独立的。

    当你有一套固定的东西时,功能语言很好,随着代码的发展,你主要在现有的东西上添加新的操作。这可以通过添加使用现有数据类型计算的新函数来实现,并且现有函数是独立的。

    缺点:

    这取决于用户选择编程方式的要求,因此只有当用户没有选择正确的方法时才有害。

    当进化走错路时,你会遇到问题:

    • 向 object-oriented 程序添加新操作可能需要编辑许多类定义以添加新方法

    • 在功能程序中添加新类型的东西可能需要编辑许多函数定义来添加新案例。

  • 207

    这是区别:

    势在必行:

    • 开始

    • 打开鞋子尺码 9 1/2。

    • 在你的口袋里腾出空间来保持钥匙的阵列[3]。

    • 将钥匙放在房间里,放入口袋里的钥匙。

    • 进入车库。

    • 打开车库。

    • 输入汽车。

    ......等等......

    • 把牛奶放入冰箱。

    • 停止。

    声明性的,其中 function 是子类别:

    • 牛奶是一种健康饮品,除非你有消化乳糖的问题。

    • 通常,人们将牛奶储存在冰箱中。

    • 冰箱是一种可以让物品保持凉爽的盒子。

    • 商店是销售商品的地方。

    • “卖”是指用钱换货。

    • 此外,为事物换钱也称为“购买”。

    ......等等......

    • 确保我们在冰箱里有牛奶(当我们需要它时 - 用于懒惰的功能语言)。

    简介:在命令式语言中,您告诉计算机如何更改其内存中的位,字节和单词以及顺序。在功能性方面,我们告诉计算机什么是事物,行为等。例如,我们说阶乘为 0 是 1,而每个其他自然数的阶乘是该数字与其前身的阶乘的乘积。我们不说:要计算 n 的阶乘,保留一个存储区并在那里存储 1,然后将该存储区中的数字乘以数字 2 到 n,并将结果存储在同一个地方,最后,内存区域将包含阶乘。

  • 11

    大多数现代语言在不同程度上都具有命令性和功能性,但为了更好地理解函数式编程,最好采用像 Haskell 这样的纯函数式语言的示例,而不是像#1_#这样的函数式语言中的命令式代码。我相信通过实例解释总是很容易,所以下面是一个。

    函数式编程:计算 n i.e n 的阶乘! i.e n x(n-1)x(n-2)x ...x 2 X 1

    -- | Haskell comment goes like
    -- | below 2 lines is code to calculate factorial and 3rd is it's execution  
    
    factorial 0 = 1
    factorial n = n * factorial (n - 1)
    factorial 3
    
    -- | for brevity let's call factorial as f; And x => y shows order execution left to right
    -- | above executes as := f(3) as 3 x f(2) => f(2) as 2 x f(1) => f(1) as 1 x f(0) => f(0) as 1  
    -- | 3 x (2 x (1 x (1)) = 6
    

    请注意,Haskel 允许函数重载到参数值的级别。以下是增加强制程度的命令式代码示例:

    //somewhat functional way
    function factorial(n) {
      if(n < 1) {
         return 1;
      }
      return n * factorial(n-1);   
    }
    factorial(3);
    
    //somewhat more imperative way
    function imperativeFactor(n) {
      int f = 1
      for(int i = 1; i <= n; i++) {
         f = f * i
      }
      return f;
    }
    

    这个可以很好地理解命令式代码如何更多地关注部分,机器状态(i for for 循环),执行顺序,流控制。

    后面的例子可以看作 java/c#lang 代码大致和第一部分作为语言本身的限制,与 Haskell 相比,通过值(零)重载函数,因此可以说它不是纯粹的函数式语言,另一方面你可以说它支持功能前卫。在某种程度上。

    披露:上述代码都不是 tested/executed,但希望应该足以传达这个概念;我也很感激任何此类更正的评论:)

  • 7

    函数式编程是一种声明性编程形式,它描述了计算逻辑,执行顺序完全是 de-emphasized。

    问题:我想把这个生物从马变成长颈鹿。

    • 加长脖子

    • 加长腿

    • 应用斑点

    • 给这个生物一个黑色的舌头

    • 去掉马尾

    每个项目都可以按任何顺序运行,以产生相同的结果。

    命令式编程是程序性的。国家和秩序很重要。

    问题:我想把车停好。

    • 注意车库门的初始状态

    • 在车道上停车

    • 如果车库门关闭,打开车库门,记住新的状态;否则继续

    • 把车拉进车库

    • 关闭车库门

    必须完成每个步骤才能达到预期的结果。在车库门关闭时进入车库会导致车库门破损。

  • 5

    函数式编程是“使用函数编程”,其中函数具有一些预期的数学属性,包括引用透明性。从这些属性中,进一步的属性流动,特别是通过可替代性实现的熟悉的推理步骤导致数学证明(i.e.证明结果的置信度)。

    因此,功能程序仅仅是一种表达。

    您可以通过在命令式程序中注意表达式不再引用透明(因此不是使用函数和值构建,并且本身不能成为函数的一部分)来轻松地查看两种样式之间的对比。两个最明显的地方是:变异(e.g. 变量)其他 side-effects non-local 控制流程(e.g. 例外)

    在由函数和值组成的 programs-as-expressions 框架上,构建了语言,概念,“功能模式”,组合器以及各种类型系统和评估算法的完整实践范例。

    通过最极端的定义,几乎任何语言 - 甚至 C 或 Java - 都可以被称为功能性的,但通常人们为具有特定相关抽象的语言保留术语(例如闭包,不可变值和模式匹配等语法辅助)。就函数式编程的使用而言,它涉及使用 functins 并构建代码而没有任何副作用。用来写证明

  • 1

    从 2005 年到 2013 年,Web 开发实施了势在必行的编程风格。

    通过命令式编程,我们编写了代码,逐步列出了我们的应用程序应该做的事情。

    函数式编程风格通过巧妙的函数组合方式产生抽象。

    在答案中提到了声明性编程,关于我将说声明性编程列出了我们要遵循的一些规则。然后,我们将我们称为初始状态的内容提供给我们的应用程序,并让这些规则定义应用程序的行为方式。

    现在,这些快速描述可能没有多大意义,所以让我们通过类比来解决命令式和声明式编程之间的差异。

    想象一下,我们不是在构建软件,而是以烘焙馅为生。也许我们是坏的面包师,不知道如何以我们应该的方式烘烤美味的馅饼。

    所以我们的老板给了我们一份方向清单,我们知道的食谱。

    食谱将告诉我们如何制作馅饼。一个配方是用命令式的方式编写的,如下所示:

    • 混合 1 杯面粉

    • 加 1 个鸡蛋

    • 加 1 杯糖

    • 将混合物倒入锅中

    • 将锅放入烤箱中 30 分钟和 350 华氏度。

    声明性配方将执行以下操作:

    1 cup 面粉,1 个鸡蛋,1 杯糖 - 初始状态

    规则

    • 如果一切都混合了,放在锅里。

    • 如果一切都未混合,请放入碗中。

    • 如果一切都在锅里,放在烤箱里。

    因此,必要的方法以逐步方法为特征。您从第一步开始,然后转到第 2 步,依此类推。

    你最终会得到一些最终产品。因此,制作这个馅饼,我们将这些成分混合,放入锅中,放入烤箱中,然后就可以得到最终产品。

    在一个声明性的世界中,它的声明性配方我们将我们的配方分成两个独立的部分,从一个列出配方初始状态的部分开始,比如变量。所以这里的变量是我们的成分数量和类型。

    我们采用初始状态或初始成分并对它们应用一些规则。

    所以我们采取初始状态并一遍又一遍地通过这些规则,直到我们准备好吃大黄草莓派或其他什么。

    因此,在声明式方法中,我们必须知道如何正确地构建这些规则。

    因此,我们可能想要检查我们的成分或状态的规则,如果混合,将它们放入锅中。

    在我们的初始状态,这是不匹配的,因为我们还没有混合我们的成分。

    因此规则 2 说,如果他们没有混合,那么将它们混合在一个碗里。好的,是的,这条规则适用。

    现在我们有一碗混合成分作为我们的州。

    现在我们再次将新状态应用于我们的规则。

    所以规则 1 说如果混合成分将它们放入锅中,好吧,现在规则 1 确实适用,让我们这样做。

    现在我们有了这个新的状态,其中的成分混合在锅里。规则 1 不再适用,规则 2 不适用。

    规则 3 说如果成分放在平底锅中,将它们放入烤箱中,那很好的规则适用于这个新状态,让我们这样做。

    我们最终得到一个美味的热苹果派或其他什么。

    现在,如果你像我一样,你可能在想,为什么我们还没有做必要的编程。这是有道理的。

    好吧,对于简单流程是的,但是大多数 Web 应用程序都有更复杂的流程,这些流程无法通过命令式编程设计正确捕获。

    在声明方法中,我们可能有一些初始成分或初始状态,如textInput="",一个变量。

    也许文本输入以空字符串开头。

    我们采用此初始状态并将其应用于应用程序中定义的一组规则。

    • 如果用户输入文本,请更新文本输入。那么,现在不适用。

    • 如果呈现模板,请计算窗口小部件。

    • 如果更新了 textInput,请重新呈现模板。

    嗯,这都不适用,所以程序只会等待事件发生。

    因此,在某些时候用户更新文本输入,然后我们可以应用规则 1。

    我们可以将其更新为"abcd"

    所以我们刚刚更新了我们的 text 和 textInput 更新,规则号 2 不适用,规则 3 表示如果文本输入更新,刚刚发生,然后重新渲染模板,然后我们回到规则 2,即如果模板被渲染,计算小部件,好吧让我们计算小部件。

    通常,作为程序员,我们希望争取更多的声明性编程设计。

    命令似乎更加明确和明显,但声明性方法可以很好地扩展到更大的应用程序。

  • 0

    我认为以强制方式表达函数式编程是可能的:

    • 使用大量状态检查对象和if... else/switch语句

    • 一些超时/等待机制来处理异步

    这种方法存在很大问题:

    • 重复规则/程序

    • 有状态会给 side-effects/错误带来机会

    功能编程,处理对象等功能/方法以及拥抱无状态,是为了解决我认为的那些问题而诞生的。

    用法示例:前端应用程序,如 Android,iOS 或 Web 应用程序的逻辑,包括。与后端沟通。

    使用命令式/过程式代码模拟函数式编程时的其他挑战:

    • 比赛条件

    • 复杂的组合和事件序列。例如,用户尝试在银行应用程序中汇款。步骤 1)并行执行以下所有操作,只有在一切正常时才进行 a)检查用户是否仍然良好(欺诈,AML)b)检查用户是否有足够的余额 c)检查收件人是否有效(欺诈, AML)等。步骤 2)执行传输操作步骤 3)显示用户余额和/或某种跟踪的更新。以 RxJava 为例,代码简洁明了。没有它,我可以想象会有很多代码,混乱和容易出错的代码

    我还相信,在一天结束时,功能代码将被转换为汇编程序或机器代码,这是编译器必不可少的。但是,除非你编写程序集,因为人类用高级/ human-readable 语言编写代码,函数式编程是表达所列方案的更合适的表达方式

  • -2

    我知道这个问题比较老,其他人已经解释得很好,我想举一个例子,用简单的术语解释同样的问题。

    问题:写 1 表。

    解决方案: -

    按命令式:=>

    1*1=1
        1*2=2
        1*3=3
        .
        .
        .
        1*n=n
    

    按功能样式:=>

    1
        2
        3
        .
        .
        .
        n
    

    在命令式样式中的解释我们更明确地编写指令,并且可以以更简化的方式调用它们。

    在功能样式中,self-explanatory 的东西将被忽略。

相关问题