english edition come soon
Copyright © 2018 Lin Pengcheng. All rights reserved.
版权所有 © 2018 林鹏程, 保留所有权利。
最近,在微信群里介绍纯函数管道数据流时,回群友关于构造方法的提问,整理一下,放这里。
纯函数管道数据流是我在个人项目实践过程总结创造出来的方法,基于我的知识体系,融合了集成电路、科学化管理思想、大工业流水线思想、函数式编程(FP)、统计学R语言的向量式编程等技术,在系统构造过程中的抽象、策略选择判断过程应用了科学化管理思想、大工业流水线思想来做为依据,是最契合我的思维方式的方法,不是象那些FP和OO狂信徒从外来的理论学习、沉迷、洗脑、走火入魔的状态:-)。
大海航行靠舵手,编程朝着数据走。初始状态,最终状态,两点间直线距离最短。 沿着数据变换形成的数据流,沿途汇流,直至最终状态,这叫百川东到海、结果导向、直指大道, 这种做法与波音飞机“脉动”生产线做法是一致的。天下武功,唯快不破。 简单直接是快速稳定可靠的根本。FP和OO其实全是在走弯路 :-)。
纯函数管道数据流方法是使项目纯粹由->>
块组成,一个->>
块函数相当于一个集成电路元件(或板),这样整个项目象个集成电路(或城市水网,或企业制度流程)。代码示意图化和象形化 对系统架构逻辑和流程类似所见即所得,对阅读、理解、优化很有帮助。每一步都是可测试、可验证,代码重用和组合非常的爽,逻辑也很简洁清晰,系统构造、调试、扩展、维护、使用也可以引用成熟的集成电路理论。
数据流设计时,要特别注意设立函数输入输出的数据规范标准,按大工业标准化生产模式。
因为Clojure
语言对纯函数管道数据流提供了语言级核心支持,有很多种类的->>
宏,写纯函数管道数据流最自然最方便,所以以下内容的代码用Clojure
语言表达。
1.元件:一个->>
块函数相当于一个集成电路元件(或板),利用纯函数的输入输出特性当作管道(导线)使用。
一个->>
块里面的一系列函数,最多只能有一个带副作用的函数且只能处于末尾。另外,要注意做好数据标准化工作,在出入口检查,中间就可以极限裸奔,这样做简洁、流畅、稳定、高效。
一般只有在带副作用的函数里才有try-catch
异常处理,异常处理是个垃圾机制,我不喜欢try-catch
异常机制,使用try-catch
异常机制也只是为了兼容系统外部的库、语言和平台的异常机制而已,对外来的只能接受,只能管好自己。在数据流里,一切用数据解决,可以接着向下传递数据信号表达状况,这种思想类似C语言的错误号数据传递机制。经典的计算机技术很多是很好的,比如数据结构+算法,错误号等。一帮不知所谓的家伙炒作出FP/OO/异常等一堆垃圾,为创新而创新,为paper而创新。
在clojure
语言里,建议函数尽量设计成参数为hash-map类型的单参数函数,象R
语言大多数函数那样,可以设计很多带默认值的命名参数,有很强的可扩展性。另外,clojure
操作hash-map的核心函数很多,操作方便,不仅在使用->>宏时可以不用写括号,而且参数的形成,校验,变换与函数调用一体化、一条龙数据流处理。还有clojure
解构方便,在函数体内形式参数使用上与一般多参数函数是一样方便的。
(defn f [x]
(->> x
f1
f2))
(defn f [{:keys [x y] :as m}]
(->> x
(f1 y ,)
f2))
2.分支:一个(cond)或(if)块作为一个函数。代码示意:
(defn f [x]
(cond
(= x 1) (f1)
(= x 2) (f2)
:else (f3)))
(defn f2 [x y]
(-> (> x 2)
(and , (< y 6))
(if , 25 30)))
(defn path-combine [s1 s2]
(cond
(string/starts-with? s2 "/")
s2
(not (string/ends-with? s1 "/"))
(-> (string/split s1 #"[\\/]")
butlast
(#(string/join "/" %))
(str , "/")
(path-combine , s2))
:else
(-> (string/join "/" [s1 s2])
(string/replace , #"[\\/]+" "/"))))
3.反馈电路(回流):一个尾递归函数相当于一个反馈电路。备注:map是批处理,另外也可以看成类似对一个游客队列,在入口重复进行验票动作,是一个前进动作,不是反馈或回流。
(defn f [i]
(if-not (zero? i)
(f1)
(-> i dec recur)))
4.分流:相当于并发或并行。例如:对数据进行分块,并行处理,代码示意:
(->> data
(partition n ,)
(pmap f ,))
(->> [pipe-f1 pipe-f2 pipe-f3]
(pmap #(% data) ,))
5.合流:相当对分流的结果进行reduce,联合后的代码示意:
(->> data
(partition n ,)
(pmap f1 ,)
(reduce f2 ,))
注1:和其他数据流的区别: 以前也曾出现很多数据流思想,但实现和设计都很不理想,从代码上看与集成电路相差极远。纯函数管道数据流clojure最合适,从语义和表现形式上看,都是最简单直观的。下面附带一个代码比较链接:
1.我的数据流代码大概是这个形式:
https://clojureverse.org/t/how-to-join-file-paths/814/12
一般是一个->>
块一个函数。
2.对比一下akka的数据流: https://doc.akka.io/docs/akka/current/stream/stream-composition.html 我们可以看出它们代码的象形和逻辑上的差距,akka代码与文章里的集成电路示意图形式上差距很大。 更不用说Rxjava那种丑爆的代码
注2:与ring middleware区别
代码看起来相似,但理念是本质上的差别。
1.middleware函数的输入输出是函数,流转的是层层打包的函数,是同心圆,middleware调试是很麻烦的。
2.纯函数管道数据流输入输出是数据,流转的是数据,是串并联。
3.我不能认同中间件的理念,和集成电路思想是冲突的。电路里,元件(电路板)是无法流转的,只有数据(电流)才能流转,理解不要有偏差。
注3:本文主讲系统构造,不在于精妙的代码,文章内是伪代码,整个软件工程100%由纯粹的->>
块构成才是重点,也是难点, ->>
宏使用这种语法不是重点,->>
构造系统是一种流程优化,使数据和逻辑分离得很清楚,达到科学、简单、灵活的架构。不用->>
架构的架构数据和逻辑混杂,我称为“可怕的泥石流”。
最后,大道至简,这种系统构造方法看起来方法很简单,象自由博击和形意拳一样,只有基本元件和基本组合方法,需要把基本功练成本能,神而明之、触之即发、千变万化的应用,不要用固定的模式限制自己的想象力,没有FP和OO那么多套路和招数,但是对数据抽象和逻辑抽象的要求比FP还要高得多,辅之道法自然、千变万化、天马行空的想像力和创造力。
做一件好事很容易,一辈子纯做好事很难。写一个纯函数管道数据流函数很容易,写一个100%由纯函数管道数据流函数组成的系统很难,用最简单的方法把一个复杂系统构造得简洁清楚,使之看起来好象很简单,这已经是大师级以上的水准了。
看到能把FP用来工作的人数那么少,我是不指望本方法能流行,但发表出来,如果能帮助他人开拓思想,有所得益,还是一件令人快乐的事。