10种编程语言实现Y组合子

简介: Y组合子是Lambda演算的一部分,也是函数式编程的理论基础。它是一种方法/技巧,在没有赋值语句的前提下定义递归的匿名函数,即仅仅通过Lambda表达式这个最基本的“原子”实现循环/迭代。本文将用10种不同的编程语言实现Y组合子,以及Y版的递归阶乘函数。

一 Y-Combinator

Y组合子是Lambda演算的一部分,也是函数式编程的理论基础。它是一种方法/技巧,在没有赋值语句的前提下定义递归的匿名函数。即仅仅通过Lambda表达式这个最基本的“原子”实现循环/迭代,颇有道生一、一生二、二生三、三生万物的感觉。

1 从递归的阶乘函数开始

先不考虑效率等其他因素,写一个最简单的递归阶乘函数。此处采用Scheme,你可以选择自己熟悉的编程语言跟着我一步一步实现Y-Combinator版的阶乘函数。

(define (factorial n)(if (zero? n)1(* n (factorial (- n 1)))))

Scheme中 (define (fn-name)) 是 (define fn-name (lambda)) 的简写,就像JS中,function foo() {} 等价于 var foo = function() {}。把上面的定义展开成Lambda的定义:

(define factorial(lambda (n)(if (zero? n)1(* n (factorial (- n 1))))))

2 绑定函数名

想要递归地调用一个函数,就必须给这个函数取一个名字。匿名函数想要实现递归,就得取一个临时的名字。所谓临时,指这个名字只在此函数体内有效,函数执行完成后,这个名字就伴随函数一起消失。为解决这个问题,第一篇文章中[1]强制规定匿名函数有一个隐藏的名字this指向自己,这导致this这个变量名被强行占用,并不优雅,因此第二篇文章[2]借鉴Clojure的方法,允许自定义一个名字。

但在Lambda演算中,只有最普通的Lambda,没有赋值语句,如何绑定一个名字呢?答案是使用Lambda的参数列表!

(lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1))))))

3 生成阶乘函数的函数

虽然通过参数列表,即使用闭包技术给匿名函数取了一个名字,但此函数并不是我们想要的阶乘函数,而是阶乘函数的元函数(meta-factorial),即生成阶乘函数的函数。因此需要执行这个元函数,获得想要的阶乘函数:

((lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1))))))xxx)

此时又出现另一个问题:实参xxx,即形参factorial该取什么值?从定义来看,factorial就是函数自身,既然是“自身”,首先想到的就是复制一份一模一样的代码:

((lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1))))))(lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1)))))))

看起来已经把自己传递给了自己,但马上发现 (factorial (- n 1)) 会失败,因为此时的 factorial 不是一个阶乘函数,而是一个包含阶乘函数的函数,即要获取包含在内部的函数,因此调用方式要改成 ((meta-factorial meta-factorial) (- n 1)) 

((lambda (meta-factorial)(lambda (n)(if (zero? n)1(* n ((meta-factorial meta-factorial) (- n 1))))))(lambda (meta-factorial)(lambda (n)(if (zero? n)1(* n ((meta-factorial meta-factorial) (- n 1)))))))

把名字改成meta-factorial就能清晰地看出它是阶乘的元函数,而不是阶乘函数本身。

4 去除重复

以上代码已经实现了lambda的自我调用,但其中包含重复的代码,meta-factorial即做函数又做参数,即 (meta meta) 

((lambda (meta)(meta meta))(lambda (meta-factorial)(lambda (n)(if (zero? n)1(* n ((meta-factorial meta-factorial) (- n 1)))))))

5 提取阶乘函数

因为我们想要的是阶乘函数,所以用factorial取代 (meta-factorial meta-factorial) ,方法同样是使用参数列表命名:

((lambda (meta)(meta meta))(lambda (meta-factorial)((lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1))))))(meta-factorial meta-factorial))))

这段代码还不能正常运行,因为Scheme以及其他主流的编程语言实现都采用“应用序”,即执行函数时先计算参数的值,因此 (meta-factorial meta-factorial) 原来是在求阶乘的过程中才被执行,现在提取出来后执行的时间被提前,于是陷入无限循环。解决方法是把它包装在Lambda中(你学到了Lambda的另一个用处:延迟执行)。

