从历史讲起JavaScript基因里的函数式编程实例

目录
  • 本篇序言
  • 一、数学之美
  • 二、lambda 演算核心
  • 三、JavaScript 的基因

本篇序言

本瓜很喜欢看历史,读史可知兴替、使人明智,作为程序员看“技术的演替历史”同样如此。过程是越看越有味,仿佛先贤智慧的光照亮了我原本封闭的心,每每只能感叹一个“服”字。所以,专栏第一篇打算先从技术历史讲起,从函数式编程的渊源讲起。

看完本篇:

你会知道为什么有人会说 “计算机是数学家一次失败思考的产物”

你会知道为什么 “ lambda 演算定义函数有效计算”

你会知道编程概念中 “闭包最初是如何形成的”

你还会知道为什么标题要说 “JavaScript 基因里写着函数式编程”

话不多说,开冲了~ ‍‍‍

一、数学之美

函数式编程的历史最早可以追溯到 1930 年,一个叫 丘奇(Church)的人,提出了 λ (lambda)演算,这是所有函数式编程语言的基础。

那这人为啥要提出这个演算?1930 年这个时间比世界上第一台计算机诞生的时间都还要早 16 年。提出这个肯定不是因为计算机编程。

没错,他是为了解决一个数学问题。

这个数学问题是:

著名的希尔伯特第十问题—— 判定问题 (1900 年提出)

我们不妨来“浅看”一下这个数学问题,可以说这个问题促使了计算机的形成。

什么是希尔伯特第十问题之判定问题?

本瓜尝试用通俗的表达解释一下:

很简单,有下列这样一个方程:

其中所有的数(aj、bj、c)都是整数,求:能否找到一组 xj (全部为整数)的解?

乍一看这个公式有点费解。。。

其实我们可以构建一个大家都熟悉的实例,保证一看就明白了~

我敲,这不就是勾股定理吗?勾三股四弦五,老祖宗在西周时就发现了。

符合判定问题的实例方程还有很多,比如:裴蜀等式、佩尔方程、四平方和定理、以及著名的【费马大猜想】等等。

噢!希尔伯特提出判定问题,旨在“一劳永逸”,如果这个问题被解决了,那么它的子问题也都能被同样解决。所以,在 1900 年到 1930 年之间,以希尔伯特为代表的数学家们 试图构建一个自动化定理证明的系统,让公理系统内的所有命题都能用一套既定的规则得以证明或证伪。

说白了,就是这群数学家也想偷懒,证明各类数学公式已经累了,想搞点自动化的通用流程,能够用通用流程去证明或证伪数学难题。

大家都在前赴后继的尝试解决这个问题,直到 1930 年后,出现了 哥德尔、图灵、丘奇 这些人,他们几乎在同一时间,但又在不同角度对这个问题作出了解释。

更为神奇的是,最终证明他们的结论竟然是等效的。

哥德尔不完备性定理中递归函数 == 图灵完备 == lambda 演算

他们彻底解决了希尔伯特第十问题吗?

很遗憾,并没有。

不过在这个过程中,他们搞清楚了一个很重要的问题,一个对计算机科学至关重要的元核心问题:

什么样的函数是可以有效计算的?!

在这之前,数学家们对于这个问题并没有一个普遍结论,只知道一些最简单的函数,以及通过简单规则将简单函数组合起来的函数(比如加法),是可以有效计算的。

这种感觉像是无心插柳,本来大家是冲着解决数学公式论证问题去的,最后不约而同的得出了“函数可有效计算”的定义。这个定义由此发展,成为了 21 世纪最具颠覆力量的学科 —— 计算机。

所以才有人说:计算机是数学家一次失败思考的产物。

数学的局限也会造成计算机的局限。

不过依然无法掩盖数学之美,美在它足够基础,但又隐藏着巨大的能量,影响着万事万物。

二、lambda 演算核心

各位,你有想过,什么样的函数是可以有效计算的?

如果由你定义,你会从怎样的角度去思考?

  • 由于本篇重点是讲函数式编程起源的 lambda 演算,所以哥德尔和图灵的解释不作展开,在文尾有相关文章推荐,可自行了解。(尤其是图灵机,一定多看看、体会体会)

