java自制简易函数式编程库初出炉

注:本文代码链接:https://files.cnblogs.com/files/bosserbosser/functools.zip

代码和文档会持续发生微调,但不会改变核心思想。

一、 由来

我在做某个项目的过程中,发现以下几个问题:

1、 多份代码中的逻辑基本是重复的

2、 即便是同一份代码,逻辑与逻辑之间层层相扣,到后来自己都读不懂了

回过神来发现,程序处理过程中的很多逻辑,都可以用简单优雅的数学语言加以描述。而代码呢?从代码量上来看,多集中在这些逻辑的实现细节上,所以代码总显得臃肿。那么问题就了然了:人的注意力总是有限的,放在实现细节上多一些,则放在宏观逻辑上少一些。

而如果在业务上进行进一步封装,则又会完全步入另一个圈套:

1、 业务逻辑层层堆叠,晦涩难懂;

2、 每个业务都要这么做的话,会变得非常繁琐。

所以要优先封装通用逻辑而非业务逻辑,目前最需要做的,就是抽离这些公用逻辑,精心构建成统一的体系,以作公用。这与函数式范式理念多有相合。

在之前的半年中也一直在接触、思考函数式的一些概念和特性。在后面的构建中,也借鉴了某些语言中的函数式实现风格,再结合java本身的优缺点,最终构建出一个简易函数式编程库,亦即本文话题的基础。

二、 函数式编程概念

引用维基百科中的描述:

In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions or declarations instead of statements. In functional code, the output value of a function depends only on the arguments that are input to the function, so calling a function f twice with the same value for an argument x will produce the same result f(x) each time. Eliminating side effects, i.e. changes in state that do not depend on the function inputs, can make it much easier to understand and predict the behavior of a program, which is one of the key motivations for the development of functional programming.

译文如下:

计算机科学中,函数式编程是一种编程范式(构建计算机程序的结构与元素的方式),它将运算看成数学函数的等价物,并且避免可变状态和可变数据。这是一种描述式编程模式,即整个程序由表达式和描述完成,而非陈述。在函数式代码中,函数的输出仅依赖于调用函数所传的参数。同一个函数的多次调用,只要参数不变,则输出结果一致。函数式编程消除了副作用(不依赖于函数的输入的状态变化)的影响,程序的行为更容易被理解和预测,这是使用函数式编程非常重要的一个原因。

三、 本文内容

关于函数式编程的内容,有很多博文可以参考。言归正传,本文并不想完整地讨论函数式编程范式的全部内容(精准、广泛地讨论函数式编程,也是非常困难的),只是想以此作为基调和引子,迅速展开本文讨论内容(大多可认为是函数式编程的一个子集)。

本文所述编程库,有以下几个特点:

1、 以函数作为头等公民和基本复用单元的习惯;

2、 以懒惰序列作为基本数据单元构建数据处理流程(数据处理流程化,宏观逻辑与实现分离);

3、 当前java(5~7版)版本下的某些重要特性。

再次强调,此处并没有实现完整意义上的(亦或较为广泛的)函数式编程库,只涉及其中的一个子集(也是当前需求最关注的部分)。

四、 核心概念

1.   函数作为头等公民

即:将函数作为基本复用单元,提高函数的地位,独立于数据存在。

在java中,传统的函数(方法)是依附于类存在的,没有独立行动能力。不过,只含有一个方法(仅指非static方法)的类所创建的对象,通常被称为“函数对象”,可以认为是实现独立函数的一个良好方式。只不过,在java8之前,只能通过定义新类并生成新对象的方式创建函数对象,语法冗长,鲜有人愿意使用而已。Java8提供了lambda表达式语法糖(scala之类的语言则走得更远),轻松解决了这一难题,但目前很多生产环境(公司)并不支持这些版本和语言。

2.   懒惰序列

即:数据提供方不一次性提供所有数据,而是“按需”提供。只有当需求方发送数据请求或者处于数据等待状态时,才会提供一份数据(每次只提供一份,而不是全部)。

在java中,懒惰序列相当于Iterator

懒惰序列有着起码的几条特征:

1、 在外部接口上,与传统数据结构完全兼容;

如:for (Integer i : lazySeq) {/* do something */};

2、 隐含地记录了数据处理的进度;

按Iterator的理解,懒惰序列往往是“不断消亡的对象”,即隐含地记录了当前的工作进度,是流水线数据处理方式所赖数据结构的优秀候选者

3、 在某些场合下,性能优于传统数据结构;

4、 在某些场合下,传统数据结构无法替代(比如:如何表达零到正无穷的序列)。

3.   Java5~7版部分特性(只提关注点)

1、 由于某些历史包袱,java的泛型部分选择了运行时消除类型的策略,将元素的类型抹去(成为Object),与外界交互时通过类型转换完成交接任务。好在,编译器会尽量保证在编译时(如果可以做到的话)对代码进行类型检查,保证类型一致。

2、 针对泛型函数(方法)所提供的自动类型推断,可以很好地减小泛型程序的代码量。

五、 库设计核心理念

1.   核心概念(库的核心成员)

1、 函数:即上文所述“函数对象”;

2、 懒惰序列(SelfIter):兼有Iterator和Iterable接口;

3、 懒惰序列生成器(IterCreator):生成懒惰序列的函数;

4、 懒惰序列转换器(IterConverter):输入和输出都是懒惰序列的函数。

