函数式编程语言和命令式编程语言有何区别?

202
大多数主流编程语言,包括面向对象编程(OOP)语言,如C#、Visual Basic、C++和Java,旨在主要支持命令式(过程式)编程,而像Haskell/Gofer这样的语言则是纯函数式的。有人可以详细说明一下这两种编程方式的区别吗?
我知道选择编程方式取决于用户需求,但为什么建议学习函数式编程语言?

1
可能是函数式编程与面向对象编程的比较的重复问题。 - Floris Velleman
1
请查看这篇其他的帖子。它清楚地描述了这些差异。 - theta
9个回答

266

以下是区别:

命令式:

  • 开始
  • 穿上鞋子,尺码为9 1/2。
  • 留出口袋空间,以便放置一个键为7的数组。
  • 将钥匙放在口袋里的钥匙的房间里。
  • 进入车库。
  • 打开车库门。
  • 进入汽车。

... 等等 ...

  • 把牛奶放进冰箱里。
  • 停止。

声明式, 其中函数式是其子类别:

  • 牛奶是一种健康的饮料,除非你有消化乳糖的问题。
  • 通常情况下,人们把牛奶放在冰箱里存储。
  • 冰箱是一个可以使物品保持凉爽的盒子。
  • 商店是销售商品的地方。
  • 通过 “销售”,我们指的是用货币交换商品。
  • 此外,用货币购买商品也称为“购买”。

... 等等 ...

  • 确保在需要时我们的冰箱里有牛奶(对于懒惰的函数式语言)。

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


7
我喜欢你的解释 @Igno,但仍有一些不清楚。在声明式编程中,即使你只是“告诉”事物,但仍然需要改变位并更改机器状态以正确进行。这让我感到困惑,因为某种程度上,声明式类似于过程式编程(如C函数),但它们在内部仍有很大的区别。在机器级别上,“C函数”和“函数式编程中的函数”是否相同? - phoenisx
1
@Subroto 我不同意在声明式编程中需要“更改位”的观点。一个名副其实的声明式语言必须保护其用户免于这样做。当然,这种语言在现代硬件上的实现最终会更改位。-- 对于您的第二个问题:所有函数式函数也可以编写为C函数,而没有可观察的副作用。但并非所有C函数都是函数式的,它们往往是过程(即执行此副作用,然后执行该副作用,并且如果设置了此位,则导致另一个副作用)。 - Ingo
17
@Igno,就像Subroto一样,我并不真正理解你的解释。看起来你写的可以概括为:“需要答案...得到答案。”但似乎忽略了重要的部分,即如何做到这一点。我不明白你是如何能够将该部分隐藏起来,因为在某个时候必须有人知道它是如何完成的...你不能永远把巫师藏在幕后。 - Brett Thomas
@igno 从你的解释中,我并不真正理解函数和抽象之间的区别,因为通常情况下,你已经将事物从用户那里抽象出来,使他们更多地沿着你第二个例子的思路思考。 - Brett Thomas
5
这与我对函数式编程的理解完全不同。我认为函数式编程是消除函数中隐藏的输入和输出。 - Ringo
14
复杂的解释。 - JoeTidee

189

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

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

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

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

函数式编程: 函数式编程范式是专门为支持纯函数式方法而创建的。函数式编程是一种声明式编程形式。

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

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

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

针对面向对象编程(OOP)或命令式语言:

当你需要对事物进行固定的一组操作并且你的代码在演变中主要是添加新的事物时,面向对象的语言非常适用。这可以通过添加实现现有方法的新类来实现,而现有的类则保持不变。

当你需要对一组固定的事物进行操作并且你的代码在演变中主要是添加新的操作时,函数式语言非常适用。这可以通过添加用于现有数据类型计算的新函数来实现,而现有的函数则保持不变。

缺点:

选择编程方式取决于用户需求,所以只有当用户没有选择适当的方式时才会存在问题。

当演变方向错误时,你会遇到问题:

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