丘奇给出了它的观点:

有效计算的函数指的是:函数每一步都可被事先确定,而且该函数可在有限的步数之内生成结果。

通俗来理解,“有效”即要在有限步骤内产生确定的结果。

天才的丘奇给出了 lambda 演算表达式:

lambda x . body

其中 x 是输入的参数,body 是运算过程,意思是 x 经过 body 的运算,然后返回结果。

lambda 演算的伟大之处在于它非常简洁,揭示了计算的本质。

细看 lambda 表达式,你会发现函数只能接受一个参数,如果我们需要传两个参数呢?

其实也是能实现的,如下:

lambda x. ( lambda y. plus x y )

这就是沿用至今的函数式编程 柯里化 思想,传入参数 x,经过运算体 body:lambda y. plus x y 的运算,body 又是一个 lambda 运算表达式,入参是 y,新的运算体是 plus x y

这种简化的设计,让我们无需过多的语法,便能实现接收多个参数。

lambda 演算核心还有两条重要的规则:转换 和 规约

  • 转换

转换的意思是:变量的名称并不重要,比如以下两种写法是等效的,相当于变量名只是形参而已。

lambda x. ( lambda y. plus x y )
lambda y. ( lambda x. plus x y )
  • 规约

规约的意思是:我们可以对这个函数体中和对应函数标识符相关的部分做替换,替换方法是把标识符用参数值替换。

举个例子:

(lambda x . x + 1) 3
// 规约后
3 + 1

这样写意味着 3 是形参 x 实际的值,数值“3”可直接取代引用的参数“x”,规约后即为 3 + 1

(lambda y . (lambda x . x + y)) q
// 规约后
lambda x . x + q

首先 q 是形参 y 实际的值,规约后,实际上就是求 lambda x . x + q

规约远能做的很多变化,正是由于规约的存在,让 lambda 演算可以实现递归,才让它可以等效于图灵完备。

我们再来概括一下:

  • lambda 核心表达式:lambda x . body,简洁而优雅;
  • 入参只有一个,可以通过柯里化来实现接受多个参数;
  • lambda 演算的“规约”规则是它实现复杂运算的重要机制,由繁化简;
  • 多问一句:把函数作为 body 返回,不正是 JavaScript 高阶函数的意思吗?

可见,现在很多我们觉得稀松平常的一些用法,其实早在近 100 年前就被提出来了,不可谓不震撼。

三、JavaScript 的基因

说了半天,终于来到了我们的 JavaScript,相信大家接触 JavaScript 之初都会被“闭包”这个概念搞得有点蒙,为什么要这样设计?我平常又确实用不上,好不容易学了个防抖、节流函数,你就不要再继续追问“什么是闭包了”。

兄弟,有福了,这次带你见识最初的闭包是如何产生的!

闭包的概念,在计算机诞生之前就被设计出来了,没错,还是来源于我们的 lambda 演算。

lambda 演算规定:

如果一个标识符是一个闭合 lambda 表达式的参数,我们则称这个标识符是被绑定的;如果一个标识符在任何封闭的上下文中都没有绑定,那么它被称为自由变量。

比如:

lambda x . plus x y

在这个表达式中,x是被绑定的,因为它是函数定义的闭合表达式 plus x y 的参数。而 y 是自由变量;

再比如:

lambda y . (lambda x . plus x y)

在内层演算 lambda x . plus x y 中,x 是被绑定的,y 是自由的;而在完整表达中,x 和 y 是都是被绑定的:x 受内层绑定,而 y 由剩下的外层演算绑定。

这正是 JavaScript 闭包最初的雏形, 内部函数保持着对函数外部变量的引用。这里“被绑定的”意思就是变量不能被清理的,是以后会被用到的。

神奇吗?闭包早于计算机诞生,仿佛就像打火机早于火柴发明一样,让人有点意外~

好了,最后说一说:为什么 JavaScript 基因里写着函数式编程 ?

这一段历史,应该很多工友早烂熟于心,网景公司想给 HTML 加一个脚本语言用于改善交互,于是招来了 布兰登·艾克,这老哥 10 天就把这门语言的框架设计好了。它思想上基于 Self 语言和 Scheme 语言,语法上和 C 语言相似。

