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

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

先看代码

这个代码平淡无奇,就是从一个数组中找到一个数,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,一经查实,立即删除!

相关文章

如何打通高薪的黄金通道 成为职场金领

身在职场的你&#xff0c;是否想过有朝一日能获得百万年薪&#xff1f;最近&#xff0c;央视二套绝对挑战特别节目巅峰营销的热播&#xff0c;引发各方人士对东风日产百万年薪招兵营销总监的关注。身价百万的营销总监人人想当&#xff0c;如何才能成为这样的职场金领&#xff0…

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…

9.28PMP每日一题

控制质量过程的输出是&#xff1a;A、变更请求B、批准的变更请求C、工作绩效数据D、事业环境因素 答案将于明天和新题一起发布&#xff01;9.27试题答案&#xff1a;C转载于:https://blog.51cto.com/13554215/2287292

将数组绑定到dropdownlist上

<% Page Language"C#" %><% Import Namespace"System.Data" %><Script Language"C#" Runat"Server">public void Page_Load(Object src,EventArgs e){ //首先建立一个数组 ArrayList arrnew ArrayList(); …

HTML标题h,HTML H标题标签

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

汤姆大叔的6道javascript编程题题解

1、找出数字数组中最大的元素&#xff08;使用Math.max函数&#xff09; 123var a [1, 2, 3, 6, 5, 4];var ans Math.max.apply(null, a);console.log(ans); // 6这题很巧妙地用了apply&#xff0c;如果不是数组&#xff0c;是很多数字求最大值&#xff0c;我们知道可以这样…

Android 多线程之几个基本问题

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

Web下的整体测试

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

用计算机算算术平方根顺序是ON然后是什么,第2课时用计算器求一个正数的算术平方根.ppt...

1.比较下列各组数的大小&#xff1a;(1) 与(2) 与 8依次按键显示&#xff1a;1.732 050 808例3 小丽想用一块面积为400 cm2的正方形纸片&#xff0c;沿着边的方向剪出一块面积为300 cm2的长方形纸片&#xff0c;使它的长宽之比为3:2&#xff0e;她不知能否裁得出来&#xff0c;…

MySQL 命令

版权声明&#xff1a;本文首发 http://asing1elife.com &#xff0c;转载请注明出处。 https://blog.csdn.net/asing1elife/article/details/82892834 MySQL 一些常见命令 更多精彩 更多技术博客&#xff0c;请移步 asing1elife’s blog 查看版本号 mysql -V重启/启动/停止 mys…

Bookshelf 2 简单DFS

链接&#xff1a;https://ac.nowcoder.com/acm/contest/993/C来源&#xff1a;牛客网 题目描述 Farmer John recently bought another bookshelf for the cow library, but the shelf is getting filled up quite quickly, and now the only available space is at the top.FJ…

一步一步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…

参数初始化

通过以下形式进行参数初始化 self.fc nn.Linear(n_head * d_v, d_model) nn.init.xavier_normal_(self.fc.weight) 转载于:https://www.cnblogs.com/yeran/p/11197047.html

使用input type=file 上传文件时需注意

在asp.net的中使用<input typefile />控件上传文件对文件的大小有限制,默认情况下大概在4m左右,如果上传再大的文件时就会出页面无法显示的错误.修改web.config文件中的参数可以设置该控件上传文件的大小,web.config中配置如下:在<system.web>节点下增加"<…

html模板 循环里if,django模板里循环变量table里想要两个一行如何控制

2016-8-3 周三做项目时遇到的问题&#xff1a;每个div由循环变量输出&#xff1a;{% for key,value in formextenddetail %}{{ key }}{{ value }}{% endfor %}但是我想两个div一行&#xff0c;使用...这种样子因为我负责的是前端&#xff0c;views这些不是很熟悉&#xff0c;想…

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 应…