在JavaScript中用匿名函数写出递归的方法

在JavaScript中用匿名函数(箭头函数)写出递归的方法

前言

今天看 Mozilla 出品的 ES6 In Depth ,看到 Arrow functions(中文翻译),其中一段让人讶异。

Using arrows to pierce the dark heart of computer science

「使用箭头来刺穿计算机的黑暗心脏」

里面提到λ (lambda)表达式、阿隆佐·邱奇(Alonzo Church)、阿兰·图灵(Alan Turing),这些耳熟能详的名词原来与我们写 JavaScript 的人这么近,这激发了我极大的探索兴趣。

最后搜索到刘未鹏2006年的一篇文章《康托尔、哥德尔、图灵——永恒的金色对角线(rev#2)》,奇妙的从 ES2015 延伸到了计算机的起源以及现代数学史的开端。

我看到了它,却不敢相信它。——康托尔

计算机是数学家一次失败思考的产物。——无名氏

原来我们轻易写下的每一个匿名函数,里面都蕴涵简单而又玄妙的数学原理。

原来用匿名函数实现的递归,动用了如此深刻的数学法则。

希望每个前端工程师都能认真阅读刘未鹏的文章,理解 Y Combinator 的 JavaScript 实现,对这门正在越变越好的语言抱以更多的敬畏之情,写起 ES2015 来或许有更好的编程体验。

注:本文部分代码将用 ES2015 编写,要跑起来可能得先用Babel编译为 ES5。

正文

我们用递归的方式实现阶乘函数,并且从朴素思路出发,最后一步步抵达Y Combinator

首先,用最简单的命名函数递归方式,如下:

第二种方式,用变量缓存匿名函数的值:

看,我们用匿名函数实现了递归,全剧终......

不,那只是 JS 引擎给我们的语法糖。实际上,所谓的「用 lambda 表达式写出递归」,不能在 lambda 定义完成之前直接引用自身。我们做如下假设:

在这个基础上,继续探索我们的话题。

如果 lambda 表达式不能直接在函数体内显示引用自身,那么我们就得隐式地调用自身;因为我们不是用循环来模拟递归,我们就是要让 lambda 表达式反复执行一段相同代码。

其中一个策略是,将 lambda 表达式作为参数之一传入其自身。(函数也是值)

OK,我们现在的确实现了具有递归效果的 lambda 表达式,但是,太难看了。没有人希望自己的阶乘函数有多余的参数,我们的目标是,fact(n)

为了达到参数纯净化目的,我们可以包裹一层工厂函数,封装肮脏的冗余传参行为。

虽然现在我们达到了在调用时参数纯净化的目标,但仍有些不美。定义 fact 时,我们还在 self(self, n - 1), 方式不够直观,我们期望能用下面的方式代替。

在函数被定义之后,我们才拿到其引用;也就是说,不可能在生产/创建一个函数时,把它自己传参进去。也就是说,对于上面的工厂函数 factory 而言,self === factory(self)永远不可能为真。不过,没关系。我们有软件工程里的黄金定律:

任何问题都可以通过增加一个间接层来解决。

既然无法让一个阶乘函数反复调用自身,那就让 factory 在需要时反复生产出虽然不是同一个,但效果等价的、新的阶乘函数。我们设想有以下特征的 Y 函数:

在知道Y函数的功能与行为后,我们再根据已知条件,把它构造出来。

首先,Y 函数一定返回阶乘函数,那么它的可能形式如下,

其次,Y 一定调用了 factory 函数两次以上

magic 函数从 factory 取出新的阶乘函数,作为参数又传入 factory,这样创建出来的阶乘函数,里面的 self 就是另一个阶乘函数。

到这里,我们只需要探究 magic 应该是什么代码形式。

可惜,上面复用 magic 函数,也只是语法糖,我们不能在 magic 定义完成前显式引用它。

诶?

那么就再增加中间层,隐式引用呗。说做就做。

惊!!,我们竟然成功了。虽然我们不知道 magic 魔术函数为什么是那样,但是,我们把它构造了出来。

同时,我们注意到,magic 的 factory 参数,好像没有存在的必要了,因为作用域内只存在唯一一个factory

神奇。magic 魔术函数果然很魔术,在外部 magic(magic) 自己调用自己, 在内部self(self),就实现了递归?

同时,我们又注意到一点,n => factory(magic(magic))(n)的形式跟n => factory(self(self))(n) 似乎一模一样,仅仅是 magic 跟 self 名字不同。

嗯?前者不就是把 magic 自身作为参数传递进自身的返回函数吗?

magic(magic) 是把自己传参进去,那么self === magic

原来 self(self) 自调用的函数,就是magic自身。

于是,我们得到:

看到最终的产物,让人惊呆了。这是什么黑魔法?

仔细一看,原来它就是 lambda 演算的 JavaScript 实现

它不仅适用于阶乘函数的递归处理,任意递归工厂函数经过Y函数后,都能得到真正的递归函数。

尾声

在这篇文章中,我们有意识地用到的特性只有一个:

函数也是值,可以作为参数传递

我们利用它,让一个函数自己调用自己,然后不断美化美化、简化简化,竟然就构造出了Y Combinator

然而:

  • 函数也是值,可传参中,反推出Y Combinator,不代表你有多厉害
  • 只是站在巨人的肩膀上
  • 背下函数也是值,可传参的定律,却不知道背后的原理就是λ演算
  • 就像还没学到微积分的高中生自己开创了微积分初步
  • 自比牛顿太幼稚,微积分原理与应用衍化成耳熟能详的说辞围绕着你
  • 没有这些弱启发,买菜还在数指头
  • 数学多美妙
<think>好的,我现在需要处理用户关于不同编程语言语句差异的查询。首先,用户提到参考了四个引用内容,其中涉及条件编译、C/C++、Ada等语言的对比,以及主流编程语言的特点。用户想知道不同编程语言中语句的详细差别对比,需要结构清晰,分点回答,并生成相关问题。 首先,我得确定用户需要哪些方面的语句对比。通常,编程语言的语句差异包括变量声明、条件语句、循环结构、函数定义、错误处理、输入输出等。引用中提到了C/C++的条件编译,Ada和C的编译特性,以及C语言适合写程序等,这可能涉及底层控制和语法简洁性。 接下来,我需要整理不同语言在这些方面的差异。例如,变量声明方面,静态类型语言如C、Java需要显式声明类型,而Python、JavaScript是动态类型。条件语句的语法差异,比如Python用冒号和缩进,而C家族用花括号。循环结构,如for循环在Python中是迭代,而在C中是传统的初始化-条件-步进。函数定义,Python用def,JavaScript用function等。错误处理方面,try-catch在Java和Python中的使用,而Go使用defer和panic。输入输出,C用printf,C++用cout,Python用print函数。 还要注意引用中提到的条件编译,这在C/C++中用#ifdef等预处理指令,而其他语言可能没有类似机制。Ada作为编译型语言,可能也有自己的特性,但用户引用中没有详细说明,可能需要简要提及。 生成相关问题时要围绕语句差异,可能涉及具体语法、使用场景、优缺点等。例如,不同语言的变量类型推断机制,或者条件编译的应用场景。 需要确保回答符合用户给的系统级指令:使用正确的LaTeX格式,中文回答,结构清晰,引用标注正确。同时,生成的相关问题要自然,符合用户可能进一步询问的方向。 现在开始组织内容,分点对比不同语句结构,引用对应的参考资料。例如,在条件编译部分引用[^1],在编译语言对比时引用[^2][^4]。确保每个对比点都有例子,如代码块,但根据用户示例,代码块需要用```包裹,并正确标注语言。 最后检查是否符合所有要求,LaTeX是否正确,引用是否恰当,问题是否相关。确保没有遗漏用户关注的语句差异点,比如可能还包括面向对象的特性,但引用中没有提到,可能需要简略处理或忽略。</think>不同编程语言中语句的差异主要体现在语法结构、执行逻辑和设计理念上。以下是主要对比: --- ### 一、变量声明与类型 1. **C/C++/Java** 需显式声明类型,例如: ```c int x = 10; // C/C++ String s = "hello"; // Java ``` 引用[^4]提到,C语言通过显式类型声明提供更底层的内存控制。 2. **Python/JavaScript** 动态类型,无需声明类型: ```python x = 10 # Python s = "hello" ``` ```javascript let x = 10; // JavaScript ``` 3. **Ada** 强类型且需显式声明,语法更严格: ```ada x : Integer := 10; ``` --- ### 二、条件语句 1. **C家族(C/C++/Java/C#)** 使用花括号`{}`包裹代码块: ```c if (x > 0) { printf("Positive"); } else if (x < 0) { printf("Negative"); } ``` 2. **Python** 依赖缩进和冒号`:`: ```python if x > 0: print("Positive") elif x < 0: print("Negative") ``` 3. **Ruby** 用`end`标记代码块结束: ```ruby if x > 0 puts "Positive" elsif x < 0 puts "Negative" end ``` --- ### 三、循环结构 1. **C风格for循环** 初始化-条件-步进三段式: ```c for (int i=0; i<10; i++) { printf("%d", i); } ``` 2. **Python迭代循环** 直接遍历可迭代对象: ```python for i in range(10): print(i) ``` 3. **函数式语言(如Haskell)** 使用递归或高阶函数(如`map`): ```haskell map (\x -> x * 2) [1..10] ``` --- ### 四、函数定义 1. **C/C++** 需声明返回类型和参数类型: ```c int add(int a, int b) { return a + b; } ``` 2. **Python** 使用`def`关键字,无返回类型声明: ```python def add(a, b): return a + b ``` 3. **JavaScript** 函数为一等公民,支持匿名函数: ```javascript const add = (a, b) => a + b; ``` --- ### 五、错误处理 1. **Java/Python** `try-catch`结构: ```java try { // 代码 } catch (Exception e) { // 处理异常 } ``` 2. **Go** 通过返回值显式处理错误: ```go result, err := someFunction() if err != nil { log.Fatal(err) } ``` 3. **Rust** 使用`Result`类型和`match`模式匹配: ```rust let result = File::open("file.txt"); match result { Ok(file) => {/* 操作文件 */}, Err(error) => {/* 处理错误 */} } ``` --- ### 六、输入输出 1. **C** 使用标准库函数: ```c printf("Value: %d", x); // 输出 scanf("%d", &x); // 输入 ``` 2. **C++** 流式操作: ```cpp cout << "Value: " << x << endl; // 输出 cin >> x; // 输入 ``` 3. **Python** 内置函数简化操作: ```python print(f"Value: {x}") # 输出 x = input("Enter a value: ") # 输入 ``` --- ### 七、条件编译(平台相关代码) 如引用[^1]所述,C/C++使用预处理器指令实现平台适配: ```c #ifdef _WIN32 // Windows专用代码 #else // Linux/macOS代码 #endif ``` 而Python等解释型语言通常通过运行时判断实现类似功能: ```python import platform if platform.system() == "Windows": # Windows逻辑 else: # 其他系统逻辑 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值