如何读懂并写出装逼的函数式代码

今天在微博上看到了 有人分享了下面的这段函数式代码,我把代码贴到下面,不过我对原来的代码略有改动,对于函数式的版本,咋一看,的确令人非常费解,仔细看一下,你可能就晕掉了,似乎完全就是天书,看上去非常装逼,哈哈。不过,我感觉解析那段函数式的代码可能会一个比较有趣过程,而且,我以前写过一篇《函数式编程》的入门式的文章,正好可以用这个例子,再升华一下原来的那篇文章,顺便可以向大家更好的介绍很多基础知识,所以写下这篇文章。

先看代码

这个代码平淡无奇,就是从一个数组中找到一个数,O(n)的算法,找不到就返回 null。

下面是正常的 old-school 的方式。不用多说。

//正常的版本
function find (x, y) {for ( let i = 0; i < x.length; i++ ) {if ( x[i] == y ) return i;}return null;
}let arr = [0,1,2,3,4,5]
console.log(find(arr, 2))
console.log(find(arr, 8))

结果到了函数式成了下面这个样子(好像上面的那些代码在下面若影若现,不过又有点不太一样,为了消掉if语言,让其看上去更像一个表达式,动用了 ? 号表达式):

//函数式的版本
const find = ( f => f(f) ) ( f =>(next => (x, y, i = 0) =>( i >= x.length) ?  null :( x[i] == y ) ? i :next(x, y, i+1))((...args) =>(f(f))(...args)))let arr = [0,1,2,3,4,5]
console.log(find(arr, 2))
console.log(find(arr, 8))

为了讲清这个代码,需要先补充一些知识。

Javascript的箭头函数
首先先简单说明一下,ECMAScript2015 引入的箭头表达式。箭头函数其实都是匿名函数,其基本语法如下:

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression

 // 等于 :  => { return expression; } 

// 只有一个参数时,括号才可以不加:
(singleParam) => { statements }
singleParam => { statements }

//如果没有参数,就一定要加括号:
() => { statements }
下面是一些示例:

var simple = a => a > 15 ? 15 : a; 
simple(16); // 15
simple(10); // 10let max = (a, b) => a > b ? a : b;// Easy array filtering, mapping, ...var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b);  // 66
var even = arr.filter(v => v % 2 == 0); // [6, 0, 18]
var double = arr.map(v => v * 2);       // [10, 12, 26, 0, 2, 36, 46]

看上去不复杂吧。不过,上面前两个 simple 和 max 的例子都把这箭头函数赋值给了一个变量,于是它就有了一个名字。有时候,某些函数在声明的时候就是调用的时候,尤其是函数式编程中,一个函数还对外返回函数的时候。比如下在这个例子:

function MakePowerFn(power) {return function PowerFn(base) {return Math.pow(base, power);} 
}

power3 = MakePowerFn(3); //制造一个X的3次方的函数
power2 = MakePowerFn(2); //制造一个X的2次方的函数

console.log(power3(10)); //10的3次方 = 1000
console.log(power2(10)); //10的2次方 = 100
其实,在 MakePowerFn 函数里的那个 PowerFn 根本不需要命名,完全可以写成:

function MakePowerFn(power) {return function(base) {return Math.pow(base, power);} 
}

如果用箭头函数,可以写成:

MakePowerFn = power  => {return base => {return Math.pow(base, power);} 
}

我们还可以写得更简洁(如果用表达式的话,就不需要 { 和 }, 以及 return 语句 ):

MakePowerFn = power => base => Math.pow(base, power)
我还是加上括号,和换行可能会更清楚一些:

MakePowerFn = (power) => ((base) => (Math.pow(base, power))
)

好了,有了上面的知识,我们就可以进入一个更高级的话题——匿名函数的递归。

匿名函数的递归
函数式编程立志于用函数表达式消除有状态的函数,以及for/while循环,所以,在函数式编程的世界里是不应该用for/while循环的,而要改用递归(递归的性能很差,所以,一般是用尾递归来做优化,也就是把函数的计算的状态当成参数一层一层的往下传递,这样语言的编译器或解释器就不需要用函数栈来帮你保存函数的内部变量的状态了)。

好了,那么,匿名函数的递归该怎么做?

一般来说,递归的代码就是函数自己调用自己,比如我们求阶乘的代码:

