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

What is difference between functional and imperative programming languages?

大多数主流语言,包括面向对象程序设计(OOP)语言,如C语言、Visual Basic、C++和Java,都是为了支持命令式(程序)编程而设计的,而Haskell/Goffer-A类语言则是纯粹的功能性语言。有人能详细说明这两种编程方法的区别吗?

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


区别如下:

祈使语气:

  • 起点
  • 穿上9.1/2码的鞋子。
  • 在口袋里腾出空间放一排钥匙。
  • 把钥匙放在房间里,把钥匙放在口袋里。
  • 进入车库。
  • 开放式车库。
  • 进入汽车。

…等等……

  • 把牛奶放进冰箱。
  • 停下来。

声明性,其功能为子类别:

  • 牛奶是一种健康的饮料,除非你有消化乳糖的问题。
  • 通常,人们把牛奶储存在冰箱里。
  • 冰箱是一个能让里面的东西保持凉爽的盒子。
  • 商店是出售商品的地方。
  • 所谓"卖",就是把东西换成钱。
  • 此外,货币交换被称为"购买"。

…等等……

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

摘要:在命令式语言中,你告诉计算机如何改变内存中的位、字节和字,以及改变顺序。在功能上,我们告诉计算机什么是事物、动作等。例如,我们说0的阶乘是1,而其他自然数的阶乘是这个数和其前一个数的阶乘的乘积。我们不说:要计算n的阶乘,保留一个内存区域并将1存储在那里,然后将该内存区域中的数字与数字2到n相乘,并将结果存储在相同的位置,最后,内存区域将包含阶乘。


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

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

1
2
3
4
5
 int total = 0;
 int number1 = 5;
 int number2 = 10;
 int number3 = 15;
 total = number1 + number2 + number3;

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

功能语言:函数编程范式被显式地创建来支持纯粹的函数方法来解决问题。函数式编程是声明式编程的一种形式。

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

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

更容易测试和调试。因为纯函数可以更容易地进行隔离测试,所以可以编写用典型值、有效边缘情况和无效边缘情况调用纯函数的测试代码。

对于OOP人员或命令式语言:

面向对象语言是很好的,当您对事物有一组固定的操作,并且随着代码的发展,您主要添加新事物。这可以通过添加实现现有方法的新类来实现,而现有类是单独存在的。

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

欺骗:

编程方式的选择取决于用户的需求,因此只有当用户选择不到正确的编程方式时才会有危害。

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

  • 向面向对象程序添加新操作可能需要编辑许多类定义才能添加新方法
  • 在函数程序中添加一种新的东西可能需要编辑许多函数定义来添加一个新的案例。


函数式编程是一种描述计算逻辑的声明式编程形式,完全不强调执行顺序。

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

  • 伸长脖子
  • 腿长
  • 应用点
  • 给这个生物一个黑舌头
  • 拆下马尾

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

命令式编程是程序性的。状态和秩序很重要。

