【问题标题】:What is difference between functional and imperative programming languages?函数式编程语言和命令式编程语言有什么区别?
【发布时间】:2013-07-23 11:29:06
【问题描述】:

大多数主流语言,包括 C#、Visual Basic、C++ 和 Java 等面向对象编程 (OOP) 语言,旨在主要支持命令式(过程式)编程,而类似 Haskell/gofer 的语言则纯粹是函数式的。谁能详细说明这两种编程方式有什么区别?

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

【问题讨论】:

标签: oop functional-programming


【解决方案1】:

这里有区别:

命令式:

  • 开始
  • 穿上 9 1/2 码的鞋子。
  • 在你的口袋里腾出空间来存放一系列[7]的钥匙。
  • 把钥匙放在房间里,把钥匙放在口袋里。
  • 进入车库。
  • 打开车库。
  • 上车。

...等等等等...

  • 将牛奶放入冰箱。
  • 停下来。

声明性,其中函数是一个子类别:

  • 牛奶是一种健康饮品,除非您无法消化乳糖。
  • 通常,人们将牛奶储存在冰箱中。
  • 冰箱是一个让里面的东西保持凉爽的盒子。
  • 商店是出售商品的地方。
  • 我们所说的“销售”是指用东西换钱。
  • 另外,用钱换东西也叫“买”。

...等等等等...

  • 确保冰箱中有牛奶(当我们需要时 - 用于惰性函数式语言)。

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

【讨论】:

  • 我喜欢你的解释@Igno,但我仍然不清楚。在声明式中,即使您只是告诉 东西,但您仍然需要更改位并更改机器中的状态才能正确进行。这让我感到困惑,声明式在某种程度上类似于过程式编程(如 C 函数),但它们在内部仍然存在很大差异。 C 函数函数式编程中的函数 (机器级别)不一样吗?
  • @Subroto 我不同意在声明式编程中需要“更改位”。一种名副其实的声明性语言必须保护他们的用户免于这样做。可以肯定的是,这种语言在当代硬件上的实现最终会改变一些细节。 - 对于您的第二个问题:所有功能函数也可以编写为 C 函数,而不会产生明显的副作用。但并非所有 C 函数都是函数式的,它们往往是过程(即,先做这个副作用,然后做那个副作用,如果设置了这个位,会引起另一个副作用)。
  • @Igno,和 Subroto 一样,我不太明白你的解释。您所写的内容似乎可以概括为:需要答案...获得答案。它似乎忽略了重要的部分。我不明白你怎么能对用户隐藏那部分,在某些时候必须有人知道它是如何完成的……你不能永远把向导隐藏在幕后。
  • 这不是我所理解的函数式编程。我认为函数式编程是从函数中删除隐藏的输入和输出。
  • 复杂的解释。
【解决方案2】:

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

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

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

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

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

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

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

更轻松的测试和调试。因为纯函数可以更容易地单独测试,所以您可以编写测试代码来调用具有典型值、有效边缘情况和无效边缘情况的纯函数。

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

当您对事物有一组固定的操作并且随着代码的发展而主要添加新事物时,面向对象的语言是很好的。这可以通过添加实现现有方法的新类来完成,而现有类则不作任何处理。

当您拥有一组固定的事物并且随着代码的发展,您主要在现有事物上添加新操作时,函数式语言是很好的。这可以通过添加使用现有数据类型进行计算的新函数来完成,而现有函数则不受影响。

缺点:

编程方式的选择取决于用户的需求,所以只有用户没有选择正确的方式才有危害。

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

  • 向面向对象程序添加新操作可能需要编辑许多类定义以添加新方法
  • 向函数式程序添加新事物可能需要编辑许多函数定义以添加新案例。

【讨论】:

  • 在这种情况下,纯函数等效于数学函数。相同的输入将始终映射到相同的输出。它们也没有任何副作用(除了返回一个或多个值),这意味着编译器可以进行一些很酷的优化,并且可以更轻松地并行运行函数,因为没有什么可抗衡的。
  • 那么,编写可维护和可测试的 oop 应用程序的正确方法和最佳实践往往是以 declerative 的心态设计命令式代码?
  • 在突出显示每个编程功能的文本上,我没有看到明显的区别。程序化编程的大部分描述都可以通过命令式编程文本进行交换,反之亦然。
  • 这个答案试图澄清什么是函数式编程,但甚至没有费心去定义什么是纯函数。我看不出有人怎么能读到这个答案,并且对了解声明式编程和过程式编程之间的区别充满信心。