13
在这种情况下,纯函数相当于数学函数。相同的输入始终映射到相同的输出。它们还缺乏任何副作用(除了返回一个或多个值),这意味着编译器可以进行一些很酷的优化,并且使得在并行运行函数更容易,因为没有需要竞争的内容。 - WorBlux
因此,编写可维护和可测试的面向对象应用程序的正确方法和最佳实践往往是以声明性思维设计命令式代码? - Kemâl Gültekin
5
我看不出在突出每种编程语言特性的文本中有明显的区别。大部分关于过程式编程的描述可以与命令式编程的文本交换,反之亦然。 - AxeEffect
12
这篇回答试图澄清什么是函数式编程,但没有定义纯函数。我不认为任何人可以阅读这篇回答并自信地知道声明式和过程式编程之间的区别。 - Ringo

21

大多数现代编程语言在不同程度上都是命令式和函数式的,但为了更好地理解函数式编程,最好以纯函数式语言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;
}

这篇文章可以作为一个很好的参考,以了解命令式代码更加关注“如何”部分、机器状态(例如for循环中的i)、执行顺序和流程控制。

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

声明:以上代码均未经测试/执行,但希望足以传达概念。同时,欢迎任何纠错评论 :)


2
应该是 return n * factorial(n-1); 吧? - jinawee
@jinawee,感谢您指出,我已经从n * (n-1)进行了更正。 - old-monk
这更像是递归和非递归方法的比较。就个人而言,我不认为函数式程序可以被视为“声明式”。就像在命令式程序中一样,我们必须指定程序执行的步骤。只是看起来像Haskell这样的语言有许多整洁的一行代码,可以缩短程序,仅此而已。 - mnj

15

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

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

  • 拉长颈部
  • 拉长腿部
  • 斑点涂上
  • 给生物黑色舌头
  • 去掉马尾巴

每个步骤可以以任意顺序运行以产生相同的结果。

命令式编程 是一种过程化的编程方式。状态和顺序很重要。

问题:我想停车。

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

每个步骤必须按顺序完成以达到所需的结果。在车库门关闭时将车开进车库会导致破损的车库门。


2
我只看到异步和同步之间的区别。 - Vladimir Vukanac
@VladimirVukanac 异步/同步是一种机制,而不是一种编程形式。 - Jakub Keller
3
谢谢,我会进一步研究这个问题。您是否可以把问题1更新为与问题2相同的“我想停车”但以函数式编程的方式书写?这样并行性就会被排除在外。 - Vladimir Vukanac
1
什么?在函数式语言中,操作的顺序同样很重要。(x - 1) * 3(x * 3) - 1不同。另一个例子是,fillBasket(emptyBasket(b), items)emptyBasket(fillBasket(b, items))有不同的效果。 - Mulan

5
//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)

一段命令式程序 = 一系列改变现有数据的语句。

关注于“什么” = 我们可变的数据(可修改的值,也称为变量)。

要链接命令式语句 = 使用过程(和/或面向对象编程)。


//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; //only depends on inputs; only impact on caller: via "return" value
} 

//2. usage = call functions to get new data
const int c = add(a,b); //c can only be assigned (=) once (const)

一个函数式的程序 = 一系列函数列表,“解释”如何获得新数据。
关注“如何”=我们的函数add
要链式使用函数“语句”=使用函数组合。

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

严肃的软件有很多数据和很多代码。

因此,同样的数据(变量)在代码的多个部分中被使用。

A. 在命令式程序中,这种(共享)数据的可变性会引起问题

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

作为优点:数据真正地在原地修改,需要复制的需求较少。(一些性能收益)

B. 另一方面,函数式代码使用不可变数据,没有这样的问题。数据是只读的,因此没有竞争条件。代码可以轻松并行化。结果可以被缓存。更容易理解。

作为缺点:数据经常被复制以获得“修改”。

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

更新:在命令式代码中,状态突变可能随时发生。因此,您的逻辑与突变状态交织在一起,这使得该状态实际上影响了您的...逻辑,因此代码变得非常难以理解。远程、非本地状态可能会以意想不到的方式影响函数。

在函数式方法中,只有局部状态才重要,只在函数内部。在外部,您可以安全地重用函数,知道它将始终以相同的方式运行,无需外部上下文即可确定其功能。组合这样的函数成为一个非常强大的工具,只要函数是纯的,就可以获得复杂但可靠的代码。