2.   对象转换关系

 

六、 库成员详述

1.   函数

目前分为无参函数(NoParamFunc)、单参函数(OneParamFunc)、双参函数(TwoParamFunc)。

这些函数类按需实现了always(返回固定值)、alwaysTrue、alwaysFalse、self(返回参数本身)等函数生成接口(类方法)。还实现了decorate(嵌套)、decorateBy(被嵌套)、tupleRstFunc等方法(对象方法),分别用于生成嵌套函数、将输入参数与计算结果一并返回的函数。此外,函数降阶操作也已被实现。

此外,OneParamOperator和TwoParamOperator分别提供了一些常见的单参和双参函数。

2.   懒惰序列

即SelfIter。使用新类SelfIter,而不是沿用Iterator,原因有:

1、 除Iterator之外,还要实现Iterable接口,附加了“自身是自身的迭代器”的语义,这种语义是“懒惰序列”这个概念所强制要求的。

2、 可以为懒惰序列附加Iterator之外的其他有用接口(例如:convert,即转换本身,转换出新的序列)。

SelfIter类目前还提供了几个懒惰序列的创建方法,可以由Iterator、Iterable、Array、单个数据,进行创建,也可以创建空序列。

SelfIter类体系庞大。AmountLimit、Chain、Cycle等SelfIter的众多子类,均为懒惰序列。这些类将在后续章节展开描述。

3.   懒惰序列生成器

即IterCreator。当前设计的懒惰序列生成器,是以懒惰序列作为返回值的无参函数。相似于SelfIter,此处也提供了构造生成器的各种方法。另外还提供了由序列转换器产生懒惰序列的两个方法,一个通过给转换器填装参数来实现,另一个通过被转换器包装来实现。

“懒惰序列”一节中所提到的SelfIter的子类,其中一部分提供了构建生成器的接口。

4.   懒惰序列转换器

即IterConverter。输入与输出均为懒惰序列的函数。

此类还提供了一个方法,用来创建一个复合转换器,此转换器由多个转换器首尾相连而成。同时还提供了嵌套方法,通过嵌套和被嵌套产生新的转换器。

“懒惰序列”一节中所提到的SelfIter的子类,其中一部分提供了构建转换器的接口。

5.   元组数据结构

即TwoEleTuple/ThreeEleTuple。为不可变有序数据结构。其含义与python等语言是一致的。

6.   容器工厂

即CollectionFactory/MapFactory。提供了Collection和Map的多种构建接口。

7.   并行处理单元

即SequentialProcessPool。同为序列转换器,提供多线程运算服务,保证输出序列的顺序与输入序列的顺序一致。

注意:须保证每个输入必须生成一个输出(由SequentialProcessPoolRunnableFactory保证)

此类的使用场景不多,目前只为另一个更复杂的并行处理器(Parallel,一个真正含义上的并发IterConverter,即相比之下,输入与输出不是一一对应关系)提供接口支撑。

七、 懒惰序列子类展开

这些子类大多由函数及懒惰序列构造而来(当然,含有自身的处理逻辑)。每个子类都提供了:

1、 适用于各种场景的构造方法

2、 与构造方法一一对应的构建(create)函数

之所以不厌其烦地提供构建函数,是有意利用java类型推断所带来的简洁性

3、 还提供了序列生成器或转换器构建方法

如此便可以便捷地构造所需的函数,一致于函数式编程的要求

下面对所有子类展开描述。

1.   Repeat

单个元素的无限循环。

2.   Cycle

有限元素集合的无限循环。

3.   Chain

由多个懒惰序列首尾拼接而成的新懒惰序列。

4.   Range

由起点、递进法则、终止法则所构成的序列。

5.   IntegerRange

这个不废话了

6.   AmountLimit

懒惰序列的限量截取。如果数量不够,也不会刻意填充。

7.   Filter

源数据经过布隆函数过滤后的序列。

8.   Map

源数据经过映射函数映射后的序列。

9.   Uniq

灵感来源于linux命令,不废话了

10.   TakeUntil

这样一个序列:从元数据中逐个获取元素并作为输出序列的元素,直至遇到一个被Until函数命中的元素(不含这个元素)。

11.   DropUntil

这样一个序列:从元数据中逐个剔除元素,直至遇到一个被Until函数命中的元素(不含这个元素),以此元素开始的所有元素作为新序列的成员

12.   Reduce

以一个对象作为起始点,通过合并函数,对源数据进行逐步合并所产生的序列。如序列[1,2,3,4,5,6]以0为基点,<lambda history,current : history+current>作为合并函数,其结果序列为:[1,3,6,10,15,21]。

13.   TwoIterZip

zip的概念参见python,此处不作详述。

14.   Split

源序列被切片后的切片序列。如序列[1,2,3,4,5,6,7],以2为切片容量进行切分,生成序列[[1,2],[3,4],[5,6],[7]]。

15.   LazySplit

概念与“Split”相同,只是每个切片也是懒惰序列,具体的特性请自行推算。

16.   GroupBy

与python中的groupby相同,不作详述。同“Uniq”类似,请注意其为保证高效而使用的策略。

17.   Parallel

对源数据序处并发有理所产生的懒惰序列

18.   SequentialProcessPool

此类已介绍

八、 应用例程

请参见源码中functools.test包目录下的代码(代码尚未写完)

转载于:https://www.cnblogs.com/bosserbosser/p/6931206.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值