问:上回乱扯淡了一通,这回该讲正题了吧。

答:OK. 先来列举一些我参考过,并从中受到启发的文章。

(1.)老赵的一篇文章:使用Lambda表达式编写递归函数

(2.)装配脑袋的两篇文章:VS2008亮点:用Lambda表达式进行函数式编程用Lambda表达式进行函数式编程(续):用C#实现Y组合子

(3.)Y组合子的推导过程(用Scheme推导),这里的“推导”并不是数学意义上上的推导证明,而是说如何一步步引导构想出Y来的,值得一看。也许从某种程度反应出了当年Y是如何被发明的。

[还有一些文章一时半会儿也找不到了,哪天记起来再过来补上]

本文决定用C#来展示列子,因为C#对普通函数和Lambda表达式都支持得非常好,方便进行对比。同时,会展示其它语言与之不同之处(如js,scheme是动态类型语言,因此实现Y简单不少;F#应该算同门师兄弟了,除了语法更间接之外,也有一个不大不小的区别;C++是一个更加静态的语言,实现起来难度更大些,需要一个不大不小的技巧,这些会在后面章节中展示)。

还是从那个古老但有效的例子开始吧——factorial。这是一个普通的递归函数:

//C#
int Fact(int x) { return x == 0 ? 1 : x * Fact(x - 1); }

所谓递归,就是在函数体内调用函数本身,然而Lambda表达式(下文简称Lambda)是一个匿名函数,又能通过什么去调用自己呢?我曾看到网友给出过这样一个啼笑皆非的方案:

//C#
Func<int, int> fact = null;
fact = x => x == 0 ? 1 : x * fact(x - 1);

对应到F#中就是这样的,这就跟前面C#中的函数颇为相似了:

//F#
let rec fact = fun x -> if(x=0) then 1 else x * fact(x-1)

可是这并没有实现所谓的匿名递归,因为你的确为了调用自身,而给自身取了一个名字“fact”,且它被上下文依赖。所以我们来规定来定义一个匿名递归Lambda的“衡量标准”:仍然以factorial函数为例,说我需要这么一个表达式(其中的所有参量不被上下文依赖,即构成闭包),将其代入到以下代码的(▲)处,可以计算得到参数(5)的阶乘值(120)。

//C#伪码
int result = (▲)(5);  //120

那不为自身命名,又如何调用之呢?请看下面这两行伪码,其中T?代表某种未知类型:

//C#伪码
T? fact_maker = self => x => x == 0 ? 1 : x * self(x - 1);
int result = fact_maker(fact_maker)(5);

我们期望的是,把这个Lambda通过self参量传递给Lambda自己(就像fact_maker(fact_maker)做的那样),以此来返回如上伪码的下划线部分(即类型为Func<int,int>的fact函数)。但是这里有个矛盾——我们既希望self能接受fact_maker(类型为Func<Tself,Func<int,int>>,其中Tself代表self的类型),又希望self在Lambda内部扮演fact的角色(类型为Func<int,int>),所以,这样是行不通的。既然我们希望fact是self的返回值,不如这样试试:

//C#伪码
Funct<Tself,Func<int,int>> fact_maker =
self => x => x == 0 ? 1 : x * self(self)(x - 1);

现在来分析一下类型,self的类型应该是Func<Tself,Func<int,int>>,self(self)的返回类型正好是Func<int,int>,同时fact_maker也是Func<Tself,Func<int,int>>,可以顺利地被self传递,一切都很和谐。那Tself该怎么表达呢?它接受自己,且返回Func<int,int>,如此便得到了如下委托定义(Ouroboros是衔尾蛇的意思):

//C#
delegate T OuroborosFunc<T>(OroborosFunc<T> self);

这样OuroborosFunc<Func<int,int>>正是我们需要的self类型。到这里,可以测试一下以上的构想,看看是否能良好工作了。

//C#
OuroborosFunc<Func<int, int>> fact_maker =
self => x => x == 0 ? 1 : x * self(self)(x - 1);
int result = fact_maker(fact_maker)(5);
Console.WriteLine(result); //120

非常棒,输出了正确的结果。如果你愿意,可以不需要这个fact_maker,而是直接把这个Lambda应用到另一个同样的Lambda身上,不过需要注意显式声明类型,因为C#不允许Lambda自动类型推断,就像这样:

//C#
int result = ((OuroborosFunc<Func<int, int>>)
(self => x => x == 0 ? 1 : x * self(self)(x - 1)))
(self => x => x == 0 ? 1 : x * self(self)(x - 1))(5);
Console.WriteLine(result); //120

嗯,也不错,虽然丑陋了一点,但毕竟还是满足了之前对匿名递归的定义。当然,其中的自我调用部分可以简化成这样:

Func<int, int> Fact =
((Func<OuroborosFunc<Func<int, int>>, Func<int, int>>)(s => s(s)))
(self => x => x == 0 ? 1 : x * self(self)(x - 1));

问:可是,讲了半天,还是没有讲到Y组合子啊。

答:别急,我们姑且用一个例子来展示匿名递归,不具备足够的抽象性,离Y组合子还有不少的路要走,请耐性待续…

