函数式编程总结

函数式编程入门

本文为理解函数式编程相关概念,以及java函数试编程的入门概念及应用。请各位大佬如发现任何理解不正确的地方评论指正。

什么是函数式编程

function可以翻译为函数也可以翻译为功能(个人感觉翻译成功能更好理解,然而翻译成函数更有意义),和面向对象一样,它是一种思想,我们可以把它理解为面向函数。函数是一种变化过程,一个函数可以当作参数传给另一个函数,一个函数的返回值也可以是一个函数。函数内可以包含其他函数,在其他函数基础上做更多的事,产生新变化。

函数式编程的一些名词及概念:

  1. 闭包 ,外部函数包含一个内部函数,内部函数引用了外部函数的成员变量,外部函数的返回值是内部函数。当调用外部函数时,内部函数被返回并赋值给某个变量,这就导致内部函数一直被引用,即使外部函数结束,它的成员变量依然不会被销毁回收;
  2. 纯函数 ,输入固定参数输出总是固定的,并且运行过程没有任何副作用,比如修改外部变量,输出日志,查数据库等;一个程序不可能全由纯函数组成,必然有副作用。函数试编程是可以将副作用剔除,剩余部分的纯函数组合,最后统一执行副作用这样来实现链式调用。
  3. 嵌套函数 ,有点类似闭包的感觉,一个函数可以定义在另一个函数内部,并且可以访问外部函数的成员变量,但是外部函数之外无法直接访问嵌套的函数;
  4. 函数柯里化 ,将一个多参函数变为只接收一个参数并返回一个新函数的形式。新函数用于逐个接收其余参数。意义在于每次传入一个参数,返回一个新函数,这个新函数可以赋值给某个变量m,此时m这个新函数已经接收过上次调用传递的变量。再次调用m时,只需传递下一步的参数即可。m本身已经包含了之前调用的所有参数处理后的变化;
  5. 高阶函数 ,入参或出参是一个函数,的函数,它可以将某种变化抽象固定下来,变化的内容以函数参数传递进来,返回一个新函数,这个新函数比原函数具有的变化(功能)更多;
  6. 惰性计算 ,当你需要计算一系列值的时候,可以先计算第一个值,之后返回计算第二个值的函数。当需要用第二个值的时候,调用计算第二个值的函数即可。一个无限的集合,只有当你取其中一个值的时候,计算才会发生。;
  7. 函数组合 ,将不同函数组合在一起变成一个新函数。纯函数比较容易被组合;新函数其实就是一个更大的函数,如:a-b,b-c。组合后就是a-c
  8. Point Free ,不关注入参出参的具体值,而是将入参和出参都用一个函数来表示,使用一些通用的函数,组合出各种复杂运算的一个新函数;
  9. lambda表达式,Java的lambda表达式是一种语法糖,主要为了让开发者可以使用函数试编程的思想去写代码,将函数式编程的优点带入java;
  10. 函数式接口,只有一个抽象方法的接口。Java规定函数接口中可以定义静态方法,默认方法(default修饰),也可以包含Object类中的方法,但只能有1个抽象方法。字段也是可以在接口中定义的,默认为public static final修饰;

JAVA内置函数接口

  1. Function<T,R>,接受一个输入并且有一个输出的函数;
  2. Consumer,接受一个输入没有输出的函数,消费;
  3. Supplier,不接受输入有一个输出的函数,生产;
  4. Predicate,断言,接受一个输入返回一个boolean;
  5. UnaryOperator,一个输入一个输出,输入输出类型相同;
  6. BiFunction<T,U,R>,两个输入一个输出;
    以上只是主要常用接口,JDK8中还定义了很多跟数据类型或参数数量有关的函数接口如Long Double等。
    常用接口的写法如下:
	Function<Integer,Integer> function = i -> i + 1;
    Consumer<Integer>   consumer = i -> System.out.println(i);
    Supplier<String>    supplier = () ->  "i'm supplier";
    Predicate<Integer>  predicate = i -> i == 0;
    UnaryOperator<Integer> unaryOperator = i -> i + 1;
    BiFunction<Integer,Integer,Integer> biFunction = (i,j) -> i + j;
    //特殊写法,void compatibility,如果表达式只有一句话,
    //可以赋值给没有返回值的函数接口,虽然函数接口不需要返回值,
    //所以此写法是忽略返回值
    Consumer<Integer> consumerVoid = (i) -> i++;

