简介:Y组合子是Lambda演算的一部分,是函数式编程的理论基础。定义一个没有赋值语句的递归匿名函数是一种方法/技巧,即只通过Lambda表达式最基本的“原子”来实现循环/迭代。本文将使用10种不同的编程语言实现Y组合器和递归阶乘函数的Y版本。作者|技术员来源|阿里科技公众号-Y-CombinatorY组合子是Lambda演算的一部分,是函数式编程的理论基础。这是一种在没有赋值语句的情况下定义递归匿名函数的方法/技巧。即只通过Lambda表达式最基本的“原子”实现循环/迭代,有道生一,一生二,二生三,三生万物的感觉。1从递归阶乘函数开始,写出最简单的递归阶乘函数,不考虑效率等其他因素。这里使用Scheme,大家可以选择自己熟悉的编程语言,跟着我一步步实现Y-Combinator版的阶乘函数。(define(factorialn)(if(zero?n)1(*n(factorial(-n1)))))(define(fn-name))在Scheme中是(definefn-name(lambda))的简写,就像在JS中一样,functionfoo(){}等同于varfoo=function(){}。把上面的定义展开成Lambda定义:(definefactorial(lambda(n)(if(zero?n)1(*n(factorial(-n1))))))2bindfunctionnamewanttorecursivetocalla函数,你必须给函数一个名字。如果一个匿名函数想要实现递归,它就不得不取一个临时的名字。所谓临时的,就是这个名字只在函数体内有效。函数执行后,名称随函数一起消失。为了解决这个问题,第一篇文章[1]中强制要求匿名函数有一个指向自身的隐藏名this,导致变量名this被强行占用,不优雅,所以第二篇文章[2]借鉴了允许自定义名称的Clojure方法。但是在Lambda演算中,只有最常见的Lambda,没有赋值语句,如何绑定一个名字?答案是使用Lambda的参数列表!(lambda(factorial)(lambda(n)(if(zero?n)1(*n(factorial(-n1))))))3通过参数列表生成阶乘函数的函数,即使用闭包技巧给匿名函数一个名字,但是这个函数并不是我们想要的阶乘函数,而是阶乘函数的元阶乘函数,也就是生成阶乘函数的函数。因此,您需要执行此元函数以获得所需的阶乘函数:((lambda(factorial)(lambda(n)(if(zero?n)1(*n(factorial(-n1))))))xxx)这时候又产生了一个问题:实参xxx,即形参阶乘应该取什么值呢?根据定义,阶乘就是函数本身。既然是“self”,首先想到的就是复制一模一样的代码:((lambda(factorial)(lambda(n)(if(zero?n)1(*n(factorial(-n1))))))(lambda(factorial)(lambda(n)(if(zero?n)1(*n(factorial(-n1)))))))看起来已经把它自己传递给了自己,但是马上发现(factorial(-n1))会失败,因为此时的factorial不是阶乘函数,而是包含阶乘函数的函数,也就是获取内部函数,所以调用方法为更改为((meta-factorialmeta-factorial)(-n1)):((lambda(meta-factorial)(lambda(n)(if(zero?n)1(*n((meta-factorialmeta-factorial)(-n1)))))(lambda(meta-factorial)(lambda(n)(if(zero?n)1(*n((meta-factorialmeta-factorial)(-n1)))))))将名称改为meta-factorial可以清楚地表明它是factorial的元函数,而不是factorial函数本身。4去除重复上面的代码已经实现了lambda的自调用,但是里面有重复的代码,meta-factorial既是函数又是参数,即(metameta):((lambda(meta)(metameta))(lambda(meta-factorial)(lambda(n)(if(zero?n)1(*n((meta-factorialmeta-factorial)(-n1)))))))5提取阶乘function因为我们要的是Factorialfunction,所以把(meta-factorialmeta-factorial)换成factorial,方法也用参数列表命名:((lambda(meta)(metameta))(lambda(meta-factorial)((lambda(factorial)(lambda(n)(if(zero?n)1(*n(factorial(-n1)))))(meta-factorialmeta-factorial))))此代码不起作用然而由于Scheme等世界上主要的编程语言实现都是采用“应用顺序”,即在函数执行时先计算参数的值,所以(meta-factorialmeta-factorial)本来是在执行期间执行的计算的过程atingthefactorial,现在提取后的执行时间被提前,从而陷入死循环。解决方案是将其包装在Lambda中(您了解了Lambda的另一种用途:延迟执行)。((lambda(meta)(metameta))(lambda(meta-factorial)((lambda(factorial)(lambda(n)(if(zero?n)1(*n(factorial(-n1))))))(lambdaargs(apply(meta-factorialmeta-factorial)args)))))此时,代码中的第4行到第8行正是最初定义的匿名递归阶乘函数,我们最终得到阶乘函数本身!6形成模式如果将阶乘函数作为一个整体抽取出来,就是一个“模式”,即可以生成任意匿名递归函数的模式:((lambda(fn)((lambda(meta)(metameta))(lambda(meta-fn)(fn(lambdaargs(apply(meta-fnmeta-fn)args)))))(lambda(factorial)(lambda(n)(if(zero?n)1(*n(factorial(-n1))))))在Lambda演算中,这种模式被称为Y-Combinator(Y-Combinator),即:(define(y-combinatorfn)((lambda(meta)(metameta))(lambda(meta-fn)(fn(lambdaargs(apply(meta-fnmeta-fn)args))))))使用Y组合器,我们可以定义任意匿名递归函数。前面的定义是递归阶乘,然后定义一个递归斐波那契数:(y-combinator(lambda(fib)(lambda(n)(if(