【解决方案3】:

大多数现代语言在不同程度上兼具命令式和函数式,但为了更好地理解函数式编程,最好举一个像 Haskell 这样的纯函数式语言的例子,而不是像 java/C# 这样的非函数式语言的命令式代码。我相信通过例子总是很容易解释,所以下面是一个。

函数式编程:计算 n 的阶乘,即 n!即 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;
}

这个read 可以作为一个很好的参考,以了解命令式代码如何更多地关注部分、机器状态(i 在 for 循环中)、执行顺序、流控制。

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

披露: 上述代码均未经过测试/执行,但希望足以传达概念;我也将感谢 cmets 进行任何此类更正:)

【讨论】:

  • 不应该是return n * factorial(n-1);吗?
  • @jinawee,谢谢指出,我已经从n * (n-1)更正了
  • 它更像是递归和非递归方法的比较。就个人而言,我不明白如何将函数式程序视为“声明性”。就像在命令式程序中一样,我们必须指定程序执行的步骤。看起来像 Haskell 这样的语言有许多简洁的单行代码,可以缩短程序,仅此而已。
【解决方案4】:

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

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

  • 加长脖子
  • 拉长腿
  • 应用斑点
  • 给这个生物一个黑色的舌头
  • 去掉马尾

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

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

问题:我想停车。

  1. 注意车库门的初始状态
  2. 把车停在车道上
  3. 如果车库门关闭,打开车库门,记住新状态;否则继续
  4. 把车开进车库
  5. 关闭车库门

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

【讨论】:

  • 我只看到异步与同步的区别。
  • @VladimirVukanac async/sync 是一种机制,而不是一种编程形式
  • 哦,谢谢,我会研究更多。你会这么好心,将问题 1 更新为与问题 2“我想停车”相同但以函数式编程方式编写的吗?然后将排除并行性。
  • 什么?函数式语言中的操作顺序同样重要。 (x - 1) * 3(x * 3) - 1 不同。再举一个例子,fillBasket(emptyBasket(b), items)emptyBasket(fillBasket(b, items)) 相比具有不同的效果。
【解决方案5】:

函数式编程是“用函数编程”,其中函数具有一些预期的数学属性,包括引用透明性。从这些属性中,可以得出更多属性,特别是通过可替代性实现的熟悉推理步骤,从而导致数学证明(即证明对结果的置信度)。

因此,函数式程序只是一个表达式。

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

在这个由函数和值组成的程序即表达式框架上,构建了语言、概念、“函数模式”、组合子以及各种类型系统和评估算法的完整实用范式。

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

