赵劼的博客《》中用C#实现了为函数求出 Y 组合子的函数。C#代码生涩,难以阅读,原代码如下:
static FuncFix (Func , Func > f) { return x => f(Fix(f))(x); } static Func Fix (Func , Func > f) { return (x, y) => f(Fix(f))(x, y); }
代码调用如下:
var fac = Fix(f => x => x <= 1 ? 1 :x * f(x - 1));
让代码变得容易阅读原是F#的强项,我们尝试用F#重写上面的代码,把 Fix 函数改名为 Y,得出以下代码:
let rec Y f x = f (Y f) x
代码调用则如下:
let fac = Y (fun yf x -> if x <= 1 then 1 else x * yf(x - 1))
现在,Y 组合子函数一下子就明撩了:它就是定义了一个高阶函数 Y,其部分实现为: Y f = f (Y f)
我们检视 fac 函数中的 Y f ,这里,参数 f 其实就是匿名函数 fun yf x -> ...
关键: 这里参数 yf 被代入值 Y f,而 Y f 等于 fac,因此上,实质上变成:
let fac = fun x -> if x <= 1 then 1 else x * fac(x - 1)
OK,现在如果在 let 后面加上 rec ,那就成为一个标准的递归函数了。
另外,需要注意的是: let rec Y f x = f (Y f) x 不能改写为 let rec Y f = f (Y f)
理论上,两者的 Y 等义,但是,后者会在 Y 被调用时,先计算出 Y f 的值,最后因为无法结束递归而导致堆栈溢出。
而前者不计算 Y f,直接计算 Y f x,在 x <= 1 时候停止递归。
================================================================================
什么?Y 组合子有什么用?就是在你需要使用匿名函数,但是同时需要递归的时候,有了 Y 组合子你才可以同时拥有两者。
譬如,可以这样:
a |> (+) 4 |> Y (fun yf x -> if x <= 1 then 1 else x * yf(x - 1))