Vavr——一个颠覆JAVA的库

前言

Java8 在一定程度上支持了函数式编程,但标准库提供的函数式 API 不是很完备和友好。为了更好的进行函数式编程,我们就不得不借助于第三方库,而 VAVR 就是这方面的佼佼者,它可以有效减少代码量并提高代码质量。本文旨在快速的介绍一个VAVR提供的一些功能,以便我们能够快速入门VAVR。本文仅作为VAVR的入门介绍,不会完全列举API,不会深入源码分析。

环境准备

jdk:1.8
vavr:1.0.0-alpha-4

概述

Vavr提供了一些最基本类型的设计良好的表示,这些最基本的类型显然在Java中缺少或尚处于初级阶段.
在Vavr中,一切都建立在以下三个基本构建块之上:TupleValueλ

元组,Java 缺失的结构

熟悉Scala的同学可能对于元组这一概念并不陌生,但是Java缺少元组的一般概念。元组是将固定数量的元素组合在一起,以便它们可以作为一个整体进行传递。与数组或列表不同,元组可以保存具有不同类型的对象,但它们也是不可变的。

在Vavr中元组的类型为 Tuple1、Tuple2、Tuple3 等,元组最多有8中类型的元素,并不是只能放8个元素。若要访问元组的元素,可以使用方法来访问第一个元素,访问第二个元素,依此类推。tt._1t._2

通过 Tuple 的静态工厂可以轻松创建一个元组

  Tuple5<Integer, Boolean, Double, String, Character> tup = Tuple.of(1, true, 2.2, "hello", 'a');

        Integer one = tup._1;
        Boolean two = tup._2;
        Double three = tup._3;

通过下标我们可以轻松获取到对应类型的值,而不用进行类型转换。不过值得注意的一点是元组的下标是从1开始。

更强大的函数接口

函数式编程是关于值和使用函数的值转换。Java 8 只提供了一个接受一个参数的 a 和一个接受两个参数的参数。而Vavr 提供最多 支持8 个参数的函数式接口。

  Function5<Integer, Integer, Integer, Integer, Integer, Integer> f = (a, b, c, d, e) -> a + b + c + d + e;

        System.out.println(f.apply(1, 2, 3, 4, 5));

我们同样可以使用Function的静态工厂方式来创建

 Function5<String, String, String, String, String, String> f2 = Function5.of(this::fiveParamsMethod);
 f2.apply("a", "b", "c", "d", "e");
        
public String fiveParamsMethod(String a, String b, String c, String d, String e){
        return a + b + c + d + e;
    }

事实上,Vavr函数接口是Java 8函数接口的升级版,它还提供了一些更好玩的功能

组合

在数学中,函数合成是将一个函数与另一个函数的结果相加,从而产生第三个函数。例如,函数f :X→Y和g :Y→Z可以组合以产生映射X→Z的函数。您可以使用:h:g(f(X))。Vavr同样提供这样的能力。可以使用andThen 或者compose

 Function1<Integer, Integer> plusOne = x -> x + 1;
        Function1<Integer,Integer> multiplyByTwo = x -> x * 2;

        Function1<Integer, Integer> f = plusOne.andThen(multiplyByTwo);
        f.apply(1);

       Function1<Integer, Integer> f2 = multiplyByTwo.compose(plusOne);
        f2.apply(1);

提升

你可以将部分函数提升为返回Option结果的总函数。偏函数一词来源于数学。X到Y的偏函数是f: X'→Y,对于X的某个子集X '。它通过不强制f将X的每个元素映射到Y的每个元素来概括函数f: X→Y的概念。这意味着分部函数只对某些输入值起作用。如果用不允许的输入值调用函数,它通常会抛出异常。

下面的除法方法是一个只接受非零因子的部分函数。

Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;

我们使用lift将divide转化为一个定义了所有输入的总函数。

Function2<Integer, Integer, Option<Integer>> safeDivide = Function2.lift(divide);

// = None
Option<Integer> i1 = safeDivide.apply(1, 0); 

// = Some(2)
Option<Integer> i2 = safeDivide.apply(4, 2); 

  • 如果使用不允许的输入值调用函数,则提升的函数将返回None而不是引发异常。
  • 如果使用允许的输入值调用函数,则提升的函数将返回Some。

例如:我们可以提升一个只允许输入正值的函数,被提升的函数捕获IllegalArgumentException并将其映射为None

     @Test
    public void test4(){
        Function2<Integer, Integer, Option<Integer>> safeSum = Function2.lift(this::sum);
        final Option<Integer> i1 = safeSum.apply(-1, -2);
        final Option<Integer> i2 = safeSum.apply(1, 2);
        System.out.println(i1);
        System.out.println(i2);

    }

    private int sum(int first, int second) {
        if (first < 0 || second < 0) {
            throw new IllegalArgumentException("Only positive integers are allowed");
        }
        return first + second;
    }

部分应用

部分应用允许通过固定某些值从现有函数派生新函数。您可以固定一个或多个参数,固定参数的数量定义了新函数的特性,如new arity =(original arity - fixed parameters)。这些参数是从左到右绑定原函数的

  Function2<Integer, Integer, Integer> add = (a, b) -> a + b;
        Function1<Integer, Integer> add2 = add.apply(2);
        final Integer r = add2.apply(4);
        System.out.println(r);

柯里化

柯里化是一种技术,通过为其中一个参数固定一个值来部分地应用一个函数,从而得到一个返回Function1的Function1函数。
当柯里化Function2时,结果与Function2的部分应用是无法区分的,因为两者都产生一个1-参数数量函数。

  Function3<Integer, Integer, Integer, String> add = (a, b, c) -> a + 2*b + c*c+"";
        final Function1<Integer, Function1<Integer, String>> apply1 = add.curried().apply(1);
        final Function1<Integer, String> apply2 = apply1.apply(2);
        final String apply3 = apply2.apply(3);

        final Function2<Integer, Integer, String> p1 = add.apply(1);
        final Function1<Integer, String> p2 = p1.apply(2);
        final String apply = p2.apply(3);

我们可以看对柯里化后的函数apply的进一步调用将返回除最后一次调用之外的另一个Function1,而部分应用却不同。

记忆化

记忆是缓存的一种形式。缓存函数只执行一次,然后从缓存返回结果。

        final Function0<Long> time = Function0.of(System::currentTimeMillis).memoized();
        TimeUnit.SECONDS.sleep(2);
        final Function0<Long> time2 = Function0.of(System::currentTimeMillis).memoized();
        System.out.println(time.get());
        System.out.println(time2.get());

两次获取的结果完全一样。

值类型

在函数设置中,我们将值视为一种范式,一种无法进一步求值的表达式。在Java中,我们通过使对象的状态为final并将其称为不可变来表达这一点。

Vavr的函数值对不可变对象进行抽象。通过在实例之间共享不可变内存来添加有效的写操作。我们得到的是免费线程安全!

Option

Option是一个表示可选值的一元容器类型。Option的实例要么是Some的实例,要么是None的实例。

posted @ 2023-04-03 22:25  loveletters  阅读(1069)  评论(0编辑  收藏  举报