问题:我想停车。

  • 注意车库门的初始状态
  • 在车道上停车
  • 如果车库门关闭,打开车库门,记住新状态;否则继续
  • 把车开进车库
  • 关闭车库门
  • 每一步都必须完成才能达到预期的结果。在车库门关闭时拉入车库会导致车库门断裂。


    大多数现代语言都有不同的命令性和功能性,但是为了更好地理解函数式编程,最好使用像Haskell这样的纯函数语言的例子,而不是像Java/C语言那样使用功能性语言。我相信用例子来解释总是很容易的,所以下面是一个例子。

    函数编程:计算n的阶乘,即n!即N x(n-1)x(n-2)x…x 2 x 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -- | 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允许函数重载到参数值的级别。下面是命令式代码在增加命令性程度方面的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //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 in for loop)、执行顺序、流控制。

    后面的例子可以被看作是Java/C.Lang-Land代码粗略地和第一部分,作为语言本身的限制,与Haskell相比,通过函数(0)来重载函数,因此可以说它不是纯函数语言,另一方面可以说它支持函数PROG。在某种程度上。

    披露:上述代码都没有经过测试/执行,但希望能够很好地传达这一概念;同时,我也希望对任何此类更正发表意见:)


    函数编程是"用函数编程",函数具有一些预期的数学性质,包括引用透明性。从这些属性中,进一步的属性流动,特别是由可替换性实现的熟悉的推理步骤,这些步骤导致数学证明(即证明结果的可信度)。

    由此可知,函数程序只是一个表达式。

    通过注意命令式程序中表达式不再具有引用透明性的地方(因此,它不是用函数和值构建的,本身不能是函数的一部分),可以很容易地看到两种样式之间的对比。最明显的两个地方是:突变(如变量)其他副作用非本地控制流(例如例外情况)

    在此程序框架下,作为由函数和值组成的表达式,建立了一个完整的语言、概念、功能模式、组合器、各种类型系统和评估算法的实用范例。

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


    从2005年到2013年,Web开发一直采用命令式编程风格。好的。

    使用命令式编程,我们一步一步地写出代码,列出了应用程序应该做什么。好的。

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

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

    现在,这些快速描述可能没有什么意义,所以让我们通过一个类比来了解命令式编程和声明式编程之间的区别。好的。

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

    所以我们的老板给了我们一个指导列表,我们知道的一个食谱。好的。

    食谱会告诉我们如何做馅饼。一个配方是以命令式的方式编写的,比如:好的。

  • 混合1杯面粉
  • 加1个鸡蛋
  • 加1杯糖
  • 把混合物倒进锅里
  • 把平底锅放在烤箱里30分钟350华氏度。
  • 声明性配方将执行以下操作:好的。

    1杯面粉,1个鸡蛋,1杯糖-初始状态好的。

    规则好的。

  • 如果所有东西都混在一起,放在平底锅里。
  • 如果一切都不混,放在碗里。
  • 如果一切都在锅里,放在烤箱里。
  • 因此命令式方法的特点是逐步的方法。你从第一步开始,到第二步,依此类推。好的。

    你最终会得到一些最终产品。所以做这个馅饼,我们把这些原料混合,放进锅里,放进烤箱,你就得到了你的最终产品。好的。

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

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

    所以我们采取初始状态,一遍又一遍地通过这些规则,直到我们得到一个现成的大黄草莓派或其他什么。好的。

    所以在声明性方法中,我们必须知道如何正确地构造这些规则。好的。

    所以我们可能想检查一下我们的配料或状态的规则,如果是混合的,把它们放进锅里。好的。

    我们的初始状态不匹配,因为我们还没有混合原料。好的。

    所以规则2说,如果他们不混合,然后在碗里混合。好吧,是的,这条规则适用。好的。

    现在我们有了一碗混合配料作为我们的州。好的。

    现在,我们再次将新的状态应用到我们的规则中。好的。

    所以规则1说,如果原料是混合的,把它们放在一个平底锅里,好的,现在规则1适用了,我们开始吧。好的。

    现在我们有了一种新的状态,配料混合在锅里。规则1不再相关,规则2不适用。好的。

    规则3说,如果原料在锅里,把它们放在烤箱里,很好,这条规则适用于这个新的状态,让我们来做。好的。

    最后我们吃了一个美味的热苹果派或其他什么。好的。

    现在,如果你和我一样,你可能在想,为什么我们不继续做命令式编程呢?这是有道理的。好的。

    嗯,对于简单的流是的,但是大多数Web应用程序都有更复杂的流,这些流不能被强制编程设计正确捕获。好的。

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

    可能文本输入以空字符串开始。好的。

    我们获取这个初始状态,并将其应用于应用程序中定义的一组规则。好的。

  • 如果用户输入文本,请更新文本输入。好吧,现在不适用。好的。

  • 如果呈现模板,则计算小部件。好的。

  • 如果更新了textinput,请重新呈现模板。
  • 好吧,这些都不适用,所以程序只会等待事件发生。好的。

    所以在某个时刻,用户更新文本输入,然后我们可以应用规则1。好的。

    我们可以把它更新到"abcd"。好的。

    所以我们只是更新了文本和文本输入更新,规则2不适用,规则3说如果文本输入是更新的,这只是刚刚发生的,然后重新呈现模板,然后我们回到规则2说如果模板被呈现,计算小部件,好的,让我们计算小部件。好的。

    一般来说,作为程序员,我们希望争取更具声明性的编程设计。好的。

    命令看起来更清晰和明显,但是声明性方法对于更大的应用程序来说非常适合扩展。好的。好啊。


    我认为可以用命令式的方式表达函数式编程:-使用大量的对象状态检查和if... else/switch语句-一些超时/等待机制来处理异步性

    这种方法存在巨大的问题:-重复规则/程序-庄重会带来副作用/错误

    函数编程,处理像对象这样的函数/方法,并接受无状态性,是为了解决这些问题而诞生的。

    使用示例:前端应用程序,如Android、iOS或Web应用程序的逻辑,包括与后端的通信


    我知道这个问题比较老,其他人已经很好地解释了它,我想给出一个简单解释相同问题的例子。

    问题:写1的表。

    解决方案:

    按命令样式:=>

    1
    2
    3
    4
    5
    6
    7
        1*1=1
        1*2=2
        1*3=3
        .
        .
        .
        1*n=n

    按功能样式:=>

    1
    2
    3
    4
    5
    6
    7
        1
        2
        3
        .
        .
        .
        n

    命令式的解释我们更明确地写指令,可以用更简单的方式来调用。

    在功能风格中,那些自我解释的东西将被忽略。