JavaScript实现Y组合子函数

什么是Y组合子函数?

通俗来讲就是用来解决匿名递归函数实现的。

实现过程

我们知道,具名递归函数可以直接通过函数名称来实现自己调用自己,即递归调用,那么匿名函数怎么实现呢?首先来看一段代码:

function Y(g) { return g(g) }

Y函数的参数名称为g,g为函数类型,然后对g进行递归调用。看到这里是否发现了些相似的点,由于匿名函数没有具体名称,因此我们声明一个函数Y对g进行包装,以便函数g可以进行递归调用,接下来我们来看看具体的调用方法:

Y(Y)

很容易意料到,程序会报错。现在看来函数仍是具有名称的,让我们写的更抽象点儿:

(function (g) {
  return g(g);                          
})(function (g) {
  return g(g);                             
})

其实可以看到,我们仅仅是对Y(Y)这种写法进行了改写,他们本质都是相同的。立即执行函数中的参数就是我们之前编写的函数Y,而该立即执行函数中的函数体与我们的Y函数并没有任何联系,在这里,他们仅仅只是代码一样而已,后面我们会看到差别。现在,程序并不能正确的跑起来,让我们以阶乘为例,看看会有怎样的事情发生:

(function (g) {
    return g(g);
})(function (g) {
    return function (x) {
        if (x === 0) {
            return 1;
        }
        return x;
    }
})

在这里,我们简单地改写了一下Y函数的函数体,他返回了另外的一个函数,该函数的功能很简单,如果传入的参数为0,则返回1,否则返回原值,我们可以先来看看这样的执行结果是什么:

(function (g) {
    return g(g);
})(function (g) {
    return function (x) {
        if (x === 0) {
            return 1;
        }
        return x;
    }
})(10)

毫无疑问地,结果为10,当10改为0时,返回的结果为1。在这里,立即执行函数的函数体中的g(g)其实仅仅只调用了一次Y函数,因为我们还没有对Y进行递归处理,接下来看看完整功能的函数是怎样的:

(function (g) {
    return g(g);
})(function (g) {
    return function (x) {
        if (x === 0) {
            return 1;
        }
        return x * g(g)(x - 1);
    }
})

是的,我们将return x改为return x * g(g)(x - 1),就实现了我们所需要的阶乘功能。想想这是为什么?此处的g(g)(x - 1)是什么意思?其实就像我之前说的那样,g(g)代表我们的Y函数会对自身进行一次递归调用,由于现在Y函数的返回值为一个函数,它需要一个数值作为参数,因为我们将(x - 1)传入了进去,这与我们写具名函数的递归写法时是一样的。

我们能否让这个函数更通用些呢?这里,阶乘的逻辑是与Y函数写在一起的,我们试着将它抽象出来:

function Y(f) {
    return (function (g) {
        return g(g);
    })(function (g) {
        return function (x) {
            return f(x, g(g));
        }
    });
}

function fc(x, g) {
    if (x === 0) {
        return 1;
    }
    return x * g(x - 1);
}

console.log(Y(fc)(4));

顺便一说,忘记之前的Y函数吧,现在的Y函数与之前不一样。可以看到,我们仅仅只是将之前的阶乘逻辑提取出来,定义为fc函数,事实上这依旧不够通用,我们先对fc函数进行修改:

function fc(g) {
    return function (x) {
        return x === 0 ? 1 : x * g(x - 1);
    }
}

所做的是将fc的两个参数变为逐个传递(即柯里化函数),然后立即函数的变化:

function Y(f) {
    return (function (g) {
        return g(g);
    })(function (g) {
        return f(function (x) {
            return g(g)(x);
        });
    });
}

function fc(g) {
    return function (x) {
        return x === 0 ? 1 : x * g(x - 1);
    }
}

console.log(Y(fc)(4));

这里,我们可以看到立即执行函数的参数部分,仅仅只是对自身进行递归调用,逻辑交由外部函数处理。

END.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容