【讨论】:

    【解决方案6】:

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

    通过命令式编程,我们编写的代码逐步准确地列出了我们的应用程序应该做什么。

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

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

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

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

    所以我们的老板给了我们一份方向清单,我们称之为食谱。

    食谱会告诉我们如何做馅饼。一个食谱是用命令式编写的,如下所示:

    1. 混合 1 杯面粉
    2. 加1个鸡蛋
    3. 加 1 杯糖
    4. 将混合物倒入锅中
    5. 将平底锅放入烤箱 30 分钟和 350 华氏度。

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

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

    规则

    1. 如果所有东西都混合了,则放入平底锅中。
    2. 如果所有东西都没有混合,则放入碗中。
    3. 如果所有东西都在锅中,则放入烤箱。

    因此,命令式方法的特点是逐步方法。您从第一步开始,然后转到第二步,依此类推。

    您最终会得到一些最终产品。因此,制作这个馅饼时,我们将这些成分混合在一起,放入平底锅和烤箱中,然后您就得到了最终产品。

    在声明式世界中,情况有所不同。在声明式配方中,我们会将配方分成两个独立的部分,从列出配方初始状态(如变量)的一部分开始。所以我们这里的变量是我们的成分的数量和它们的类型。

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

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

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

    所以我们可能要检查我们的成分或状态的规则,如果混合,请将它们放入平底锅中。

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

    所以规则 2 说,如果它们不混合,则将它们混合在一个碗中。好的,是的,这条规则适用。

    现在我们有一碗混合配料作为我们的状态。

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

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

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

    第 3 条规定,如果原料在平底锅中,则将它们放入烤箱中,这条规则适用于这种新状态,很好,让我们照做吧。

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

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

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

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

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

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

    1. 如果用户输入文本,则更新文本输入。好吧,现在不适用。

    2. 如果模板被渲染,计算小部件。

    3. 如果 textInput 已更新,请重新渲染模板。

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

    所以在某个时候用户更新了文本输入,然后我们可能会应用规则号 1。

    我们可能会将其更新为“abcd”

    所以我们刚刚更新了 text 和 textInput 更新,第 2 条规则不适用,第 3 条规则表示文本输入是否更新,刚刚发生,然后重新渲染模板,然后我们回到规则 2,即如果模板渲染完毕,计算widget,好吧,让我们计算widget。

    总的来说,作为程序员,我们希望争取更多的声明式编程设计。

    命令式似乎更加清晰和明显,但声明式方法非常适合大型应用程序。

    【讨论】:

      【解决方案7】:

      • 命令式语言:

      • 高效执行

      • 复杂的语义

      • 复杂的语法

      • 并发是程序员设计的

      • 复杂的测试,没有参照透明,有副作用

      • 有状态

      • 函数式语言:

      • 简单语义

      • 简单语法

      • 执行效率较低

      • 程序可以自动并发

      • 测试简单,参照透明,无副作用

      • 没有状态

      【讨论】:

        【解决方案8】:

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

        • 使用大量的对象状态检查和if... else/switch语句
        • 一些超时/等待机制来处理异步性

        这种方法存在很大的问题:

        • 重复规则/程序
        • 有状态会留下副作用/错误的机会

        函数式编程,将函数/方法视为对象并拥抱无状态,是为了解决我认为的这些问题而诞生的。

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

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

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

        我还相信,功能代码最终会被编译器转换为命令式/程序化的汇编或机器代码。但是,除非您编写汇编,否则作为人类使用高级/人类可读语言编写代码的人,函数式编程是列出的场景更合适的表达方式

        【讨论】:

          【解决方案9】:

          关于什么是函数式程序和什么是命令式程序似乎有很多意见。

          我认为函数式程序最容易被描述为面向“惰性求值”。该语言设计采用递归方法,而不是让程序计数器遍历指令。

          在函数式语言中,函数的求值将从 return 语句 开始并回溯,直到最终达到一个值。这对语言语法有深远的影响。

          当务之急:随身携带计算机

          下面,我尝试使用邮局的类比来说明这一点。命令式语言会将计算机发送到不同的算法,然后让计算机返回结果。

          功能:到处运送食谱

          函数式语言将发送食谱,当您需要结果时,计算机将开始处理食谱。

          这样,您可以确保不会将过多的 CPU 周期浪费在从未用于计算结果的工作上。

          当您在函数式语言中调用函数时,返回值是由配方构成的配方,而配方又由配方构成。这些配方实际上就是所谓的闭包

          // helper function, to illustrate the point
          function unwrap(val) {
            while (typeof val === "function") val = val();
            return val;
          }
          
          function inc(val) {
            return function() { unwrap(val) + 1 };
          }
          
          function dec(val) {
            return function() { unwrap(val) - 1 };
          }
          
          function add(val1, val2) {
            return function() { unwrap(val1) + unwrap(val2) }
          }
          
          // lets "calculate" something
          
          let thirteen = inc(inc(inc(10)))
          let twentyFive = dec(add(thirteen, thirteen))
          
          // MAGIC! The computer still has not calculated anything.
          // 'thirteen' is simply a recipe that will provide us with the value 13
          
          // lets compose a new function
          
          let doubler = function(val) {
            return add(val, val);
          }
          
          // more modern syntax, but it's the same:
          let alternativeDoubler = (val) => add(val, val)
          
          // another function
          let doublerMinusOne = (val) => dec(add(val, val));
          
          // Will this be calculating anything?
          
          let twentyFive = doubler(thirteen)
          
          // no, nothing has been calculated. If we need the value, we have to unwrap it:
          console.log(unwrap(thirteen)); // 26
          

          unwrap 函数将对所有函数求值,使其具有标量值。

          语言设计后果

          命令式语言中的一些不错的功能在函数式语言中是不可能的。例如value++ 表达式,它在函数式语言中很难计算。函数式语言对语法必须如何进行限制,因为它们的评估方式。

          另一方面,命令式语言可以借鉴函数式语言的好主意并成为混合体。

          函数式语言很难使用 一元运算符(例如 ++)来增加值。造成这种困难的原因并不明显,除非您了解函数式语言是“反向”评估的

          实现一元运算符必须像这样实现:

          let value = 10;
          
          function increment_operator(value) {
            return function() {
              unwrap(value) + 1;
            }
          }
          
          value++ // would "under the hood" become value = increment_operator(value)
          
          

          注意我上面使用的unwrap函数,是因为javascript不是函数式语言,所以在需要的时候我们必须手动解包。

          现在很明显,应用增量一千次会导致我们用 10000 个闭包来包装这个值,这是毫无价值的。

          更明显的方法是直接就地更改值 - 但是瞧:您已经引入了可修改值 aka 可变值这使得语言势在必行 - 或者实际上是一种混合语言。

          在后台,当提供输入时,它归结为两种不同的方法来产生输出。

          下面,我将尝试用以下项目来描绘一个城市:

          1. 计算机
          2. 你的家
          3. 斐波那契

          命令式语言

          任务:计算第三个斐波那契数。 步骤:

          1. 计算机放入一个盒子并用便签标记:

            Field Value
            Mail Address The Fibonaccis
            Return Address Your Home
            Parameters 3
            Return Value undefined

            然后发送计算机

          2. 斐波那契将在收到盒子后照常做:

            • 参数是否

              • 是:更换便签,然后将电脑送回邮局:

                Field Value
                Mail Address The Fibonaccis
                Return Address Your Home
                Parameters 3
                Return Value 0 or 1 (returning the parameter)

                并返回给发件人。

              • 否则:

                1. 在旧的上面放一个新的便签

                  Field Value
                  Mail Address The Fibonaccis
                  Return Address Otherwise, step 2, c/oThe Fibonaccis
                  Parameters 2 (passing parameter-1)
                  Return Value undefined

                  然后发送。

                2. 取下退回的便签。将一个新的便笺放在最初的上面,然后再次发送计算机:

                  Field Value
                  Mail Address The Fibonaccis
                  Return Address Otherwise, done, c/o The Fibonaccis
                  Parameters 2 (passing parameter-2)
                  Return Value undefined
                3. 现在,我们应该有来自请求者的初始便笺,以及两个使用过的便笺,每个都填充了它们的返回值字段。我们将返回值汇总并放在最后的便笺的返回值字段中。

                  Field Value
                  Mail Address The Fibonaccis
                  Return Address Your Home
                  Parameters 3
                  Return Value 2 (returnValue1 + returnValue2)

                  并返回给发件人。

          您可以想象,在您将计算机发送到您调用的函数之后,会立即开始大量工作。

          整个编程逻辑是递归的,但实际上,当计算机在一堆便笺的帮助下从一个算法移动到另一个算法时,算法是按顺序发生的。

          函数式语言

          任务:计算第三个斐波那契数。步骤:

          1. 在便签上写下以下内容:

            Field Value
            Instructions The Fibonaccis
            Parameters 3

          基本上就是这样。那个便签现在代表fib(3)的计算结果。

          我们已将参数 3 附加到名为 The Fibonaccis 的配方中。计算机不必执行任何计算,除非有人需要标量值。

          函数式 Javascript 示例

          我一直致力于设计一种名为 Charm 的编程语言,这就是斐波那契在该语言中的外观。

          fib: (n) => if (                         
            n < 2               // test
            n                   // when true
            fib(n-1) + fib(n-2) // when false
          )
          print(fib(4));
          

          此代码可以编译成命令式和函数式“字节码”。

          命令式 javascript 版本是:

          let fib = (n) => 
            n < 2 ?
            n : 
            fib(n-1) + fib(n-2);
          

          半功能 javascript 版本将是:

          let fib = (n) => () =>
            n < 2 ?
            n :
            fib(n-1) + fib(n-2);
          

          纯函数式 javascript 版本会涉及更多,因为 javascript 没有等效的函数。

          let unwrap = ($) =>
            typeof $ !== "function" ? $ : unwrap($());
          
          let $if = ($test, $whenTrue, $whenFalse) => () =>
            unwrap($test) ? $whenTrue : $whenFalse;
          
          let $lessThen = (a, b) => () =>
            unwrap(a) < unwrap(b);
          
          let $add = ($value, $amount) => () =>
            unwrap($value) + unwrap($amount);
          
          let $sub = ($value, $amount) => () =>
            unwrap($value) - unwrap($amount);
          
          let $fib = ($n) => () =>
            $if(
              $lessThen($n, 2),
              $n,
              $add( $fib( $sub($n, 1) ), $fib( $sub($n, 2) ) )
            );
          
          

          我会手动将其“编译”成 javascript 代码:

          "use strict";
          
          // Library of functions:
            /**
             * Function that resolves the output of a function.
             */
            let $$ = (val) => {
              while (typeof val === "function") {
                val = val();
              }
              return val;
            }
          
            /**
             * Functional if
             *
             * The $ suffix is a convention I use to show that it is "functional"
             * style, and I need to use $$() to "unwrap" the value when I need it.
             */
            let if$ = (test, whenTrue, otherwise) => () =>
              $$(test) ? whenTrue : otherwise;
          
            /**
             * Functional lt (less then)
             */
            let lt$ = (leftSide, rightSide)   => () => 
              $$(leftSide) < $$(rightSide)
          
          
            /**
             * Functional add (+)
             */
            let add$ = (leftSide, rightSide) => () => 
              $$(leftSide) + $$(rightSide)
          
          // My hand compiled Charm script:
          
            /**
             * Functional fib compiled
             */
            let fib$ = (n) => if$(                 // fib: (n) => if(
              lt$(n, 2),                           //   n < 2
              () => n,                             //   n
              () => add$(fib$(n-2), fib$(n-1))     //   fib(n-1) + fib(n-2)
            )                                      // )
          
          // This takes a microsecond or so, because nothing is calculated
          console.log(fib$(30));
          
          // When you need the value, just unwrap it with $$( fib$(30) )
          console.log( $$( fib$(5) ))
          
          // The only problem that makes this not truly functional, is that
          console.log(fib$(5) === fib$(5)) // is false, while it should be true
          // but that should be solveable
          

          https://jsfiddle.net/819Lgwtz/42/

          【讨论】:

            【解决方案10】:
            //The IMPERATIVE way
            int a = ...
            int b = ...    
            
            int c = 0; //1. there is mutable data
            c = a+b;   //2. statements (our +, our =) are used to update existing data (variable c)
            

            命令式程序 = 更改现有数据的语句序列。

            关注 WHAT = 我们的变异数据(可修改的值,也就是变量)。

            链接命令式语句 = 使用过程(和/或 oop)。


            //The FUNCTIONAL way
            const int a = ... //data is always immutable
            const int b = ... //data is always immutable
            
            //1. declare pure functions; we use statements to create "new" data (the result of our +), but nothing is ever "changed"
            int add(x, y) 
            {
               return x+y;
            } 
            
            //2. usage = call functions to get new data
            const int c = add(a,b); //c can only be assigned (=) once (const)
            

            函数式程序 = “解释”如何获取新数据的函数列表。

            专注于 HOW = 我们的功能add

            链接功能“语句” = 使用功能组合。


            这些基本区别具有深远的影响。

            严肃的软件有大量的数据和大量的代码。

            所以在代码的多个部分使用相同的数据(变量)。

            A.在命令式程序中,此(共享)数据的可变性会导致问题

            • 代码难以理解/维护(因为数据可以在不同的位置/方式/时刻修改
            • 并行化代码很难(当时只有一个线程可以改变内存位置),这意味着必须序列化对相同变量的访问 = 开发人员必须编写额外的代码来强制执行对共享资源的这种序列化访问,通常通过锁 /信号量

            作为一个优点:数据真正修改到位,不需要复制。 (一些性能提升)

            B.另一方面,功能代码使用不存在此类问题的不可变数据。数据是只读的,因此没有竞争条件。代码可以很容易地并行化。结果可以被缓存。更容易理解。

            作为一个缺点:为了获得“修改”而大量复制数据。

            另请参阅:https://en.wikipedia.org/wiki/Referential_transparency

            【讨论】:

              【解决方案11】:

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

              问题:写 1 的表。

              解决方案:-

              按命令式:=>

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

              按功能样式:=>

                  1
                  2
                  3
                  .
                  .
                  .
                  n
              

              命令式解释我们更明确地编写指令,可以更简化地调用。

              在函数式风格中,不言自明的东西将被忽略。

              【讨论】:

                猜你喜欢
                • 2018-05-06
                • 1970-01-01
                • 2011-03-29
                • 2017-01-14
                • 1970-01-01
                • 2013-07-29
                • 2011-02-09
                • 2020-08-04
                • 2018-12-23
                相关资源
                最近更新 更多