((lambda (meta)(meta meta))(lambda (meta-factorial)((lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1))))))(lambda args(apply (meta-factorial meta-factorial) args)))))

此时,代码中第4行到第8行正是最初定义的匿名递归阶乘函数,我们终于得到了阶乘函数本身!

6 形成模式

如果把其中的阶乘函数作为一个整体提取出来,那就是得到一种“模式”,即能生成任意匿名递归函数的模式:

((lambda (fn)((lambda (meta)(meta meta))(lambda (meta-fn)(fn(lambda args(apply (meta-fn meta-fn) args))))))(lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1)))))))

Lambda演算中称这个模式为Y组合子(Y-Combinator),即:

(define (y-combinator fn)((lambda (meta)(meta meta))(lambda (meta-fn)(fn(lambda args(apply (meta-fn meta-fn) args))))))

有了Y组合子,我们就能定义任意的匿名递归函数。前文中定义的是递归求阶乘,再定义一个递归求斐波那契数:

(y-combinator(lambda (fib)(lambda (n)(if (< n 3)1(+ (fib (- n 1))(fib (- n 2)))))))

二 10种实现

下面用10种不同的编程语言实现Y组合子,以及Y版的递归阶乘函数。实际开发中可能不会用上这样的技巧,但这些代码分别展示了这10种语言的诸多语法特性,能帮助你了解如何在这些语言中实现以下功能:

  1. 如何定义匿名函数;
  2. 如何就地调用一个匿名函数;
  3. 如何将函数作为参数传递给其他函数;
  4. 如何定义参数数目不定的函数;
  5. 如何把函数作为值返回;
  6. 如何将数组里的元素平坦开来传递给函数;
  7. 三元表达式的使用方法。

这10种编程语言,有Python、PHP、Perl、Ruby等大家耳熟能详的脚本语言,估计最让大家惊讶的应该是其中有Java!

1 Scheme

我始终觉得Scheme版是这么多种实现中最优雅的!它没有“刻意”的简洁,读起来很自然。

(define (y-combinator f)((lambda (u)(u u))(lambda (x)(f (lambda args(apply (x x) args))))))((y-combinator(lambda (factorial)(lambda (n)(if (zero? n)1(* n (factorial (- n 1)))))))10) ; => 3628800

2 Clojure

其实Clojure不需要借助Y-Combinator就能实现匿名递归函数,它的lambda——fn——支持传递一个函数名,为这个临时函数命名。也许Clojure的fn不应该叫匿名函数,应该叫临时函数更贴切。

同样是Lisp,Clojure版本比Scheme版本更简短,却让我感觉是一种刻意的简洁。我喜欢用fn取代lambda,但用稀奇古怪的符号来缩减代码量会让代码的可读性变差(我最近好像变得不太喜欢用符号,哈哈)。

