缘起

造了一个轮子,根据GitHub项目地址,生成项目目录树,直观的展现项目结构,以便于介绍项目。欢迎Star。

repository-tree

技术栈:

  • ES6
  • Vue.js
  • Webpack
  • Vuex
  • lodash
  • GitHub API

应用涉及到了展现目录树,实现方法不可或缺的一定是递归遍历。进而开启了我对lambda演算的探索发现之旅。

探索发现之旅

本次乘坐的是 斐波那契 号邮轮,下面会涉及到一些 JavaScript 函数式编程中的一些基本概念。如果出现眩晕、恶心(kan bu dong)等不良反应,想下船的旅客纯属正常。常旅客请安心乘坐。

高阶函数

函数式编程中,接受函数作为参数,或者返回一个函数作为结果的函数通常就被称为高阶函数

mapfilterreduce 均属于高阶函数,高阶函数并不神秘,我们日常编程也会用到。

ES6 中的 map 例子

const arr = [1, 2, 3, 4, 5, 6]const powArr = arr.map(v => v * v)console.log(powArr) // [ 1, 4, 9, 16, 25, 36 ]

尾调用

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,是指某个函数的最后一步是调用另一个函数。尾调用即是一个作为返回值输出的高阶函数。

例如:

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

函数f()在尾部调用了函数g()

尾调用的重要性在于它可以不在调用栈上面添加一个新的堆栈帧,而是更新它,如同迭代一般。

尾递归

递归我们都不陌生,函数调用自身,称为递归。如果尾调用自身,就称为尾递归。通常被用于解释递归的程序是计算阶乘

// ES5
function factorial(n) {return n === 1 ? 1 : n * factorial(n - 1);
}factorial(6) // => 720// ES6
const factorial = n => n === 1 ? 1 : n * factorial(n - 1)factorial(6) // => 720

递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生“栈溢出”错误。对函数调用在尾位置的递归或互相递归的函数,由于函数自身调用次数很多,递归层级很深,尾递归优化则使原本 O(n) 的调用栈空间只需要 O(1)

尾递归因而具有两个特征:

  • 调用自身函数(Self-called);
  • 计算仅占用常量栈空间(Stack Space)。

再看看尾递归优化过的阶乘函数:

// ES5
function factorial(n, total) {return n === 1 ? total : factorial(n - 1, n * total);
}factorial(6, 1) // => 720// ES6
const factorial = (n, total) => n === 1 ? total : factorial(n - 1, n * total)factorial(6, 1) // => 720

在ES6中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

上面的阶乘函数factorial,尾递归优化后的阶乘函数使用到了total这个中间变量,为了做到递归实现,确保最后一步只调用自身,把这个中间变量改写成函数的参数,这样做是有缺点的,为什么计算6的阶乘,还要传入两个变量6和1呢?解决方案就是柯里化

柯里化

柯里化(Currying),是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

维基百科上的解释稍微有点绕了,简单来说,一个 currying 的函数只传递给函数一部分参数来调用它,让它返回一个闭包函数去处理剩下的参数。

// 阶乘尾递归优化写法
function currying(fn, n) {return function (m) {return fn.call(this, m, n);};
}function tailFactorial(n, total) {if (n === 1) return total;return tailFactorial(n - 1, n * total);
}const factorial = currying(tailFactorial, 1);factorial(6) // => 720

下面看下 ES6 中的 柯里化:

const fact = (n, total) => n === 1 ? total : fact(n - 1, n * total)const currying = f => n => m => f(m, n)const factorial = currying(fact)(1)factorial(6) // => 720

上面代码通过柯里化,将尾递归变为只接受单个参数的 factorial,得到了想要的factorial(6) 独参函数。

思考?,有木有更简单的方法实现上面独参尾递归栗子。当然有,利用ES6的函数新特性,函数默认值。

简单化问题:

const fact = (n, total = 1) => n === 1 ? total : fact(n - 1, n * total)factorial(6) // => 720

Lambda表达式

JavaScript 中,Lambda表达式可以表示匿名函数。

恒等函数在 JavaScript 中的栗子:

// ES5
var f = function (x) {return x;
};// ES6
const f = x => x

lambda表达式 来写是这样子的:λx.x

现在试着用lambda表达式写出递归(匿名函数递归),使用具有递归效果的lambda表达式,将lambda表达式作为参数之一传入其自身。

// ES5
function factorial(f, n) {return n === 1 ? 1 : n * f(f, n - 1)
}factorial(factorial, 6) // => 720// ES6
const factorial = (f, n) => n === 1 ? 1 : n * f(f, n - 1)factorial(factorial, 6) // => 720

是的,这么做还是太难看了,没人希望写一个阶乘函数还要传入其他参数。解决方案仍然是柯里化。尾调用优化后的Lambda表达式递归:

const fact = (f, n ,total = 1) => n === 1 ? total : f(f, n - 1, n * total)const currying = f => n => m => f(f, m ,n)const factorial = currying(fact)()factorial(6) // => 720