function fact(n){return n==0 ? 1 :  n * fact(n-1);
};
result = fact(5);

在匿名函数下,这个递归该怎么写呢?对于匿名函数来说,我们可以把匿名函数当成一个参数传给另外一个函数,因为函数的参数有名字,所以就可以调用自己了。 如下所示:

function combinator(func) {func(func);
}

这个是不是有点作弊的嫌疑?Anyway,我们再往下,把上面这个函数整成箭头函数式的匿名函数的样子。

(func) => (func(func))
现在你似乎就不像作弊了吧。把上面那个求阶乘的函数套进来是这个样子:

首先,先重构一下fact,把fact中自己调用自己的名字去掉:

function fact(func, n) {return n==0 ? 1 :  n * func(func, n-1);
}fact(fact, 5); //输出120

然后,我们再把上面这个版本变成箭头函数的匿名函数版:

var fact = (func, n) => ( n==0 ? 1 : n * func(func, n-1) )
fact(fact, 5)
这里,我们依然还要用一个fact来保存这个匿名函数,我们继续,我们要让匿名函数声明的时候,就自己调用自己。

也就是说,我们要把

(func, n) => ( n==0 ? 1 : n * func(func, n-1) )
这个函数当成调用参数,传给下面这个函数:

(func, x) => func(func, x)
最终我们得到下面的代码:

( (func, x) => func(func, x) ) (  //函数体(func, n) => ( n==0 ? 1 :  n * func(func, n-1) ), //第一个调用参数5 //第二调用参数
);

好像有点绕,anyway, 你看懂了吗?没事,我们继续。

动用高阶函数的递归
但是上面这个递归的匿名函数在自己调用自己,所以,代码中有hard code的实参。我们想实参去掉,如何去掉呢?我们可以参考前面说过的那个 MakePowerFn 的例子,不过这回是递归版的高阶函数了。

HighOrderFact = function(func){return function(n){return n==0 ? 1 : n * func(func)(n-1);};
};

我们可以看,上面的代码简单说来就是,需要一个函数做参数,然后返回这个函数的递归版本。那么,我们怎么调用呢?

fact = HighOrderFact(HighOrderFact);
fact(5);

连起来写就是:

HighOrderFact ( HighOrderFact ) ( 5 )

但是,这样让用户来调用很不爽,所以,以我们一个函数把 HighOrderFact ( HighOrderFact ) 给代理一下:

fact = function ( hifunc ) {return hifunc ( hifunc );
} (//调用参数是一个函数function (func) { return function(n){return n==0 ? 1 : n * func(func)(n-1);};}
);

fact(5); //于是我们就可以直接使用了
用箭头函数重构一下,是不是简洁了一些?

fact = (highfunc => highfunc ( highfunc ) ) (func => n =>  n==0 ? 1 : n * func(func)(n-1)
);

上面就是我们最终版的阶乘的函数式代码。

回顾之前的程序
我们再来看那个查找数组的正常程序:

//正常的版本
function find (x, y) {for ( let i = 0; i < x.length; i++ ) {if ( x[i] == y ) return i;}return null;
}

先把for干掉,搞成递归版本:

function find (x, y, i=0) {if ( i >= x.length ) return null;if ( x[i] == y ) return i;return find(x, y, i+1);
}

然后,写出带实参的匿名函数的版本(注:其中的if代码被重构成了 ?号表达式):

( (func, x, y, i) => func(func, x, y, i) ) (  //函数体(func, x, y, i=0) => (i >= x.length ?  null :x[i] == y  ?  i : func (func, x, y, i+1)), //第一个调用参数arr, //第二调用参数2 //第三调用参数
)

最后,引入高阶函数,去除实参:

const find = ( highfunc => highfunc( highfunc ) ) (func => (x, y, i = 0) => (i >= x.length ?  null :x[i] == y  ?  i : func (func) (x, y, i+1))
);

注:函数式编程装逼时一定要用const字符,这表示我写的函数里的状态是 immutable 的,天生骄傲!

再注:我写的这个比原来版的那个简单了很多,原来版本的那个又在函数中套了一套 next, 而且还动用了不定参数,当然,如果你想装逼装到天上的,理论上来说,你可以套N层,呵呵。

现在,你可以体会到,如此逼装的是怎么来的了吧?。