(defn y-combinator [f](#(% %) (fn [x] (f #(apply (x x) %&)))))((y-combinator(fn [factorial]#(if (zero? %) 1 (* % (factorial (dec %))))))10)

3 Common Lisp

Common Lisp版和Scheme版其实差不多,只不过Common Lisp属于Lisp-2,即函数命名空间与变量命名空间不同,因此调用匿名函数时需要额外的funcall。我个人不喜欢这个额外的调用,觉得它是冗余信息,位置信息已经包含了角色信息,就像命令行的第一个参数永远是命令。

(defun y-combinator (f)((lambda (u)(funcall u u))(lambda (x)(funcall f (lambda (&rest args)(apply (funcall x x) args))))))(funcall (y-combinator(lambda (factorial)(lambda (n)(if (zerop n)1(* n (funcall factorial (1- n)))))))10)

4 Ruby

Ruby从Lisp那儿借鉴了许多,包括它的缺点。和Common Lisp一样,Ruby中执行一个匿名函数也需要额外的“.call”,或者使用中括号“[]”而不是和普通函数一样的小括号“()”,总之在Ruby中匿名函数与普通函数不一样!还有繁杂的符号也影响我在Ruby中使用匿名函数的心情,因此我会把Ruby看作语法更灵活、更简洁的Java,而不会考虑写函数式风格的代码。

def y_combinator(&f)lambda {|&u| u[&u]}.call do |&x|f[&lambda {|*a| x[&x][*a]}]end
endy_combinator do |&factorial|lambda {|n| n.zero? ? 1: n*factorial[n-1]}
end[10]

5 Python

Python中匿名函数的使用方式与普通函数一样,就这段代码而言,Python之于Ruby就像Scheme之于Common Lisp。但Python对Lambda的支持简直弱爆了,函数体只允许有一条语句!我决定我的工具箱中用Python取代C语言,虽然Python对匿名函数的支持只比C语言好一点点。

def y_combinator(f):return (lambda u: u(u))(lambda x: f(lambda *args: x(x)(*args)))y_combinator(lambda factorial: lambda n: 1 if n < 2 else n * factorial(n-1))(10)

6 Perl

我个人对Perl函数不能声明参数的抱怨更甚于繁杂的符号!

sub y_combinator {my $f = shift;sub { $_[0]->($_[0]); }->(sub {my $x = shift;$f->(sub { $x->($x)->(@_); });});
}print y_combinator(sub {my $factorial = shift;sub { $_[0] < 2? 1: $_[0] * $factorial->($_[0] - 1); };
})->(10);

假设Perl能像其他语言一样声明参数列表,代码会更简洁直观:

sub y_combinator($f) {sub($u) { $u->($u); }->(sub($x) {$f->(sub { $x->($x)->(@_); });});
}print y_combinator(sub($factorial) {sub($n) { $n < 2? 1: $n * $factorial->($n - 1); };
})->(10);

7 JavaScript

JavaScript无疑是脚本语言中最流行的!但冗长的function、return等关键字总是刺痛我的神经:

var y_combinator = function(fn) {return (function(u) {return u(u);})(function(x) {return fn(function() {return x(x).apply(null, arguments);});});
};y_combinator(function(factorial) {return function(n) {return n <= 1? 1: n * factorial(n - 1);};
})(10);

ES6提供了 => 语法,可以更加简洁:

const y_combinator = fn => (u => u(u))(x => fn((...args) => x(x)(...args)));
y_combinator(factorial => n => n <= 1? 1: n * factorial(n - 1))(10);

8 Lua

Lua和JavaScript有相同的毛病,最让我意外的是它没有三元运算符!不过没有使用花括号让代码看起来清爽不少~

function y_combinator(f)return (function(u)return u(u)end)(function(x)return f(function(...)return x(x)(...)end)end)
endprint(y_combinator(function(factorial)return function(n)return n < 2 and 1 or n * factorial(n-1)end
end)(10))

注意:Lua版本为5.2。5.1的语法不同,需将 x(x)(...) 换成 x(x)(unpack(arg))

9 PHP

PHP也是JavaScript的难兄难弟,function、return……

此外,PHP版本是脚本语言中符号($、_、()、{})用的最多的!是的,比Perl还多。

function y_combinator($f) {return call_user_func(function($u) {return $u($u);}, function($x) use ($f) {return $f(function() use ($x) {return call_user_func_array($x($x), func_get_args());});});
}echo call_user_func(y_combinator(function($factorial) {return function($n) use ($factorial) {return ($n < 2)? 1: ($n * $factorial($n-1));};
}), 10);

10 Java

最后,Java登场。我说的不是Java 8,即不是用Lambda表达式,而是匿名类!匿名函数的意义是把代码块作为参数传递,这正是匿名类所做得事情。

image.png
image.png

作者 | 技师

​​​​​​​​​​​​​​原文链接

本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/513718.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7读不出来卡显示无服务器,win7识别不了网络如何解决_win7显示未识别网络的处理方法...

我们在使用萝卜家园win7系统系统久了之后难免会出现各种问题&#xff0c;例如最近就有网友向小编反映说自己的win7出现了识别不了网络的情况&#xff0c;不知道怎么解决很是苦恼。没关系&#xff0c;下面本文就为大家整理了关于win7显示未识别网络的处理方法。处理方法如下&…

走完线上 BUG 定位最后一公里

简介&#xff1a; 因为线上线下环境隔离的问题&#xff0c;线上的输入很多时候难以在日常环境中构造&#xff0c;定位 bug 效率低下。是否有简单快捷的办法呢&#xff1f; 一个小故事 周末12点的闹钟在回龙观均价3000的出租屋急促的响起&#xff0c;程序员小A慵懒的拿过手机&…

不带头节点的链表有哪些缺点_14. 删除链表中重复的结点

删除链表中重复的结点 题目描述在一个排序的链表中&#xff0c;存在重复的结点&#xff0c;请删除该链表中重复的结点&#xff0c;重复的结点不保留&#xff0c;返回链表头指针。 例如&#xff0c;链表1->2->3->3->4->4->5 处理后为 1->2->5写链表中我…

基于 Flutter 的 Web 渲染引擎「北海」正式开源!

简介&#xff1a; 阿里巴巴历时 3 年自研开发的 Web 渲染引擎北海&#xff08;英文名&#xff1a;Kraken&#xff09;正式开源&#xff0c;致力打造易扩展&#xff0c;跨平台&#xff0c;高性能的渲染引擎&#xff0c;并已在优酷、大麦、天猫等业务场景中使用。 作者 | 染陌 来…

“倚天”一出,谁与争锋?阿里发布首颗云芯片倚天 710,死磕自研芯

作者 | 贾凯强、伍杏玲 出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;10 月 19 日&#xff0c;2021 年云栖大会正式拉开帷幕。达摩院院长、阿里云智能事业部总裁张建锋表示&#xff0c;如今一个以云为核心的新型计算体系结构正在形成&#xff0c;该…

三菱m70刀杯上下m代码_加工中心常用G代码和M代码大全,收藏好了

我们在使用数控加工中心的过程中&#xff0c;最常见的数控代码有两种&#xff0c;一种是G代码&#xff0c;一种是M代码。本文整理了常见的G代码和M代码的含义&#xff0c;不同厂商不同的数控系统可能稍有出入&#xff0c;在实际中以说明书为准。G代码&#xff1a;准备功能&…

DataWorks搬站方案:Azkaban作业迁移至DataWorks

简介&#xff1a; DataWorks迁移助手提供任务搬站功能&#xff0c;支持将开源调度引擎Oozie、Azkaban、Airflow的任务快速迁移至DataWorks。本文主要介绍如何将开源Azkaban工作流调度引擎中的作业迁移至DataWorks上。 DataWorks迁移助手提供任务搬站功能&#xff0c;支持将开源…

DataWorks搬站方案:Airflow作业迁移至DataWorks

简介&#xff1a; DataWorks提供任务搬站功能&#xff0c;支持将开源调度引擎Oozie、Azkaban、Airflow的任务快速迁移至DataWorks。本文主要介绍如何将开源Airflow工作流调度引擎中的作业迁移至DataWorks上 DataWorks提供任务搬站功能&#xff0c;支持将开源调度引擎Oozie、Az…

mysql的每隔1分钟定时_深入研究MySQL(四)、备份与恢复

一、前言知道备份的重要性吗&#xff1f;没经历过的可能永远不知道&#xff0c;我们在重装系统、手机升级等时候&#xff0c;备份一下必要数据总是有好处的&#xff0c;指不定哪个神操作导致磁盘数据丢失&#xff0c;前些日子删库跑路被判刑的那位老哥&#xff0c;如果公司有备…

重磅发布 阿里云数据中台全新产品DataTrust聚焦企业数据安全保障

简介&#xff1a; DataTrust&#xff08;隐私增强计算产品&#xff09;是基于阿里云底层多项基础安全能力&#xff0c;经过阿里云数据中台丰富的客户业务实践&#xff0c;构建的一款为企业数据安全流通的产品。 随着包括零售、制造、金融等多行业数字化转型加速推进&#xff0…

完全无人驾驶量产车Apollo Moon威马版首秀 成本降一半、能力翻10倍

2021年10 月19日&#xff0c;百度Apollo联合威马正式发布两款新车&#xff0c;新车型均基于威马W6打造。一款是配置激光雷达的新一代无人车Apollo Moon量产车型&#xff08;Apollo Moon威马版&#xff09;&#xff1b;另一款是搭载百度ANP领航辅助驾驶系统与AVP自主泊车系统的量…

二阶振荡环节的谐振频率_自动控制系统时域分析十三:对数频率特性

一&#xff1a;对数频率特性曲线(波德图-Bode图)Bode图由对数幅频特性和对数相频特性两条曲线组成。⒈波德图坐标(横坐标是频率&#xff0c;纵坐标是幅值和相角)的分度&#xff1a;1)横坐标分度(称为频率轴)&#xff1a;它是以频率w的对数值logw进行线性分度的。但为了便于观察…

深入分析 Flutter 渲染性能

简介&#xff1a; Flutter 有很多优点&#xff0c;特别是对于开发者来说&#xff0c;跨平台多端支持&#xff0c;丰富的 UI 组件库和交互效果&#xff0c;声明式 UI&#xff0c;React 的更新方式&#xff0c;Hot-reload 提高开发效率等等。虽然它在渲染性能上有不少缺陷&#x…

张勇云栖大会谈科技担当与责任:做开放共享人人受益的好科技

10月19日&#xff0c;2021云栖大会在杭州开幕&#xff0c;阿里巴巴集团董事会主席兼首席执行官张勇在主论坛致辞中表示&#xff0c;从万物互联到万物生长&#xff0c;云栖大会经过12年的轮回&#xff0c;正站在一个新的起点上。阿里希望在基础研究方面有更多、更扎实的社会担当…

技术干货 | 源码解析 Github 上 14.1k Star 的 RocketMQ

简介&#xff1a; 站在发送方视角&#xff0c;通过源码&#xff0c;来分析在事务消息发送中 RocketMQ 是如何工作的。 前言 Apache RocketMQ 作为广为人知的开源消息中间件&#xff0c;诞生于阿里巴巴&#xff0c;于 2016 年捐赠给了 Apache。从 RocketMQ 4.0 到如今最新的 v4…

编译后没有taget文件夹_matconvnet安装、编译、配置

一、安装&#xff08;可见大神windows下编译Matconvnet的方法(CPU和GPU)&#xff09;1.&#xff08;本人&#xff09;安装matlab2015b、Visual Studio 20152.官网Home - MatConvNet下载matconvnet工具包&#xff0c;我的名字是matconvnet-1.0-beta25&#xff0c;然后解压文件到…

面对大规模 K8s 集群,如何先于用户发现问题?

简介&#xff1a; 怎样才能在复杂的大规模场景中&#xff0c;做到真正先于用户发现问题呢&#xff1f;下面我会带来我们在管理大规模 ASI 集群过程中对于快速发现问题的一些经验和实践&#xff0c;希望能对大家有所启发。 作者 | 彭南光&#xff08;光南&#xff09; 来源 | 阿…

第7届UBBF在迪拜举办 加强网络设施建设将加速产业发展成为共识

今天&#xff0c;由联合国宽带委员会和华为共同举办的第7届全球超宽带高峰论坛&#xff08;UBBF 2021&#xff09;在迪拜开幕。作为固网领域全球最大的峰会&#xff0c;今年的UBBF以“联接&#xff0c;新增长”为主题&#xff0c;全球领先的运营商、设备商共同围绕“网络基础设…

使用 rocketmq-spring-boot-starter 来配置、发送和消费 RocketMQ 消息

简介&#xff1a; 本文将 rocktmq-spring-boot 的设计实现做一个简单的介绍&#xff0c;读者可以通过本文了解将 RocketMQ Client 端集成为 spring-boot-starter 框架的开发细节&#xff0c;然后通过一个简单的示例来一步一步的讲解如何使用这个 spring-boot-starter 工具包来配…

回归的误差服从正态分布吗_盘点10大回归类型:总有一款深得你心

全文共2507字&#xff0c;预计学习时长5分钟除了统计模型和其他的一些算法&#xff0c;回归是机器学习成功运行的重要构成要素。回归的核心是寻找变量之间的关系&#xff0c;而机器学习需要根据这种关系来预测结果。显然&#xff0c;任何称职的机器学习工程师都应重视回归&…