初识函数式编程的一点漫谈

昨天看一些函数式编程、Haskell、Scala的书和文章,到半夜还莫名其妙:

「好像除了函数是一等公民外,没发现什么好处呀……很多人说学函数式能改变思维方式,又不肯说这改变具体是什么。」

「javascript里函数也是一等公民,用着也很爽,函数式比之,还有什么好处?」

很不满意地躺下,用手机继续google之,突然发现一篇文章,醍醐灌顶,立刻爬起来仔细阅读,摸黑记录(兴奋得没工夫开灯),发微博致谢。

「函数式编程的关键是不定义变量」,这至为重要的一点,之前看的书和文章竟然都没强调——说是说过了,却没指出这是重点。

就连忙现想个功能,尝试不定义变量地实现它,发现当有了这个勒定,函数语言自然会被「倒逼」着具备lambda等特性,因为我发现只用老java,不用java8语法,很难在「不定义变量」的约束下实现之,不知道是不是不可能实现,我的数学能力不足以证明之。

今天早上起来,赶快翻开《写给大忙人看的java8》,使用java8语法完成程序。完成后重构一下,发现果然不出所料,最终代码的结构,是函数复函数,函数套函数,主函数是超级大函数,而这些函数的分隔和关联,着眼点就在其参数和返回值上。

数学里,函数的意义是定义域到值域的映射,代码里写的函数,其意义是参数集合到返回值集合的映射,为了达成这从集合到集合的映射,在不许定义变量的情况下,只有借助java8的lambda和stream才能实现。

每个函数都是从一个集合,变换到另一个集合,这样经过若干步变换,就实现了从程序的总参数,到程序的总结果的变换。

程序的意义本来就是从输入到输出的变换,输入是用户操作、网络请求、定时器等事件引起的中断,及存储器某个地方现有的数据,输出则是将一些数据写到存储器中,包括写磁盘、写显存以让屏幕显示某些图画、写数据库、发网络请求调远处接口等。可以把程序的「总输入参数」看做一个集合,「总输出结果」看做一个集合,从参数集合到结果集合的映射,就是一个「大函数」,这与函数式编程的「主函数是超级大函数」是一致的。

其实「网站」的「主函数」又何尝不是「超级大函数」呢,本质就是socket服务端的主循环,通过很复杂的策略,把「总输入」包成request对象,把「总输出」准备用response对象处置,然后路由到具体的程序上,那程序可能进一步「分治」,用框架把request的参数绑到具体处理方法的入参上,把该方法的出参用框架给到response返回,然后通过路径匹配,把各个请求给到适当的处理方法上。只是,很多时候,我们在太高层、太狭窄的上下文中工作得太久,以至于忘了自己身处一个「超级大函数」之中。

视线更大一点,计算机又何尝不是「超级大函数」呢,一通电,CPU即不停运转,这就是最大的函数,「总输入」是电能,「总输出」是电路使硬件发生的变化;在这之上一层,操作系统的主循环是次一级的「超级大函数」,「总输入」是随时监听到的硬件中断;再之上一层,才是某个进程的主循环这「大函数」,「总输入」是操作系统分配来的事件,上段说的网站服务器的socket主循环就是这种「大函数」;在这函数调用之下,Filter和Servlet这些组件的生命周期才得以运转,然后进一步才是Spring等框架的初始化,Struts或Spring等框架表现层组件的初始化和响应事件方法执行,再下一步,才是业务层,持久层,读写磁盘,读写数据库,读写API这些具体的「小函数」,小函数的输出,再经过层层包裹,成为「超级大函数」的输出的一部分,即对某处的硬件产生效果。

视线再大一点,就是宇宙大爆炸是「究极大函数」,那个瞬间一切原子的位置,是否已决定了之后的一切,这已是哲学的问题了。这个「洪荒之力」,过于究极,我们也只是这个大函数中极为渺小的一些数据啊,缘聚,就有了我,缘散,就没了我,像一滴水消失在水中,像计算机断电后,内存中的010101化为乌有,人生天地间,忽如远行客。

说远了。函数想从输入变换到输出,处理方法是当然要有的,这个方法,命令式语言着眼于「参数怎样算出结果」的流程,而函数式语言着眼于「参数与结果的对应」的关系,一个着眼于过程,一个着眼于结果,一个在厚重的书本中寻找来龙去脉,一个用搜索引擎来直指目的讯息,一个运筹帷幄,一个应变随机,一个深厚,一个扁平,一个动,一个静,一个重步骤,一个重设置,若说没奇缘,java偏又升到8,若说有奇缘,为何阵营终分化。