3
从2005年到2013年,命令式编程风格在Web开发中得到了实践。
使用命令式编程,我们编写的代码列出了应用程序应该如何一步一步地执行。
函数式编程通过巧妙地组合函数来产生抽象。
有关答案中提到了声明式编程,并且关于这点我会说,声明式编程列出了我们要遵循的一些规则。然后,我们为应用程序提供所谓的初始状态,让这些规则定义应用程序的行为方式。
现在,这些简短的描述可能没有多少意义,因此让我们通过比喻来解释命令式和声明式编程之间的区别。
想象一下,我们不是在构建软件,而是靠制作馅饼谋生。也许我们是糟糕的面包师,不知道应该如何做出美味可口的馅饼。
所以我们的老板给我们提供了一份指南,即食谱。
食谱将告诉我们如何制作馅饼。其中一份食谱以命令式风格编写,如下所示:
  1. 混合1杯面粉
  2. 加入1个鸡蛋
  3. 加入1杯糖
  4. 将混合物倒入平底锅中
  5. 将平底锅放入350华氏度的烤箱中,烤30分钟。
声明式食谱会执行以下操作:
1杯面粉、1个鸡蛋、1杯糖-初始状态
规则
  1. 如果所有东西都混合在一起,请放入平底锅中。
  2. 如果所有东西未混合在一起,请放入碗中。
  3. 如果所有东西都在平底锅中,请放入烤箱中。
因此,命令式方法的特点是逐步进行。您从第一步开始,然后转到第二步,依此类推。
最终,您会得到一些最终产品。制作这个馅饼,我们将这些成分混合在一起,放入平底锅和烤箱,然后你就有了最终产品。
在声明式世界中,情况有所不同。在声明式食谱中,我们将我们的食谱分为两个部分,首先列出食谱的初始状态,如变量。因此,我们的变量是成分的数量和类型。
我们取初始状态或初始成分并对它们应用一些规则。
因此,我们将初始状态传递给这些规则,一遍又一遍地进行,直到我们得到一个可以食用的大黄草莓馅饼或其他什么。
因此,在声明式方法中,我们必须知道如何正确地构建这些规则。
所以我们可能想要检查我们的成分或状态的规则,如果混合了,则将它们放入平底锅中。
对于我们的初始状态,这不匹配,因为我们尚未混合成分。
因此,规则2说,如果它们没有混合在一起,则在碗中混合它们。好的,是的,这个规则适用。
现在,我们有一个混合成分的碗作为我们的状态。
现在我们再次将这个新状态应用于我们的规则。
因此,规则1表示如果混合了成分,请将其放入平底锅中,好的,现在规则1适用了,让我们这样做。
现在我们有了这个新状态,其中成分已经混合并放在平底锅中。规则1不再相关,规则2不适用。
规则3表示如果成分在平底锅中,请将它们放入烤箱中,很好,这条规则适用于这个新状态,让我们这样做。
然后我们就会得到一个美味的热苹果派或其他食品。
现在,如果您像我一样,可能会想,为什么我们不仍然使用命令式编程。这很有道理。
对于简单的流程是可以的,但大多数Web应用程序具有无法通过命令式编程设计正确捕获的更复杂的流程。
在声明性方法中,我们可能有一些初始成分或初始状态,例如textInput =“”,一个单一的变量。
也许文本输入开始为空字符串。
我们将这个初始状态应用于您的应用程序中定义的一组规则。
  1. 如果用户输入文本,请更新文本输入。目前这不适用。
  2. 如果模板被渲染,请计算小部件。
  3. 如果textInput已更新,请重新渲染模板。
好的,这些都不适用,因此程序将等待事件发生。
所以在某个时刻,用户更新了文本输入,然后我们可能应用规则1。
我们可以将其更新为“abcd” 因此,我们刚刚更新了我们的文本和textInput更新,规则2不适用,规则3表示如果textInput已更新(刚刚发生),那么重新呈现模板,然后我们回到规则2,这表明如果模板被呈现,则计算小部件,好的让我们计算小部件。
总的来说,作为程序员,我们希望努力实现更多的声明性编程设计。
命令式似乎更清晰明了,但是声明性方法对于更大的应用程序扩展得非常好。

1
我认为可以用命令式的方式表达函数式编程:
  • 使用大量对象状态检查和if...else/switch语句
  • 一些超时/等待机制来处理异步性
