博客
关于我
函数式编程思维在三行代码情书中的应用
阅读量:543 次
发布时间:2019-03-07

本文共 4882 字,大约阅读时间需要 16 分钟。

42:9



函数式编程概述

如今主流的编程语言,函数式编程范式或多或少都融入其中成了“标配”,或者说主流语言都在进行函数式方面的扩充,这是一个大趋势。以Java为例,随着 Lambda块Stream API 等这种高阶函数的加持,Java总算是装备了函数式这一利器;博大精深的C++也在2011版的语言标准里加入了Lambda块的支持;再比如前一段时间我初步体验了的 ,虽然其运行于JVM之上,然而其对 动态语言函数式编程范式 以及 元编程功能 的加持所带来的表现力和简洁性可以说甩了Java几条街,可以利用Groovy的所有动态功能构建高性能的JVM应用、将开发效率提高几个数量级。语言的例子有很多,我不一一枚举。

注: 本文首发于 My 公众号 CodeSheep ,可 长按扫描 下面的 小心心 来订阅 ↓ ↓ ↓

CodeSheep · 程序羊



为什么要使用函数式编程范式

这里讲几个函数式编程的典型特点,区别的对象那就是传统的命令式编程

命令式编程 VS 函数式编程

  • 0x01. 更高层次的抽象(高阶函数)

用高阶抽象来取代基本的控制结构本身就是一个全新的思考方式,这样可以让开发者聚焦精力于业务场景而无需费心复杂地层运作

举个栗子:将一个字符串集合中的所有单词转为大写,我们用Java语言来实现

如果按照传统的命令式编程的解法,那接下来不出意外我们得来写循环、遍历这种迭代操作了:

for (int i=0; i

但如果使用Java的函数式编程范式,一切都是那么的优雅,一句话搞定

wordList.stream.map( w -> w.toUpperCase() )

这里的map()函数就是所谓的高阶函数,我们用高阶函数代替了底层的迭代,因为我们并没有处理细节,我们仅仅定义了映射的逻辑,迭代由高阶函数来自动完成!

  • 0x02. 提升代码信噪比(简洁性)

区别于面向对象语言用抽象来封装不确定因素,函数式编程通过尽量减少不确定因素来使代码极度简洁

上面的例子对于本条优点的展现我想应该也不必多说了

  • 0x03. 控制权转交于运行时(动态性)

区别于传统的编译形语言,配备函数式编程范式的动态语言更多的将控制权转交到语言运行时手里,获得的则是更高的灵活性、表现力和性能权衡。

这三点优点将在接下来的例子中切实的感受并领会!



函数式编程例析

举例1:词频统计

做的事情很简单:给定一个单词集合,统计出集合中除了助词(如ofonthe等)之外的单词出现的频次,不区分大小写

命令式解法: 至少分为以下几大步

  • 先进行循环迭代
  • 然后统一将单词转为小写
  • 然后判断单词是否是助词
  • 最后进行词频统计
public class WordCount {       // 定义一个助词集合,这些单词不参与计数    private Set
auxiliaryWordSet = new HashSet
() { { add("of"); add("the"); add("to"); add("and"); add("so"); add("are"); }}; // 传统命令式解法实现的词频统计函数 public Map doWordCount( List
context ) { Map
result = new HashMap
(); for ( String word:context ) { // 循环迭代 String lowerCaseWord = word.toLowerCase(); // 将单词统一转换为小写 if( !auxiliaryWordSet.contains(lowerCaseWord) ) { if( null == result.get(lowerCaseWord) ) result.put( lowerCaseWord, 1 ); else result.put( lowerCaseWord, result.get(lowerCaseWord)+1 ); } } return result; } // main() 函数 public static void main(String[] args) { List
wordList = new ArrayList
() { { add("The"); add("Products"); add("of"); add("Samsung"); add("and"); add("Apple"); add("are"); add("so"); add("amazing"); add("especially"); add("Apple"); }}; WordCount wordCount = new WordCount(); Map res = wordCount.doWordCount( wordList ); System.out.print(res); // 打印:{apple=2, amazing=1, samsung=1, especially=1, products=1} }}

函数式解法:

如果我们用Java的Stream API和Lambda块所构成的函数式范式来重写 doWordCount() 函数,一切都将如此简洁:

public Map doWordCount2( List
context ) { Map
result = new HashMap
(); context.stream().map( w -> w.toLowerCase() ) .filter( w -> !auxiliaryWordSet.contains(w) ) .forEach( w -> result.put( w, result.getOrDefault(w,0) + 1 ) ); return result;}

备注:这里的getOrDefault是Java的Map提供的一个便利函数,意思是:在Map中若没有找到给定的key时,返回一个“默认值”

对比命令式解法,用户省去了很多繁琐的迭代和判断,我们只讲焦点聚焦在业务逻辑之上,代码信噪比提升不小吧!


举例2:连词成句

给定一个离散的单词集合,我们想将字母数大于1的单词的首字母大写后,用 短横线- 连接起来成为一个句子

命令式解法:

public class WordConnect {       // 将单词的首字母大写    public String capitalizeFirstLetter( String s ) {        return s.substring(0,1).toUpperCase() + s.substring(1,s.length() );    }    // 连词成句    public String connectWord( List
context ) { StringBuilder result = new StringBuilder(); for ( String word: context ) { if ( word.length() > 1 ) { result.append( capitalizeFirstLetter(word) ); result.append("-"); } } return result.substring(0,result.length()-1).toString(); } // main()函数 public static void main(String[] args) { List
wordList = new ArrayList
() { { add("The"); add("Products"); add("of"); add("Samsung"); add("and"); add("Apple"); add("are"); add("so"); add("amazing"); add("especially"); add("Apple"); }}; WordConnect wordConnect = new WordConnect(); String res = wordConnect.connectWord( wordList ); System.out.print(res); // 打印:The-Products-Of-Samsung-And-Apple-Are-So-Amazing-Especially-Apple }}

函数式解法1: Java Steam API 和 Lambda块实现

public String connectWord( List
context ) { return context.stream().filter( w -> w.length()>1 ) .map( w -> capitalizeFirstLetter(w) ) .collect( Collectors.joining("-") );}

我什么都不想说了,这不要太简洁好吧!

函数式解法2: Groovy语言实现

public String connectWord( context ) {    context.findAll { it.length() >1 }    .collect { it.capitalize() }    .join '-'}

关于Groovy语言的初体验,可以参考我的文章:



函数式最佳实践:高效编写三行情书

还记得去年的520,为了表达心中对于老婆无限的、无法表达的爱,我想写一封不超过三行的代码情书,我更想用尽可能短的代码来尽可能多地表达,于是我选择了函数式编程。

我的520三行代码情书在此:

public TimeRiver timeFlow( List
days ) { return (TimeRiver)days.stream() .filter( n->theDaysNotWithYou(n) ) .map( e->accompanyByMyLove(e) ) .collect( Collectors.joining("❤️") );}

我的520三行代码情书



后记

文中提到的Groovy动态编程语言,作者体验过一点,可以参考:

如果有兴趣,也来看看作者一些关于容器化、微服务化方面的文章:

作者更多的原创文章:


长按扫描 下面的 小心心 来订阅 CodeSheep,获取更多 务实、能看懂、可复现的 原创文 ↓↓↓

CodeSheep

转载地址:http://xwinz.baihongyu.com/

你可能感兴趣的文章
nginx配置域名和ip同时访问、开放多端口
查看>>
Nginx配置多个不同端口服务共用80端口
查看>>
Nginx配置好ssl,但$_SERVER[‘HTTPS‘]取不到值
查看>>
Nginx配置如何一键生成
查看>>
Nginx配置实例-负载均衡实例:平均访问多台服务器
查看>>
Nginx配置文件nginx.conf中文详解(总结)
查看>>
Nginx配置负载均衡到后台网关集群
查看>>
ngrok | 内网穿透,支持 HTTPS、国内访问、静态域名
查看>>
NHibernate学习[1]
查看>>
NHibernate异常:No persister for的解决办法
查看>>
NIFI1.21.0_Mysql到Mysql增量CDC同步中_日期类型_以及null数据同步处理补充---大数据之Nifi工作笔记0057
查看>>
NIFI1.21.0_NIFI和hadoop蹦了_200G集群磁盘又满了_Jps看不到进程了_Unable to write in /tmp. Aborting----大数据之Nifi工作笔记0052
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表多表增量同步_增删改数据分发及删除数据实时同步_通过分页解决变更记录过大问题_02----大数据之Nifi工作笔记0054
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表或全表增量同步_实现指定整库同步_或指定数据表同步配置_04---大数据之Nifi工作笔记0056
查看>>
NIFI1.23.2_最新版_性能优化通用_技巧积累_使用NIFI表达式过滤表_随时更新---大数据之Nifi工作笔记0063
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_根据binlog实现数据实时delete同步_实际操作04---大数据之Nifi工作笔记0043
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_配置binlog_使用处理器抓取binlog数据_实际操作01---大数据之Nifi工作笔记0040
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_配置数据路由_实现数据插入数据到目标数据库_实际操作03---大数据之Nifi工作笔记0042
查看>>
NIFI从MySql中离线读取数据再导入到MySql中_03_来吧用NIFI实现_数据分页获取功能---大数据之Nifi工作笔记0038
查看>>
NIFI从MySql中离线读取数据再导入到MySql中_无分页功能_02_转换数据_分割数据_提取JSON数据_替换拼接SQL_添加分页---大数据之Nifi工作笔记0037
查看>>