函数引用

  1. 构造器引用,ClassName::new ,有参的构造方法需要使用参数推导:Function<T,R> function = ClassName::new;
  2. 数组引用,与构造器引用一样,把数组看成一种特殊的类,
    Function<Integer,String[]> func1 = (length)->new String[length];
    String[] arr1 = func1.apply(6);
    Function<Integer,String[]> func2 = String[]::new;
    String[] arr2=func2.apply(1);
    
  3. 对象::实例方法,instanceName::method;
  4. 类::静态方法,ClassName::method;
  5. 类::实例方法,ClassName::method ;
  6. 引用规则,函数接口可以赋值给Object类型的引用,但不能直接将lambda表达式赋值给Object引用,需要通过(类型)强制转换后才可以;
    Runnable r1 = () -> {System.out.println("Hello Lambda!");};
    Object obj = r1;
    Object o = (Runnable) () -> { System.out.println("hi"); };
    
    lambda表达式不能被直接当作Object使用,需要转成函数接口后才可以被直接当作Object引用 ;
    System.out.println( (Runnable)() -> {} ); 
    

特殊规则 一个有N个参数的函数接口,第一个参数类型为T,我们可以将一个参数数量为N-1的函数赋值给它,但是这个函数所在类的型必须也是T 如下关键代码部分展示:

//函数接口需要两个参数,第一个参数类型为FPClass
@FunctionalInterface
public interface FpInterface {
   public void setClassName(FPClass fpClass,String className);
}
//FPClass中有个方法,只有一个参数
public void setClassName(String className) {
   this.className = className;
}
//我们可以直接将一个参数的函数赋值给两个参数的函数接口,
//但是方法所在类的类型必须与函数接口第一个参数类型相同。
Interface fpInterface = FPClass::setClassName;

Stream

*java引入函数式编程最重要的作用之一是支持无限集合的概念,因为函数是在调用时才做计算(惰性计算),即使你的集合无限大,在你使用的时候你也只是要取其中一部分,取的时候实际就是调用函数计算出结果。这就涉及中间函数、归并函数和终点函数的概念,你必须最终调用一个终点函数。在处理一个无限流的过程中,中间函数负责处理数据,归并函数负责组合数据累积变化,终点函数负责获取最终结果。

Stream主要的中间函数
filter()//过滤,入参是一个Predicate,返回boolean
map()//映射,这个不是hashMap的map,不是一个东西。这里所说的映射是一种变化过程,通过这个过程将数据变为另一种东西。
flatMap()//扁平化映射,解决类型嵌套问题
distinct()
sorted()
peek()//处理副作用
limit()
skip()
返回值仍然是一个Stream的函数都是中间函数

Stream主要的终点函数
forEach()
forEachOrdered()//有顺序
toArray()
reduce()
collect()
min()
max()
count()
anyMatch()
allMatch()
noneMatch()
findFirst()
findAny()

reduce 它的作用是实现变化的累积,比如累加累减,类型转换等等很多。reduce也可以用来实现filter map等的逻辑,它支持串行和并行两种执行方式,并行执行时要求是纯函数。参数有三个:
初始值的定义(Identity) 变化开始时的值
累加器(Accumulator) 变化的过程
组合器(Combiner) 累积所有变化后的结果
reduce有三个不同参数的方法,参数列表分别为:
1.只需要一个累加器
2.一个初始值和一个累加器
3.初始值,累加器,组合器
Collect 可以将结果放入一个集合返回,或产生一个字符串

fruitList.stream().map(Fruit::getName).collect(Collectors.joining());
fruitList.stream().collect(ArrayList<Fruit>::new,(list,p) -> list.add(p),List::addAll);