这种方法存在巨大问题:
  • 规则/过程被重复
  • 有状态性留下了副作用/错误的机会
我相信,函数式编程将函数/方法视为对象并拥抱无状态性,是为了解决这些问题而诞生的。
用法示例:前端应用程序,如Android、iOS或Web应用程序的逻辑,包括与后端通信。
在模拟命令式/过程式代码的函数式编程时,还面临其他挑战:
  • 竞态条件
  • 复杂的事件组合和序列。例如,用户尝试在银行应用程序中发送资金。步骤1)同时进行以下所有操作,仅在所有操作都成功的情况下继续执行:a)检查用户是否仍然可用(欺诈,AML)b)检查用户是否有足够的余额c)检查收件人是否有效且良好(欺诈,AML)等。步骤2)执行转账操作。步骤3)显示用户余额更新和/或某种跟踪。例如,使用RxJava,代码简洁而明智。如果没有它,我可以想象会有很多代码,代码混乱且容易出错。

我还相信,最终,功能代码将被编译器翻译成汇编或机器代码,这是命令式/过程式的。但是,除非您编写汇编语言,作为使用高级/人类可读语言编写代码的人,函数式编程是表达所列场景的更合适方式。


0

关于函数式程序和命令式程序有很多不同的观点。

我认为,函数式程序最容易描述为“惰性评估”导向。语言通过递归方法而非程序计数器迭代指令来设计实现。

在函数式语言中,函数的求值从返回语句开始回溯,直到最终得出一个值。这对于语言语法产生了深远的影响。

命令式:将计算机运送

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

函数式:运输食谱

函数式语言将发送食谱,在需要结果时,计算机将开始处理食谱。这样,您可以确保不浪费太多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 个闭包包装该值,这是没有意义的。
更明显的方法是实际上直接在原地更改值 - 但是,你引入了可修改的值(即可变值),使语言具有命令式特性 - 或者实际上是混合类型。
在幕后,这归结为两种不同的方法来处理输入并生成输出。
下面,我将尝试用以下项目制作一个城市的图示:
  1. 计算机
  2. 你的家
  3. 斐波那契数列

命令式语言

任务:计算第三个斐波那契数。 步骤:
计算机 放入一个盒子中,并用一个 便利贴 标记它:
字段 值 邮寄地址 The Fibonaccis 返回地址 你的家 参数 3 返回值 未定义
然后将计算机寄出。
收到盒子后,The Fibonaccis 将按照惯例执行以下操作:
参数是否小于2?
是:更改 便利贴,并将计算机退回到邮局:
字段 值 邮寄地址 The Fibonaccis 返回地址 你的家 参数 3 返回值 0 或 1(返回参数)
然后退回给寄件人。
否则:
在旧的 便利贴 上面贴上一个新的 便利贴
字段 值 邮寄地址 The Fibonaccis 返回地址 否则,步骤2,c/o The Fibonaccis 参数 2(传递参数-1) 返回值 未定义
然后将其发送。
取下返回的 便利贴。在最初的 便利贴 上面贴上一个新的 便利贴 并再次发送计算机:
字段 值 邮寄地址 The Fibonaccis 返回地址 否则,完成,c/o The Fibonaccis 参数 2(传递参数-2) 返回值 未定义
到现在为止,我们应该有来自请求者的初始 便利贴 和两个已使用的 便利贴,每个都填写了它们的返回值字段。我们总结返回值并将其放入最终 便利贴 的返回值字段中。
字段 值 邮寄地址 The Fibonaccis 返回地址 你的家 参数 3 返回值 2(returnValue1 + returnValue2)
然后退回给寄件人。

正如你所想象的那样,在你将计算机发送到调用函数后,立即开始了相当多的工作。

整个编程逻辑是递归的,但实际上,随着计算机从算法到算法的移动,算法是按顺序进行的,借助于一堆“便笺”栈。

函数式语言

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

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

    字段
    指令 The Fibonaccis
    参数 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);

HALF的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/


-1

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

问题:写出1的乘法表。

解决方案:-

通过命令式风格:=>

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

通过函数式编程风格: =>

    1
    2
    3
    .
    .
    .
    n

在命令式风格中,我们会更明确地编写指示,以简化指令。

而在函数式风格中,那些已经不言自明的事情将被忽略。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接