最近看了赵姐夫的这篇博客http://blog.zhaojie.me/2009/08/recursive-lambda-expressions.html,主要讲的是如何使用 Lambda 编写递归函数。其中提到了不动点组合子这个东西,这个概念来自于函数式编程的世界,直接理解起来可能有些困难,因此咱们能够一块儿来尝试使用 Lambda 来编写递归函数,以此来探索不动点组合子的奥秘。在阅读过程当中,咱们可使用“C# 交互窗口”或者 Xamarin WorkBook 来运行给出的代码,由于 Lambda 表达式中的变量,类型大多会被省略掉,直接阅读起来可能有些难懂。html
首先用常规手段写一个递归形式的阶乘express
int facRec (int n) { return n == 1 ? 1 : n * facRec(n - 1); } facRec(5) // 120
那么如何使用 Lambda 表示阶乘的递归形式呢?Lambda 是匿名函数,那么就不能直接在内部调用本身,不过函数的参数是能够有名字的,那么能够给这个 Lambda 添加一个函数参数,在调用的时候,就把这个 Lambda 本身做为参数传入,从而实现递归的效果:编程
delegate Func<int, int> F(F self); F fac = (F f) => (int n) => n == 1 ? 1 : n * f(f)(n - 1); fac(fac)(5) // 120
您可能已经发现了,我没有把 F 定义为接受两个参数,第一个接受一个函数做为参数,第二个是要求阶乘的值,返回一个 int 结果的形式。这实际上是一种函数式编程的作法——任何包含多个参数的函数均可以写成多个只包含一个参数的函数的组合的形式,咱们把这种操做叫作“柯里化”,例如:闭包
int sum(int a, int b, int c) { return a + b +c; } Func<int, Func<int ,int>> fSum(int a) { return (int b) => { return (int c) => { return a + b + c; }; }; } sum(1,2,3) == fSum(1)(2)(3) //true
虽然fSum的返回值类型看起来有些鬼畜,可是彻底是 C# 本身的缘由——不能自动推断方法的返回值类型。函数式编程
接着回到咱们的探索过程,注意到第3行出现了f(f)
这样的东西,那么能够把这种表达式提取出来,做为参数传入。函数
fac = (F f) => (int n) => { Func<Func<int,int>, Func<int,int>> tmp = (Func<int,int> g) => { return (int h) => { if(h == 1) return 1; else { return h * g(h - 1); } }; }; return tmp(f(f))(n); }; fac(fac)(5) // 120
如今,能够看到第 5 行返回的函数看起来挺像咱们最开始定义的普通形式的递归阶乘,何不尝试将其提取出来,而后在 fac 中调用。post
Func<Func<int, int>, Func<int, int>> fac0 = (Func<int, int> g) => { return (int h) => { if(h == 1) return 1; else { return h * g(h - 1); } }; }; fac = (F f) => (int n) => { return fac0(f(f))(n); }; fac(fac)(5) // 120
这下咱们的 fac 函数就变得简短了不少,可是其中仍引用了一个在外部定义的函数,这让他变得不够“纯”,因此能够把这个函数做为参数传入.net
delegate F NewF(Func<Func<int, int>, Func<int, int>> g); NewF newFac = g => { return (F f) => (int n) => g( f(f) )(n); }; // 等价于 newFac = g => f => n => g(f(f))(n); newFac(fac0)(newFac(fac0))(5)
重复的东西又出现了,能够把newFac(fac0)
提取出来,这样的话就须要一个接受 F
类型函数并返回一个 Func<int, int>
类型函数的东西——其实就是前面定义的 F
啦~code
F sF = f => f(f); sF(newFac(fac0))(5) // 120
如今接着尝试把fac0
从两层括号中解放出来,以实现柯里化。因此首先就须要定义一个接受跟newFac
类型相同的委托做为参数,并返回一个委托,这个返回的委托接受一个参数,参数类型与 fac0
相同。htm
delegate Func<Func<Func<int, int>, Func<int, int>>, Func<int,int>> NewSF(NewF newF); NewSF newSF = newF => { return (Func<Func<int, int>, Func<int, int>> g) => { var f = newF(g); return f(f); }; }; newSF(newFac)(fac0)(5)
newF
是一个 NewF
类型的委托,返回值的类型是 F
。注意到 newFac = g => f => n => g(f(f))(n)
,这是一个纯函数,能够直接代入到newSF
之中,因此上面的newSF
能够进一步化简。首先用泛型化简 g
的类型,在泛型的特例化以后,g
的类型跟上面的 newSF
里面的 g
的类型实际上是同样的。newSF
的参数 newF
能够代换为 newFac
,newFac(g)
的结果类型是 F
,也就是上面的 f
,由于 f
须要把自身做为参数,因此就从新把 newFac(g)
做为参数传给 newFac(g)
返回的委托。
delegate T Y<T>(Func<T, T> g); Y<Func<int, int>> y = g => { return n => { return newFac(g)(newFac(g))(n); }; }; y(fac0)(5) // 120
还记得咱们得出 sF
的过程吗?接着把上面的 y
化简一下
y = g => { return n => { return sF(newFac(g))(n); }; }; y(fac0)(5) // 120
而后写的紧凑一些
y = g => n => sF(newFac(g))(n);
看看咱们如今获得的成果:
sF = f => f(f); newFac = g => f => n => g(f(f))(n); y = g => n => sF(newFac(g))(n); y(fac0)(5)
因为 C# 并非一门函数式的语言,Lambda 表达式不能直接调用,必需要转换成委托类型才能够直接调用,因此致使了 y
函数依赖另外两个函数,不过因为依赖的两个函数都是纯函数,因此没啥影响。可是上面的式子仍可继续简化,下面我把 newFac
定义在 y 表达式的内部:
y = g => { return n => { NewF localNewFac = localG => f => localN => localG(f(f))(localN); return sF(localNewFac(g))(n); }; }; y(fac0)(5)
能够看到 localNewFac
接受一个 localG
做为参数,而后返回一个 lambda 表达式,而后在第6行把 g
做为了实参传递给 localNewFac
,这么看来,localNewFac
其实不必接受一个 localG
做为参数,只要在闭包中捕获外部的变量 g
就行了
y = g => { return n => { F localF = f => localN => g(f(f))(localN); return sF(localF)(n); }; }; y(fac0)(5)
因为有 sF
的存在,编译器就有能力推断 sF
的参数类型,上面的代码就能够简化为:
y = g => { return n => { return sF(f => localN => g(f(f))(localN))(n); }; }; y(fac0)(5)
如今,咱们就能够获得下面两个式子:
sF = f => f(f); y = g => n => sF (f => m => g(f(f)) (m)) (n); y(fac0)(5) // 120
如今来从新审视一下 fac0
的类型,能够将其定义为下面的样子
delegate T FT<T>(T f); FT<Func<int, int>> newFac0 = (Func<int, int> f) => n => n == 1 ? 1 : n * f(n - 1);
忽略类型不看的话,这个 newFac0
跟最开始定义的 fac
简直如出一辙!接下来就从新定义一下 Y
的类型,使其能与 FT
类型兼容:
delegate T YT<T> (FT<T> f); delegate T SFT<T> (SFT<T> f); SFT<Func<int, int>> sFT = f => f(f); YT<Func<int, int>> yt = g => n => sFT (f => m => g(f(f)) (m)) (n); yt(newFac0)(5) // 120
SFT
是一个辅助类型,由于 C# 里面不能直接调用 f => f(f)
这样的表达式。FT
是一个泛型的递归表达式的类型,能够用来定义任意的有递归能力的 Lambda。YT
定义了一个高阶函数的类型,能够用来递归调用一个匿名函数:
yt(f => n => n == 1 ? 1 : n * f(n - 1))(5)
再回过头去看最开始 fac
的使用方式: fac(fac)(5)
,若是咱们把 fac
跟 newFac0
表示的 Lambda 表达式叫作 fn(f)
,其中 f = fn(f)
,这里出现了递归的定义,毕竟 fac
表示的是一个递归函数。也就是说 f
被 fn
这个函数映射到了自身,这在数学上叫作“不动点”,例如 f(x) = x^2
, 那么 x = 1
时,f(1) = 1
,那么 x
就是函数 f
的一个不动点。
因此 yt(fn(f)) = fn(fn(f)) = fn(f) = f
好吧,其实这里我也有些混乱了
因此 yt(fn)
这个函数计算出了函数 fn(x)
一个不动点,也就是 f
,人们就把 yt
称为 不动点算子(factor) 也就是 Y Combinator。
参考连接:
https://blog.cassite.net/2017/09/09/y-combinator-derivation/