大到可以小说的Y组合子(一)的更多相关文章

  1. 大到可以小说的Y组合子(三)

    答:关于Fix的问题你fix了吗? 问:慢着,让我想想,上次留下个什么问题来着?是说我们有了一个求不动点的函数Fix,但Fix却是显式递归的,是吧? 答:有劳你还记的这个问题. 问:Fix的参与背离了 ...

  2. 大到可以小说的Y组合子(二)

    问:上一回,你在最后曾提到"抽象性不足",这话怎么说? 答:试想,如果现在需要实现一个其它的递归(比如:Fibonacci),就必须把之前的模式从头套一遍,然后通过fib_make ...

  3. 大到可以小说的Y组合子(零)

    问:啊!我想要一个匿名的递归… 答:Y(音同Why)… … … 问:作为一位命令式语言的使用者,为什么会突然折腾起Y组合子呢? 答:的确,这事儿要从很久以前的几次搁浅开始说起…上学的时候,从来没有接触 ...

  4. Y组合子

    Y组合子 Y组合子的用处 作者:王霄池链接:https://www.zhihu.com/question/21099081/answer/18830200来源:知乎著作权归作者所有.商业转载请联系作者 ...

  5. Lambda演算 - 简述Y组合子的作用

    Y组合子:\f.(\x.f(xx))(\x.f(xx)),接受一个函数,返回一个高阶函数 Y组合子用于生成匿名递归函数. 什么叫匿名递归函数,考虑以下C语言递归函数 int sum(int n) { ...

  6. Racket中使用Y组合子

    关于Y组合子,网上已经介绍很多了,其作用主要是解决匿名lambda的递归调用自己. 首先我们来看直观的递归lambda定义, 假设要定义阶乘的lambda表达,C#中需要这么定义 Func<in ...

  7. 简单易懂的程序语言入门小册子(4):基于文本替换的解释器,递归,如何构造递归函数,Y组合子

    递归.哦,递归. 递归在计算机科学中的重要性不言而喻. 递归就像女人,即令人烦恼,又无法抛弃. 先上个例子,这个例子里的函数double输入一个非负整数$n$,输出$2n$. \[ {double} ...

  8. [学习] 从 函数式编程 到 lambda演算 到 函数的本质 到 组合子逻辑

    函数式编程 阮一峰 <函数式编程初探>,阮一峰是<黑客与画家>的译者. wiki <函数编程语言> 一本好书,<计算机程序的构造与解释>有讲到schem ...

  9. 面向组合子设计Coder

    面向组合子 面向组合子(Combanitor-Oriented),是最近帮我打开新世界大门的一种pattern.缘起haskell,又见monad与ParseC,终于ajoo前辈的几篇文章. 自去年9 ...

随机推荐

  1. mac更新之前,好容易把网络设置好

    1.[虚拟机]虚拟网络编辑器--恢复默认设置 [主机网络]自动ip [VM8] 当时忘记将mac里面的网络设置截图...导致更新后网络出问题后却显示网络设置失败

  2. M_LROOT,LD_LIBRARY_PATH, “Not all extension Dlls were loaded”问题原因及解决方法(持续更新)

    最近在需要在云主机上进行压力测试,所以需要Linux的Agent. 一.安装:教程可以百度,大概步骤如下: 1.Upload Linux.zip to 指定的机器 2.解压,chmod 777 $Li ...

  3. (转)C#.NET使用TTS引擎实现文语转换

    (转)C#.NET使用TTS引擎实现文语转换 本文讲述使用微软TTS5.1语音引擎(中文)实现文本阅读和音频输出为WAV完美解决方案. 网上很多文章说的是要安装SAPI.51 SDK,而这个东西有好几 ...

  4. flask开发restful api系列(6)-配置文件

    任何一个好的程序,配置文件必不可少,而且非常重要.配置文件里存储了连接数据库,redis的用户密码,不允许有任何闪失.要有灵活性,用户可以自己配置:生产环境和开发环境要分开,最好能简单的修改一个东西, ...

  5. Git之detached HEAD

    今天遇到了和CSDN上博主相同的问题,就是使用git -branch -a 发现自己处于一个零时的分支上.这篇博文写的不错,转载记录一下. 转载:http://blog.csdn.net/lili62 ...

  6. jdk8新特性之lambda expressions

    本文分两部分: 语法简单说明 lambda的使用 注:这两部分内容均以类+注释的方式进行说明,并且内容均来自官方教程(https://docs.oracle.com/javase/tutorial/j ...

  7. Android的两种菜单

    Android子菜单和选项菜单与上下文菜单的实现 菜单在Android开发中必不可少,可是要怎么实现各种菜单呢?是不是很头疼呢?下面我就来介绍一下: 1. 选项菜单和子菜单的实现 选项菜单:最常规的菜 ...

  8. 为什么设计模式在C++社区没有Java社区流行?

    我们发现设计模式在Java社区很流行,但是在C++社区却没有那么被关注,甚至有点被排斥,究竟是什么原因造成这个差异的呢?    昨天和同事讨论这个问题,最后得出几点原因:     (1)C++内存需要 ...

  9. Alias Method解决随机类型概率问题

    举个例子,游戏中玩家推倒了一个boss,会按如下概率掉落物品:10%掉武器 20%掉饰品 30%掉戒指 40%掉披风.现在要给出下一个掉落的物品类型,或者说一个掉落的随机序列,要求符合上述概率. 一般 ...

  10. ubuntu下编译时遇到的错误及解决方式

    1.错误展示: hangma@ubuntu:~/test/test/protest/stack_test$ gcc  -c my_stack.c -o my_stack.o In file inclu ...