其它
你还别说这就是装逼,简单来说,我们可以使用数学的方式来完成对复杂问题的描述,那怕是递归。其实,这并不是新鲜的东西,这是Alonzo Church 和 Haskell Curry 上世纪30年代提出来的东西,这个就是 Y Combinator 的玩法,关于这个东西,你可以看看下面两篇文章:

《The Y Combinator (Slight Return)》,

《Wikipedia: Fixed-point combinator》

(全文完)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/277202.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iView 实战系列教程(21课时)_2.iView 实战教程之导航、路由、鉴权篇

在c盘创建一个iview-router的项目、然后使用默认的配置跳过添加vue-router的插件编译我们的文件。编译好之后&#xff0c;我们启动App默认的页面就打开了。默认两个路由一个是about界面一个是home我们使用编辑器打开代码&#xff0c;用我们的iview的menu组件替换掉这两个路由在…

计算机专业单元测试卷答案,银保监会考试题库:计算机类模拟试题练习(六)答案...

2020银保监会招聘考试即将开始。根据往年经验&#xff0c;银保监会招聘考试科目包括行测、申论和专业科目共三科&#xff0c;专业科目是考试提分的关键&#xff0c;那么如何才能提升专业科目的做题速度并快速提分呢&#xff1f;别担心&#xff0c;中公金融人小编根据历年的考试…

Ubuntu安装Nginx

在Ubuntu下安装Nginx有以下方法&#xff0c;但是如果想要安装最新版本的就必须下载源码包编译安装。 一、基于APT源安装 sudo apt-get install nginx 安装好的文件位置&#xff1a; /usr/sbin/nginx&#xff1a;主程序 /etc/nginx&#xff1a;存放配置文件 /usr/share/nginx&am…

HTML标题h,HTML H标题标签

可以将HTML标题或HTML h标签定义为要在网页上显示的标题或副标题。当你将文本放在标题标签……… h1>内时, 它在浏览器中以粗体显示, 并且文本的大小取决于标题的数量。从到标签定义了六个不同的HTML标题, 从最高级别h1(主标题)到最低级别h6(最重要的标题)。h1是最大的标题标…

Android 多线程之几个基本问题

Android中的进程和线程 Android中的一个应用程序一般就对应着一个进程&#xff0c;多进程的情况可以参考Android 多进程通信之几个基本问题 Android中更常见的是多线程的情况&#xff0c;一个应用程序中一般都有包括UI线程等多个线程。Android中规定网络访问必须在子线程中进行…

Web下的整体测试

随着Internet的日益普及&#xff0c;现在基于B/S结构的大型应用越来越多&#xff0c;可如何对这些应用进行测试成为日益迫切的问题。有许多测试人员来信问我B/S的测试如何做&#xff0c;由于工作较繁忙&#xff0c;对大家提出的问题也是头痛医头脚痛医脚&#xff0c;没有对WEB的…

一步一步SharePoint 2007之五:向网站中添加一个子网站

一步一步SharePoint 2007之五&#xff1a;向网站中添加一个子网站摘要感受完看到成果的激动&#xff0c;感受完邻家女孩的漂亮、可爱和端庄&#xff0c;不要停止&#xff0c;来&#xff0c;让我们一起来动手打造心目中的完美女神吧&#xff01;本篇文章将介绍如何向一个网站中添…

微型计算机系统分为哪几个层次,计算机系统分为哪4层?

满意答案al053192014.06.23采纳率&#xff1a;49% 等级&#xff1a;12已帮助&#xff1a;7516人第一层&#xff1a;物理层(PhysicalLayer)&#xff0c;规定通信设备的机械的、电气的、功能的和过程的特性&#xff0c;用以建立、维护和拆除物理链路连接。具体地讲&#xff0c…

ASP.NET Core 基础教程 - ASP.NET Core 基础教程 - 简单教程,简单编程

原文:ASP.NET Core 基础教程 - ASP.NET Core 基础教程 - 简单教程&#xff0c;简单编程 ASP.NET Core 是对 ASP.NET 有重大意义的一次重新设计。本章节我们将介绍 ASP.NET Core 中的一些新的概念和它们是如何帮助我们开发现代化的 Web 应用程序 尽管 ASP.NET Core 是跨平台的&a…

ASP.NET Core Windows 环境配置 - ASP.NET Core 基础教程 - 简单教程,简单编程