Collectors 一个处理collect的函数集合,或者叫收集器,有很多实用方法,主要分三类:
数据收集:set、map、list,将collect转换成某种集合
聚合归约:统计counting、求和、最值、平均、字符串拼接joining、规约
前后处理:分区partitioningBy、分组groupingBy()、自定义操作mapping、reducing
groupingBy():
根据给定函数分类。有3种重载方法
1.只接受一个classifier
2.接受一个classifier,和一个downstream。downstream也是一个Collectors,方便分类后再做后续处理
3.接受一个classifier和一个downstream,并接受一个supplier自定义一个map
partitioningBy: 固定分为两类。返回true或false为key的Map
mapping: 一个映射,映射之后可以利用downstream继续处理
reducing: 需要一个BinaryOperator,两个输入一个输出,根据比较器筛掉一个不符合规则的。或者有一个初始值,先经过一个map处理,之后根据比较策略筛选掉一个不符合规则的。

函数编程的技巧

1.多个入参的函数,可以用柯里化,先固定一部分输入参数,之后返回一个新函数,新函数的入参只包含一些之前未固定的参数。
2.如果函数不是纯函数,无法实现连续的链式调用随意组合,可以将函数内的副作用提取出来放到一个新的类里,作为新类的成员变量保存,之后使用monad设计模式,将变化累积到这个成员变量上,最后执行。就是将副作用累加,最后再执行,原来函数就变为一个纯函数可以任意组合链式调用。

monad—自函子范畴上的一个幺半群

这句话很烧脑,因为是初学所以无法肯定自己解释是正确的,希望大佬多指教。
我认为这句话最终是说,monad是一个幺半群。那什么是幺半群呢,大家都说他是范畴轮的概念,于是我们去查范畴轮对幺半群的定义有哪些概念。
1.群
其实就是一个集合,里边有一堆元素,这些元素必须符合3个条件规则。
2.半群
就是这个集合,里边元素不需要符合3个条件规则,只需符合其中两个(太好了,正好第三个条件不太懂) 那这个集合就叫半群。
这两个条件是什么呢?
(1)集合中的元素可以随意组合:abc=a*(b*c)
(2)集合中必须有一个元素e,任何元素X跟e结合,都还是X,典型的e不就是乘法中的1,加法中的0吗。
3.幺元
对于编程方面的理解,上边说的集合,里边的元素应该是函数。也就是包括一堆函数的集合
这些函数可以随意组合,并且有一个函数e跟其他任何一个函数x组合,计算结果还是X。比如{a+0,a+1,a+2},a+0就是幺元,因为这个元素代表无变化。他的最大意义是,从无变化开始。
4.幺半群
就是一个集合包含一堆函数,其中有一个函数是幺元。
也就是说,幺半群就是一组函数。他们都满足两个条件:
1、随意组合。
2、从无变化开始。
幺办群最大的特点就是,无变化到有变化,也就是变化的累积。看到这里,明白网上为什么都说 Monad适合处理副作用了。刚开始,一个值没有变化,之后将这个值放到各种函数中计算,每次计算的结果都作为下次计算的输入,变化不断的累积。这个所说的刚开始的值,可以是一个值,比如数字5,也可以是一个函数,比如a+1。这取决于你的变化是什么。monad就是要做变化的累积。当这个值是某个值时比较好理解,比如0变2,2变6。当这个值是一个函数时,可以理解为:一个打一行日志的函数变成打一行日志再打一行日志的函数。
5.范畴
在编程方面理解,范畴就是类型。比如String类型,这个范畴里的对象就是String类里的变量和函数。
6.函子与自涵子
涵子是将一个东西转化成另外一个东西,自涵子就是将一个东西转化成自己的东西。
编程方面理解,自涵子就是一个函数(方法),它可以将一个不是自己范畴内的任何类型对象,包裹在自己范畴内(转化成他自己的东西)。Java代码来说就是:
Optional,Optional类里有一个T t全局变量,这个T就是任意类型的变量。Optional有一个方法叫of(T t),当调用Optional.of(xxx)的时候,就把xxx,存入了Optional中的T t变量中。把任何类型的东西,映射(保存)到自己范畴(类)内,就是自函子干的事
flatMap函数是将盒子里的值拿出来,与某个函数进行运算,产出一个值,再封装回盒子里返回。也就是说如果你这个函数返回的是一个有盒子包装的值,那就不需要再往盒子里装了。
map函数,是将值与函数进行计算,返回一个值。因为函数返回的是一个值,所以需要封装到一个盒子里。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值