纯函数管道数据流·基本构造方法

shxiao 2019-06-26

纯函数管道数据流·基本构造方法

版权所有 © 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/ho... 
一般是一个->>块一个函数。

2.对比一下akka的数据流:
https://doc.akka.io/docs/akka... 
我们可以看出它们代码的象形和逻辑上的差距,akka代码与文章里的集成电路示意图形式上差距很大。
更不用说Rxjava那种丑爆的代码

注2:与ring middleware区别

代码看起来相似,但理念是本质上的差别。

1.middleware函数的输入输出是函数,流转的是层层打包的函数,是同心圆,middleware调试是很麻烦的。

2.纯函数管道数据流输入输出是数据,流转的是数据,是串并联。

3.我不能认同中间件的理念,和集成电路思想是冲突的。电路里,元件(电路板)是无法流转的,只有数据(电流)才能流转,理解不要有偏差。

注3:本文主讲系统构造,不在于精妙的代码,文章内是伪代码,整个软件工程100%由纯粹的->>块构成才是重点,也是难点, ->>宏使用这种语法不是重点,->>构造系统是一种流程优化,使数据和逻辑分离得很清楚,达到科学、简单、灵活的架构。不用->>架构的架构数据和逻辑混杂,我称为“可怕的泥石流”。

最后,大道至简,这种系统构造方法看起来方法很简单,象自由博击和形意拳一样,只有基本元件和基本组合方法,需要把基本功练成本能,神而明之、触之即发、千变万化的应用,不要用固定的模式限制自己的想象力,没有FP和OO那么多套路和招数,但是对数据抽象和逻辑抽象的要求比FP还要高得多,辅之道法自然、千变万化、天马行空的想像力和创造力。

做一件好事很容易,一辈子纯做好事很难。写一个纯函数管道数据流函数很容易,写一个100%由纯函数管道数据流函数组成的系统很难。

看到能把FP用来工作的人数那么少,我是不指望本方法能流行,但发表出来,如果能帮助他人开拓思想,有所得益,还是一件令人快乐的事。

相关推荐