原文:ASP.NET Core Windows 环境配置 - ASP.NET Core 基础教程 - 简单教程&#xff0c;简单编程 ASP.NET Core Windows 环境配置 ASP.NET Core 是对 ASP.NET 有重大意义的一次重新设计。本章节我们将介绍 ASP.NET Core 中的一些新的概念和它们是如何帮助我们开发现代化的 Web 应…

go 中gcc 编译问题(gcc.exe fatal error no input files compilation terminated)

2019独角兽企业重金招聘Python工程师标准>>> 问题背景 在windows 中编译 go 的pipe时由于pipe依赖sqlite&#xff0c;需要通过cgo进行编译。出现如下异常 gcc.exe fatal error no input files compilation terminated 问题分析 由于 windows中缺少c 的编译环境 解决…

安装mysql-connector-python-8.0.11-py3.6遇到问题

1.提示“This application requires Visual Studio 2015 Redistributable” 下载vc_redist.x86安装即可。 下载链接&#xff1a;https://download.microsoft.com/download/6/D/F/6DF3FF94-F7F9-4F0B-838C-A328D1A7D0EE/vc_redist.x86.exe https://download.microsoft.com/downl…

画闭合的多边形 - HTML5 Canvas 作图

10、function DrawPolygon(Canvas,P)功能&#xff1a;画闭合的多边形参数&#xff1a;P是一个包含各个顶点坐标的数组实例&#xff1a;<html><script type"text/javascript" src"bigengineer.js"></script><body><canvas id&qu…

计算机网络硬件的作用是什么,网络技术在计算机软硬件的作用

网络技术在计算机软硬件的作用电子信息工程是一种建立在计算机系统软件技术和计算机网络技术上的工程,在现代国防、科研和通讯等领域发挥着巨大的作用。下面是小编搜集整理的相关内容的论文&#xff0c;欢迎大家阅读参考。摘要&#xff1a;随着我国经济的飞速发展&#xff0c;科…

VS历程简单记录

当时一开始装VS2015&#xff0c;是用了很长时间了。装了之后是可以用的&#xff0c;打了不少代码的。 后来很长一段时间没用它了&#xff0c;时隔良久&#xff0c;双击快捷方式竟然打开不了&#xff0c;让我“重装”。 其实也不用重装&#xff0c;本机用的Win10&#xff0c;去控…

智能文件名排序

默认排序问题 windows排序 Windows的资源管理中&#xff0c;提供了文件名的智能排序功能&#xff0c;可以识别出文件名中数字&#xff08;数字位数不相同&#xff09;&#xff0c;然后比较数字大小进行排序&#xff0c;如下图&#xff1a; 代码默认排序 但在C#中的列表排序中则…

VS2005 Web Application Project启用WSE(Ver 3.0)的方法

使用过WSE的朋友都知道&#xff0c;若要启用Server端Projects的WSE功能&#xff0c;需要选中如下所示的两个单选框:Enable this project for Web Services Enhancements和Enable Microsoft Web Services Enhancement Soap Protocol Factory.但是在WSE3.0中对于Web Application …

计算机英语课程背景,专家讲座第十五讲:信息化背景下高质量大学英语课程建设与教学设计...

11月27日下午&#xff0c;南京大学王海啸教授应邀到我院开展题为“信息化背景下高质量大学英语课程建设与教学设计”学术讲座。讲座由外国语言文学学院胡元江副院长和鲍贵教授共同主持&#xff0c;学院全体教师和研究生参加了本次学术活动。王海啸教授目前兼任教育部高等学校大…

ORA-28001: the password has expired

原创文章地址:https://blog.csdn.net/zdw_wym/article/details/38066745 Oracle提示错误消息 ORA-28001: the password has expired&#xff0c;是由于Oracle11G的新特性所致&#xff0c;Oracle11G创建用户时缺省密码过期限制是180天&#xff08;即6个月&#xff09;&#xff0…

使用c#操作IBM WebSphere MQ

IBM WebSphere MQ 5.3升级到CSD05之后&#xff0c;提供了使用.net操作MQ的类库&#xff0c;安装完成之后&#xff0c;会在MQ的安装目录的bin文件夹下面多出一个amqmdnet.dll文件&#xff0c;把这个DLL作为引用添加到你的.net工程中&#xff0c;你的.net程序中就可以操作IBM Web…