我们再看这里的 Scheme 语言,它其实就是一门堂堂正正的函数式编程语言,它是第一大函数式编程语言 Lisp (1958 年)两种方言的其中一种。而 Lisp 则来源于 lambda 演算,来源于 丘奇,来源于那个解决 1900 年数学问题的意外收获!

时间线是这样的:

  • => 1900 年希尔伯特数学问题
  • => 1930 年丘奇 lambda 演算
  • => 1958 年 Lisp 语言
  • => 1975 年 Scheme 语言
  • => 1995 年 JavaScript 语言

知道从哪里来,才能知道往哪里去。

所以,朋友们,我们现在所用的 JavaScript,基因里有一个重要的组成部分是函数式,把函数放在第一位、关注输入输出、参数柯里化、高级函数等等,在近百年里逐渐演进。前段时间,看到一篇文章,JSON 之父吐槽说:现在我们更关注于把 JavaScript 的使用规模扩大,而不是关注怎样使这门语言变得更好。然后导致他建议退役 JavaScript,我大受震撼。或许,如果某一天,ES 版本迭代关注点只有:又新增了几个语法糖,而忽略了这门语言最初的设计思想,忽略去完善它,那真有点可惜。

以上就是从历史讲起JavaScript基因里的函数式编程实例的详细内容,更多关于JavaScript基因函数式编程的资料请关注我们其它相关文章!

(0)