最终达到了目的,得到了独参函数factorial。

Lambda演算

在Lambda演算中的所有函数都是匿名的,它们没有名称,它们只接受一个输入变量,即独参函数。

构建一个高阶函数,它接受一个函数作为参数,并让这个函数将自身作为参数调用其自身:

const invokeWithSelf = f => f(f)

用Lambda演算写出递归栗子:

const fact = f => (total = 1) => n => n === 1 ? total : f(f)(n * total)(n - 1)const factorial = fact(fact)()factorial(6) // => 720

黑魔法Y组合子

什么是Y组合子?

Y = λf.(λx.f(xx))(λx.f(xx))

η-变换后的写法:

Y = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))

用ES6箭头函数写出lambda演算Y组合子

const Y = f =>(x => f(v => x(x)(v)))(x => f(v => x(x)(v)))

Y组合子推导

以匿名函数递归开始

const fact = f => (total = 1) => n => n === 1 ? total : f(f)(n * total)(n - 1)const factorial = fact(fact)()factorial(6) // => 720

上面代码有一种模式被重复了三次, f(f) 两次, fact(fact) 一次。为了让代码更加 DRY ,尝试把 f(f) 解耦,当作参数传递。

const fact = f => (g => (total = 1) => n => n === 1 ? total : g(n * total)(n - 1))(f(f))const factorial = fact(fact)()factorial(6) // => Maximum call stack size exceeded

当然上面代码运行结果会栈溢出,因为 JavaScript 中参数是 按值传递 的,形参必须先求值再作为实参传入函数,f(f) 作为参数传递时,会无限递归调用自身,导致栈溢出。这时候就需要用到 lambda 演算中的 η-变换。其原理是用到了惰性求值。

η-变换

什么是η-变换?如果两个函数对于任意的输入都能产生相同的行为(即返回相同的结果),那么可以认为这两个函数是相等的。

lambda演算中有效的η-变换f = λx.(fx)

JavaScript中的η-变换f = x => f(x)

根据η-变换f(f) 作为函数代入,等价于 x => f(f)(x)

const fact = x => (f => (total = 1) => n => n === 1 ? total : f(n * total)(n - 1))(v => x(x)(v))const factorial = fact(fact)()factorial(6) // => 720

抽离共性

也许你也已经发现f => (total = 1) => n => n === 1 ? total : f(n * total)(n - 1)这就是柯里化后的递归方法。抽离出 fact 方法。

const fact = f => (total = 1) => n => n === 1 ? total : f(n * total)(n - 1)const factorial = (x => fact((v => x(x)(v))))(x => fact((v => x(x)(v))))()factorial(6) // => 720

构建Y

将具名 fact 函数变为匿名函数,构建一个工厂函数 Y,将 fact 函数作为参数传入。

const fact = f => (total = 1) => n => n === 1 ? total : f(n * total)(n - 1)const Y = f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v))) // 瞧,这不就是黑魔法Y组合子嘛const factorial = Y(fact)()factorial(6) // => 720

用Y组合子实现的匿名递归函数,它不仅适用于阶乘函数的递归处理,任意递归工厂函数经过Y函数后,都能得到真正的递归函数。


沿途风景

斐波那契数列

在数学上,斐波那契数列是以递归的方法定义的:

用文字来说:就是斐波那契数列由0和1开始,之后的斐波那契系数就由之前的两数加和。

0,1,1,2,3,5,8,13,21,34,55,89,144,233......

用JavaScript递归实现:

// 非尾递归
function fibonacci (n) {if ( n <= 1 ) return 1;return fibonacci(n - 1) + fibonacci(n - 2);
}fibonacci(6) // 13

使用尾调用优化的斐波那契数列

// 尾递归写法
function fibonacci (n , before , after) {if( n <= 1 ) return before;return fibonacci (n - 1, after, before + after);
}fibonacci(6, 1, 2) // 13

使用lambda表达式的斐波那契数列

// ES6 lambda calculus
const Y = f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v)))const fibonacci = Y(f => (n) => n <= 1 ? 1 : f(n - 1) + f(n - 2)
)fibonacci(6) // 13

德罗斯特效应

在生活中,德罗斯特效应(Droste effect)是递归的一种视觉形式,指一张图片部分与整张图片相同,一张有德罗斯特效应的图片,在其中会有一小部分是和整张图片类似。 而这小部分的图片中,又会有一小部分是和整张图片类似,以此类推,……。德罗斯特效应的名称是由于荷兰著名厂牌德罗斯特(Droste) 可可粉的包装盒,包装盒上的图案是一位护士拿着一个有杯子及纸盒的托盘,而杯子及纸盒上的图案和整张图片相同

总结

我在做repository-tree项目的过程中学习到了很多之前没有接触过的东西,这也是我的初衷,想到各种各样的idea,去想办法实现它,过程中自然会提升自己的见识。以此篇博文激励自己继续学习下去。