映射这件事,java程序员也早就熟悉啊,不论是web.xml,还是struts.xml,还是spring中bean的id到实体,还是mybatis中「函数」的id到语句,hibernate?它叫做O-R什么来着?服务器这「超级大函数」接收到的各路神仙,给分化瓦解,分而治之,你虽一路来,我却N路去,这是但凡程序员都知道的「分治」原则。顺序选择循环,顺序循环可以咬牙不要,不用选择还想达到目的的,没听说过,即使没听过「函数式」的程序员,也每天都在做把「大函数」的任务视情况分配到「小函数」来处理的工作。

那「不定义变量」的意义是什么,它的意义是,函数是纯逻辑——当然,是大逻辑套小逻辑的复杂逻辑。它不需要「知识」,即存储在存储单元中的数据。就好比形式逻辑,A为真,B为真,A且B则一定为真,至于A和B是什么,不用想。「不定义变量」从根本上避免了来自知识的污染,对程序来说,参数即是全部知识,外部知识不需要,我也没兴趣向外写,我只以这点有限的参数知识为准,返回适当的结果知识。

这个切割非常漂亮,能让程序员专注于对局部的考察,只关注当前函数这「茅庐」即可,外面洪水滔天都不在意(毕竟它们渗不进来)。显然,这阅读代码、单元测试起来,都太方便了,有限知识的纯逻辑推演,正是数学和逻辑学「天然真理」在程序世界中的表现,天然真理是干净的,不需要被证明,不可能被证伪。

但科学是「有待」的,商业程序也是「有待」的,商业程序里可能没有用全局变量装状态,但最大的状态都在用N个数据库服务器或缓存服务器的集群装着呢。数据就是知识,知识就是力量,阿尔法打败了李世石,在这真正的洪荒之力面前,纯逻辑的推演显得力不从心,互联网时代,记忆力和计算力都外包,计算力这方面函数式干净利落,但记忆力即「状态」的价值同样举足轻重啊。科学知识,早晚要被证伪,并非天理,但能指导生活;商业程序,其中含有状态,并不干净,但能帮助生活。函数式的思维,应该作为一种武器,纳入我们的兵器谱,在适当的时候取出来,破军杀敌。而不是奉为至宝,顶礼膜拜,一言不合,出言不逊。这才是一个客观务实的态度。

以上是初识函数式编程的一点漫谈,没有限于问题本身,「漫溢」了,不过作为散文,而非论文,「通感移觉」这种「使用知识」的修辞,很有效用,但虽然形散,神却不散,不知看到这里(感谢)的你,对这会点头否?雾里看花,见识有限,全是个人思考和领悟的产物,恐怕错误不少,请多指正。

最后把开头说的用java尝试函数式编程的代码贴上来吧,java8是刚学,代码挺笨的,请指教。

public static void main(String[] args) {
    //把给定的一些字符串,统计字母出现次数,结果为:
    //a:1 b:4 k:3 o:6
    System.out.println(strs2SumGroupStr("booka", "bookb", "koob"));
}
//booka,bookb... -> 'a:1 b:4..'
public static String strs2SumGroupStr(String... strs){
    return stream2SumGroupStr(strs2Stream(strs));
}
//booka,bookb... -> b,o,o,k,a,b...
public static IntStream strs2Stream(String... strs){
    return String.join("", strs).chars();
}
//b,o,o,k,a,b... -> 'a:1 b:4..'
public static String stream2SumGroupStr(IntStream is){
    return groupMap2SumGroupStr(stream2GroupMap(is));
}
//b,o,o,k,a,b... -> {a:*,b:*..}
public static Map stream2GroupMap(IntStream is){
    return is.boxed().collect(Collectors.groupingBy(i->i, Collectors.summarizingInt(i->i)));
}
//{a:*,b:*..} -> 'a:1 b:4..'
public static String groupMap2SumGroupStr(Map map){
    return groupSet2SumGroupStr(groupMap2GroupSet(map));
}
//{a:*,b:*..} -> ('a:1','b:4'..)
public static Set groupMap2GroupSet(Map map){
    return map
                .keySet()
                .stream()
                .map(k->(char)k.intValue()+":"+map.get(k).getCount())
                .collect(Collectors.toSet());
}
//('a:1','b:4'..) -> 'a:1 b:4..'
public static String groupSet2SumGroupStr(Set set){
    return set
                .stream()
                .reduce((x,y) -> x + " " + y)
                .get();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容