相关推荐

  • JavaScript的函数式编程基础指南

    引言 JavaScript是一种强大的,却被误解的编程语言.一些人喜欢说它是一个面向对象的编程语言,或者它是一个函数式编程语言.另外一些人喜欢说,它不是一个面向对象的编程语言,或者它不是一个函数式编程语言.还有人认为它兼具面向对象语言和函数式语言的特点,或者,认为它既不是面向对象的也不是函数式的,好吧,让我们先搁置那些争论. 让我们假设我们共有这样的一个使命:在JavaScript语言所允许的范围内,尽可能多的使用函数式编程的原则来编写程序. 首先,我们需要清理下脑子里那些关于函数式编程的错误观

  • 详解用函数式编程对JavaScript进行断舍离

    我和JavaScript 从1997年网景的Navigator 3浏览器开始就开始使用JavaScript.当时,JavaScript还只能做一些很简单的事情.我记得最酷的就是用JavaScript实现mouseover特性,在那个时候已经算得上是高科技了!当鼠标移过去之后,文本内容就神奇的改变了.因为当时都是pre-DHTML,你根本不需要隐藏或则显示DOM元素. 关于DHTML DHTML是Dynamic HTML的简称,就是动态的html(标准通用标记语言下的一个应用),是相对传统的静态的

  • JavaScript函数式编程Thunk原理解析

    目录 引言 举个栗子 代码演进 改造成异步 toThunk 工具函数 引言 本篇带来 Thunk 理解,这也是本瓜最津津乐道的 JS 函数式编程中延迟处理的思想核心之一! 什么是 Thunk ? 简单理解:在计算机编程中,Thunk 就是一种实现延迟执行的手段. 举个栗子 我要计算 99 的 9 次方,然后再把它打印出来,你会怎么写? 大聪明肯定是直接一句话: console.log(Math.pow(99, 9)) // 913517247483640800 有点想法的同学肯定想封装一个函数,

  • JavaScript函数式编程示例分析

    目录 函数式编程 函数柯理化(Curring) Compose 场景案例 总结 函数式编程 1.函数式编程指的是函数的映射关系 2.vue3.react16.8的函数组件推动了前端函数编程 3.必须是纯函数(幂等):同样的输入有同样的输出 //非纯函数 function getFirst1(arr){ return arr.splice(0,1); }; //纯函数 function getFirst2(arr){ return arr.slice(0,1); }; const arr = [1

  • JavaScript函数式编程实现介绍

    目录 为什么要学习函数式编程 什么是函数式编程 前置知识 函数是一等公民 函数可以储存在变量中 函数作为参数 函数作为返回值 高阶函数 什么是高阶函数 使用高阶函数的意义 常用高阶函数 闭包 纯函数 纯函数概念 纯函数的好处 副作用 柯里化 函数组合 Functor(函子) MayBe 函子 Either函子 为什么要学习函数式编程 Vue进入3.*(One Piece 海贼王)世代后,引入的setup语法,颇有向老大哥React看齐的意思,说不定前端以后还真是一个框架的天下.话归正传,框架的趋

  • JS中的一些常用的函数式编程术语

    组合 Composition 组合某种类型(含函数)的两个元素,进而生成一个该类型的新元素: JavaScript let compose = (f, g) => a => f(g(a)) let toUpperCase = x => x.toUpperCase() let exclaim = x => x + '!' let shout = compose(exclaim, toUpperCase); shout("hello world") // HELLO

  • JavaScript中的函数式编程详解

    函数式编程 函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变,与函数式编程相对的是命令式编程.我们有这样一个需求,给数组的每个数字加一: // 数组每个数字加一, 命令式编程 let arr = [1, 2, 3, 4]; let newArr = []; for(let i = 0; i < arr.length; i++){ newArr.push(arr[i] + 1); } console.log(newArr)

  • javascript函数式编程基础

    目录 一.引言 二.什么是函数式编程 三.纯函数(函数式编程的基石,无副作用的函数) 四.函数柯里化 五.函数组合 六.声明式和命令式代码 七.Point Free 八.示例应用 九.总结 一.引言 函数式编程的历史已经很悠久了,但是最近几年却频繁的出现在大众的视野,很多不支持函数式编程的语言也在积极加入闭包,匿名函数等非常典型的函数式编程特性.大量的前端框架也标榜自己使用了函数式编程的特性,好像一旦跟函数式编程沾边,就很高大上一样,而且还有一些专门针对函数式编程的框架和库,比如:RxJS.cy

  • 从历史讲起JavaScript基因里的函数式编程实例

    目录 本篇序言 一.数学之美 二.lambda 演算核心 三.JavaScript 的基因 本篇序言 本瓜很喜欢看历史,读史可知兴替.使人明智,作为程序员看“技术的演替历史”同样如此.过程是越看越有味,仿佛先贤智慧的光照亮了我原本封闭的心,每每只能感叹一个“服”字.所以,专栏第一篇打算先从技术历史讲起,从函数式编程的渊源讲起. 看完本篇: 你会知道为什么有人会说 “计算机是数学家一次失败思考的产物”: 你会知道为什么 “ lambda 演算定义函数有效计算” : 你会知道编程概念中 “闭包最初是

  • javascript函数式编程实例分析

    本文实例讲述了javascript函数式编程.分享给大家供大家参考.具体分析如下: js像其他动态语言一样是可以写高阶函数的,所谓高阶函数是可以操作函数的函数.因为在js中函数是一个彻彻底底的对象,属于第一类公民,这提供了函数式编程的先决条件. 下面给出一个例子代码,出自一本js教程,功能是计算数组元素的平均值和标准差,先列出非函数式编程的一种写法: var data = [1,1,3,5,5]; var total = 0; for(var i = 0;i < data.length;i++)

  • 从柯里化分析JavaScript重要的高阶函数实例

    目录 前情回顾 百变柯里化 缓存传参 缓存判断 缓存计算 缓存函数 防抖与节流 lodash 高阶函数 结语 前情回顾 我们在前篇 <从历史讲起,JavaScript 基因里写着函数式编程> 讲到了 JavaScript 的函数式基因最早可追溯到 1930 年的 lambda 运算,这个时间比第一台计算机诞生的时间都还要早十几年.JavaScript 闭包的概念也来源于 lambda 运算中变量的被绑定关系. 因为在 lambda 演算的设定中,参数只能是一个,所以通过柯里化的天才想法来实现接

  • JavaScript代码里的判断小结

    比较判断 比较判断是比较两个值,返回一个布尔值,表示是否满足比较条件.JavaScript一共提供了8个比较运算符,这里主要说一下严格相等运算符和相等运算符的区别 严格相等运算符=== 判断 返回 两个值类型不同 false 两个值都是null/undefined/true/false true 两个值其中之一为NaN false 两个值都为数值且值相等 true 两个值都为字符串,且值相等 true 两个值都指向同一个引用类型 true 1 === "1" // false true

  • 通过实例了解Javascript柯里化流程

    函数式编程是一种如今比较流行的编程范式,它主张将函数作为参数进行传递,然后返回一个没有副作用的函数,说白了,就是希望一个函数只做一件事情. 像Javascript,Haskell,Clojure等编程语言都支持函数式编程. 这种编程思想涵盖了三个重要的概念: 纯函数 柯里化 高阶函数 而这篇文章主要是想向大家讲清楚柯里化这个概念. 什么是柯里化 首先我们先来看一个例子: function sum(a, b, c) { return a + b + c; } // 调用 sum(1, 2, 3);

  • Javascript柯里化实现原理及作用解析

    函数式编程是一种如今比较流行的编程范式,它主张将函数作为参数进行传递,然后返回一个没有副作用的函数,说白了,就是希望一个函数只做一件事情. 像JavaScript,Haskell,Clojure等编程语言都支持函数式编程. 这种编程思想涵盖了三个重要的概念: 纯函数 柯里化 高阶函数 而这篇文章主要是想向大家讲清楚柯里化这个概念. 什么是柯里化 首先我们先来看一个例子: function sum(a, b, c) { return a + b + c; } // 调用 sum(1, 2, 3);

  • TypeScript与JavaScript项目里引入MD5校验和

    目录 一.什么是MD5校验和? 二.MD5校验和的优点和漏洞 三.如何在TS项目里引入MD5校验和? 四.MD5校验的使用 五.另一个npm依赖包的使用方法 一.什么是MD5校验和? MD5,是Message Digest Algorithm 5的缩写,即消息摘要算法版本5. 消息摘要算法通过对所有数据提取指纹信息以实现数据签名.数据完整性校验等功能,由于其不可逆性,有时候会被用做敏感信息的加密.消息摘要算法也被称为哈希(Hash)算法或散列算法.任何消息经过散列函数处理后,都会获得唯一的散列值

  • 《JavaScript函数式编程》读后感

    本文章记录本人在学习 函数式 中理解到的一些东西,加深记忆和并且整理记录下来,方便之后的复习. 在近期看到了<JavaScript函数式编程>这本书预售的时候就定了下来.主要目的是个人目前还是不理解什么是函数式编程.在自己学习的过程中一直听到身边的人说面向过程编程和面向对象编程,而函数式就非常少.为了自己不要落后于其他同学的脚步,故想以写笔记的方式去分享和记录自己阅读中所汲取的知识. js 和函数式编程 书中用了一句简单的话来回答了什么是函数式编程: 函数式编程通过使用函数来将值转换为抽象单元

  • JavaScript 函数式编程实践(来自IBM)第1/3页

    函数式编程简介 说到函数式编程,人们的第一印象往往是其学院派,晦涩难懂,大概只有那些蓬头散发,不修边幅,甚至有些神经质的大学教授们才会用的编程方式.这可能在历史上的某个阶段的确如此,但是近来函数式编程已经在实际应用中发挥着巨大作用了,而更有越来越多的语言不断的加入诸如 闭包,匿名函数等的支持,从某种程度上来讲,函数式编程正在逐步"同化"命令式编程. 函数式编程思想的源头可以追溯到 20 世纪 30 年代,数学家阿隆左 . 丘奇在进行一项关于问题的可计算性的研究,也就是后来的 lambd

  • 深入探讨javascript函数式编程

    有时,优雅的实现是一个函数.不是方法.不是类.不是框架.只是函数. - John Carmack,游戏<毁灭战士>首席程序员 函数式编程全都是关于如何把一个问题分解为一系列函数的.通常,函数会链在一起,互相嵌套, 来回传递,被视作头等公民.如果你使用过诸如jQuery或Node.js这样的框架,你应该用过一些这样的技术, 只不过你没有意识到. 我们从Javascript的一个小尴尬开始. 假设我们需要一个值的列表,这些值会赋值给普通的对象.这些对象可能包含任何东西:数据.HTML对象等等. v

随机推荐