参考

Lambda演算

JS 函数式编程指南

《ECMAScript 6 入门》

康托尔、哥德尔、图灵——永恒的金色对角线

原文

ES6函数与Lambda演算

ES6函数与Lambda演算相关推荐

  1. Lambda演算学习笔记

    前言 blog好久没有更新了,上次更新还是4月28号.这段时间实在是很忙,4月的最后一周为了赶一篇论文,累死累活,最后在tom的帮助下总算在4月30号截稿之前完成了.4月29号的晚上一直改到了第二天凌 ...

  2. 了解一下ES6: 函数简述深浅拷贝

    标准开头 今天我们来看一下ES6的函数部分知识 函数 函数初始值 有时候,函数的非必填参数,我们可以给予其默认值.保证程序完整不会出错 在早期,我们赋初始值可能是这样做的: // 早期ES5方法 fu ...

  3. 【逻辑与计算理论】Lambda 演算的类型与其 Lambda 演算建模

    Lambda演算的类型 我们已经掌握了直觉逻辑(Intuitionistic Logic,IL), -------------------------------------------------- ...

  4. 【逻辑与计算理论】Lambda 演算——开篇

    原文来自Good Math/Bad Math的系列连载,全文分7章,本篇是第1章.中文博客负暄琐话对这个系列的前6章做过翻译,强迫症表示忍受不了「下面没有了」,于是自己动手做了全套.这里只对原文做了翻 ...

  5. “后序遍历二叉运算树进行Lambda演算的化简”带来的联系

    今天闲来无事,想到一个自以为绝妙的想法,那就是用后序遍历二叉树Lambda演算的化简. 数据结构与算法中,我们想写个计算器就必须遇到一个问题,表达式求值!其实表达式很多就是我们所谓的现实生活中的问题解 ...

  6. 图灵机的逻辑等价形式——lambda演算简介

    译者述 才疏学浅,非数学专业,翻译尽量尊重原文,如有纰漏,海涵. 论文摘要 这篇论文是一篇简短易懂的lambda演算介绍.λ-calculus(lambda演算)是Alonzo Church开创,最初 ...

  7. JavaScript ES6函数:优点

    by Bhuvan Malik 通过布凡·马利克(Bhuvan Malik) JavaScript ES6函数:优点 (JavaScript ES6 Functions: The Good Parts ...

  8. 阮一峰老师的JavaScript标准参考教程:函数和ES6函数的拓展

    函数 1. 概述 函数的声明 JavaScript 有三种声明函数的方法. (1)function 命令 function命令声明的代码区块,就是一个函数.function命令后面是函数名,函数名后面 ...

  9. λ演算(lambda演算)原理通俗易懂的详细总结

    λ演算 中午我做了一个梦,梦里我写了一篇叫做"λ演算"的文章,我怕忘了所以一边做梦一边写了下来.所以下面的内容全部都是胡扯,一个字都别信. 总有文章会选择在开篇说一段废话 λ演算本 ...

最新文章

  1. SQL Server 2008中SQL增强之三:Merge(在一条语句中使用Insert,Update,Delete)
  2. JavaWeb黑马旅游网-学习笔记09【旅游线路收藏】
  3. linux as3.0 sendmail SMTP 验证 成功总结
  4. project 2013 显示标题
  5. arduino 光控灯_Arduino光控开关
  6. 【SSH网上商城项目实战01】整合Struts2、Hibernate4.3和Spring4.2
  7. 相当于jQuery .hide()来设置可见性:隐藏
  8. oracle里面的锁,基于oracle中锁的深入理解
  9. 【Android】1.开发环境搭建
  10. Docker概述 官方文档 Google翻译
  11. 556. 下一个更大元素 III
  12. unity游戏开发需要学什么?
  13. html 表格双击事件,bootstrap table onDblClickCell双击单元格事件
  14. Photoshop透明婚纱照抠图处理
  15. a标签中herf的用法
  16. 这个春天,邀你一起探寻AI与青春的碰撞之力
  17. 基于视词袋模型的场景识别
  18. 进程池(multiprocess.Pool)
  19. K12教育小初高各个版本教材内的章节数据
  20. Angular 组件类测试

热门文章

  1. 漫画:程序员带娃日常(2)
  2. 云时代架构之荔枝架构实践与演进历程
  3. 【观察】多地国税局升级华为存储,“降税减负”服务国计民生
  4. java计算机毕业设计在线商城系统源码+mysql数据库+系统+lw文档+部署
  5. linux实现表格数据的转置
  6. 数据分析之excel(一)快捷键/绝对,相对引用/替换查找和日期函数
  7. Python 如何让打印内容变得优雅(颜色打印)
  8. maskrcnn-benchmark-master推断过程
  9. “315晚会”三十而立,何时“不惑”?
  10. 【Kay】1 数据仓库简介