Vavr core是一个Java函数库。它有助于减少代码量并提高健壮性。函数式编程的第一步是开始思考不可变的值。Vavr 提供不可变的集合以及必要的函数和控制结构来操作这些值。
<dependencies>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.4</version>
</dependency>
</dependencies>
dependencies {
compile "io.vavr:vavr:0.10.4"
}
dependencies {
implementation "io.vavr:vavr:0.10.4"
}
Java缺少元组的一般概念。元组将固定数量的元素组合在一起,以便它们可以作为一个整体传递。与数组或列表不同,元组可以保存不同类型的对象,但它们也是不可变的。 元组的类型为 Tuple1、Tuple2、Tuple3 等。目前有 8 个元素的上限。要访问元组的元素,可以使用方法访问第一个元素,访问第二个元素,依此类推。tt._1t._2
final Tuple2<Integer, String> eliauk = Tuple.of(1, "Eli auk");
Assert.equals(eliauk._1, 1);
Assert.equals(eliauk._2, "Eli auk");
通过Tuple.of()静态工厂方法创建元组,元组的类型为Tuple2,元组的元素类型为Integer和String,元组的元素个数为2,元组的元素下标从1开始。
final Tuple2<Integer, String> map = eliauk.map(
a -> a + 1,
b -> b + "1"
);
Assert.equals(map._1, 2);
Assert.equals(map._2, "Eli auk1");
通过map()方法逐个映射元组组件,map()方法的参数为一个函数,函数的参数为元组的元素,函数的返回值为一个新的元组。
final Tuple2<Integer, String> eliauk = Tuple.of(1, "Eli auk");
final Tuple2<Integer, String> map = eliauk.map((a, b) -> Tuple.of(a + 1, b + "1"));
Assert.equals(map._1, 2);
Assert.equals(map._2, "Eli auk1");
通过map()方法使用一个映射函数映射元组组件,map()方法的参数为一个函数,函数的参数为元组的元素,函数的返回值为一个新的元组。
final String apply = eliauk.apply((a, b) -> b.substring(2) + a);
Assert.equals(apply, "i auk1");
通过apply()方法变换元组,apply()方法的参数为一个函数,函数的参数为元组的元素,函数的返回值为一个新的元组。
函数式编程是关于使用函数进行值和值转换的。Java 8 只提供了一个接受一个参数和一个接受两个参数的参数。Vavr 提供的功能最多为 8 个参数。功能接口被调用等。
Function2<Integer, Integer, Integer> sum = new Function2<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer a, Integer b) {
return a + b;
}
};
通过匿名类创建函数,匿名类的参数为函数的参数,匿名类的返回值为函数的返回值。
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
final Function1<Integer, Integer> function1 = Function1.of(a -> a + 1);
Function1<Integer, Integer> plusOne = a -> a + 1;
Function1<Integer, Integer> multiply = a -> a*2;
final Function1<Integer, Integer> add1AndMultiplyBy2 = plusOne.andThen(multiply);
Assert.equals(add1AndMultiplyBy2.apply(2),6);
通过andThen()方法组合函数,andThen()方法的参数为一个函数,函数的参数为当前函数的返回值,函数的返回值为一个新的函数。
Function1<Integer, Integer> plusOne = a -> a + 1;
Function1<Integer, Integer> multiply = a -> a*2;
final Function1<Integer, Integer> add1AndMultiplyBy2 = plusOne.compose(multiply);
Assert.equals(add1AndMultiplyBy2.apply(2),5);
通过compose()方法组合函数,compose()方法的参数为一个函数,函数的参数为当前函数的参数,函数的返回值为一个新的函数。
andThen()和compose()的区别在于参数的顺序不同,andThen()方法的参数为一个函数,函数的参数为当前函数的返回值,compose()方法的参数为一个函数,函数的参数为当前函数的参数。 上边说的解释可能太官方了,大概理解为andThen()方法是先执行当前函数,再执行参数函数,compose()方法是先执行参数函数,再执行当前函数。
您可以将部分函数提升为返回结果的总函数。术语偏函数来自数学。从 X 到 Y 的部分函数是函数 f:X′ → Y,对于 X 的某个子集 X′。它推广了函数 f:X → Y 的概念,不强制 f 将 X 的每个元素映射到 Y 的元素。这意味着部分函数仅对某些输入值正常工作。如果使用不允许的输入值调用函数,它通常会引发异常。
Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;
final Option<Integer> apply = Function2.lift(divide).apply(1, 0);
Assert.equals(apply.isEmpty(),true);
通过lift()方法提升函数,lift()方法的参数为一个函数,函数的参数为当前函数的参数,函数的返回值为一个新的函数,新的函数的返回值为一个Option。
部分应用程序允许您通过修复某些值从现有函数派生新函数。您可以修复一个或多个参数,固定参数的数量定义了新函数的 arity,例如 .参数从左到右绑定
Function2<Integer, Integer, Integer> sum = Integer::sum;
Function1<Integer, Integer> add1 = sum.apply(1);
Function5<Integer, Integer, Integer, Integer, Integer, Integer> sum1 = (a, b, c, d, e) -> a + b + c + d + e;
Function2<Integer, Integer, Integer> add6 = sum1.apply(2, 3, 1);
final Function2<Integer, Integer, Integer> memoized = add6.memoized();
Assert.equals(add1.apply(2),3);
Assert.equals(add6.apply(4).apply(5),15);
通过apply()方法部分应用函数,可以先传一个参数再传一个在传一个从左到右一次绑定
Function2<Integer, Integer, Integer> sum = Integer::sum;
final Function1<Integer, Integer> add1 = sum.curried().apply(1);
Function5<Integer, Integer, Integer, Integer, Integer, Integer> sum1 = (a, b, c, d, e) -> a + b + c + d + e;
final Function1<Integer, Function1<Integer, Function1<Integer, Function1<Integer, Integer>>>> apply = sum1.curried().apply(1);
Assert.equals(add1.apply(2),3);
Assert.equals(apply.apply(2).apply(3).apply(4).apply(5),15);
区别
记忆是缓存的一种形式。记忆函数仅执行一次,然后从缓存返回结果。 下面的示例在第一次调用时计算一个随机数,并在第二次调用时返回缓存的数字。
final Function0<Integer> randomGenerator = getRandomGenerator();
final Integer apply = randomGenerator.apply();
final Integer apply1 = randomGenerator.apply();
Assert.equals(apply,apply1);
肯定大多数人不太理解这个memoized和普通变量声明有什么区别,其实这个memoized是一个函数,这个函数的返回值是一个随机数,但是这个函数只会执行一次,第二次调用的时候会直接返回第一次调用的结果,这就是memoized的作用。 普通变量声明
配置设置: 当你有一个固定的值(如配置信息)需要在整个应用程序中使用时。
public static final String API_KEY = "your-api-key-here";
配置设置: 当你有一个固定的值(如配置信息)需要在整个应用程序中使用时。
public static final String API_KEY = "your-api-key-here";
单次计算: 当你只需要进行一次计算并存储结果时。
double squareRootOfTwo = Math.sqrt(2);
状态存储: 当你需要在应用程序的生命周期内维护某种状态时。
int counter = 0;
临时变量: 在循环或条件语句中作为临时存储。
for(int i = 0; i < 10; i++) {
int square = i * i;
System.out.println(square);
}
.memoized()
缓存计算密集型操作: 如果你有一个计算密集型的操作,多次调用它是不高效的,你可以使用 .memoized()
来缓存结果。
Function0<Double> expensiveCalculation = Function0.of(() -> {
// ... some expensive calculations
return result;
}).memoized();
懒加载: 如果你有一个操作可能不会被立即需要,或者可能根本就不需要,使用 Function0 会更高效。
Function0<DatabaseConnection> lazyDbConnection = Function0.of(() -> connectToDatabase()).memoized();
函数组合: 当你需要将多个函数组合在一起进行复杂的操作时,使用 Function0 可以更容易地实现函数组合。
Function0<Integer> combinedFunction = Function0.of(() -> function1()).andThen(result -> function2(result));
高阶函数: 当你需要将函数作为参数传递或从另一个函数返回函数时。
public Function0<Double> getRandomGenerator() {
return Function0.of(Math::random).memoized();
}
测试与模拟: 使用 Function0 可以更容易地在单元测试中模拟依赖。
Function0<Long> currentTimeProvider = Function0.of(System::currentTimeMillis).memoized();
用法可以参考Stream-Query的Opp
用法可以参考Stream-Query的Opp中的ofTry
惰性是表示惰性求值的 monadic 容器类型。与Supplier相比,Lazy 是记忆的,即它只评估一次,因此在引用上是透明的。
Lazy<Double> lazy = Lazy.of(Math::random);
lazy.isEvaluated(); // = false
lazy.get(); // = 0.123 (random generated)
lazy.isEvaluated(); // = true
lazy.get();
Assert.equals(lazy.get(),lazy.get());
final Supplier<Double> doubleSupplier = Math::random;
doubleSupplier.get();
doubleSupplier.get();
Assert.notEquals(doubleSupplier.get(),doubleSupplier.get());
通过Lazy.of()方法创建Lazy,Lazy.of()方法的参数为一个函数,函数的返回值为Lazy的值,Lazy的值只会计算一次,之后再调用get()方法都会返回第一次计算的值。
Either 表示一个值是左值还是右值。通常,左值用于错误,右值用于成功。默认情况下,Either 是一个不可变的类型,但是您可以使用 Either.left() 和 Either.right() 创建可变的 Either。
public void testEither() {
Either<String, Integer> value = compute().map(i -> i * 2);
if (value.isRight()) {
System.out.println("Success: " + value.get());
} else {
System.out.println("Failure: " + value.getLeft());
}
}
public static Either<String, Integer> compute() {
// 模拟一个条件,例如,随机数生成来决定是否成功
if (RandomUtil.randomBoolean()) {
// 计算成功,返回一个“右”值
return Either.right(42);
} else {
// 计算失败,返回一个“左”值
return Either.left("Computation failed"); // 这是一个示例错误消息
}
}
这个方法还是挺有意思的,大多数情况下成功设定为返回右边的值,失败返回左边的值,这样就可以通过isRight()方法判断是否成功,通过get()方法获取值,通过getLeft()方法获取错误信息。 还可以对返回成功的值进行一些列的操作也可以使用toEither()自定义失败的返回值
验证控件是一个应用函子,有助于累积错误。当尝试组合Monads时,组合过程将在第一次遇到错误时短路。但是“验证”将继续处理组合函数,累积所有错误。这在验证多个字段(例如 Web 表单)时特别有用,并且您希望知道遇到的所有错误,而不是一次一个错误。
class Person {
final String name;
final int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person(" + name + ", " + age + ")";
}
}
public Validation<Seq<String>, Person> validatePerson(String name, int age) {
return Validation.combine(validateName(name), validateAge(age)).ap(Person::new);
}
private Validation<String, String> validateName(String name) {
return name == null || name.trim().isEmpty() ? Validation.invalid("Name cannot be empty") : Validation.valid(name);
}
private Validation<String, Integer> validateAge(int age) {
return age < 0 || age > 150 ? Validation.invalid("Age must be between 0 and 150") : Validation.valid(age);
}
Validation<Seq<String>, Person> validPerson = validatePerson("John", 30);
Assert.equals(validPerson.get().age,30);
Assert.equals(validPerson.get().name,"John");
Validation<Seq<String>, Person> invalidPerson = validatePerson(" ", -1);
Assert.equals(invalidPerson.getError().asJava(), List.of("Name cannot be empty", "Age must be between 0 and 150").asJava());
为了设计一个全新的Java集合库,它满足了函数式编程的要求,即不变性,已经投入了很多精力。
Java的Stream将计算提升到不同的层,并在另一个显式步骤中链接到特定的集合。有了Vavr,我们不需要所有这些额外的样板。
Vavr 是一个不可变的链表。突变会创建新的实例。大多数操作都是线性时间执行的。后续操作将逐个执行。
final Optional<Integer> reduce = Stream.of(1, 2, 3).reduce(Integer::sum);
final int sum = IntStream.of(1, 2, 3).sum();
final Number sum1 = List.of(1, 2, 3).sum();
final int i = sum1.intValue();
Assert.equals(i,sum);
因为Vavr引入了元组的说明那么与JDK8的Stream相比,Vavr新增了好多便捷的方法以及新特性这里举一个例子
将一个list转换为Map
final java.util.Map<Integer, Character> collect = Stream.of(1, 2, 3).collect(Collectors.toMap(a -> a, b -> (char) (b + 64)));
final Map<Integer, Character> map = List.of(1, 2, 3).toMap(a -> a, b -> (char) (b + 64));
Assert.equals(map.get(1).get(), collect.get(1));
// 上边是普通的转换为map的方法,下边是Vavr新加入了元组之后的转换为map的方法
final HashMap<Integer, Character> javaMap = List.of(1, 2, 3).toJavaMap(HashMap::new, a -> Tuple.of(a, (char) (a + 64)));
Assert.equals(javaMap.get(1), 'A');
这个特性需要引入
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr-test</artifactId>
<version>0.10.4</version>
</dependency>
Arbitrary<Integer> ints = Arbitrary.integer();
// square(int) >= 0: OK, passed 1000 tests.
Property.def("square(int) >= 0")
.forAll(ints)
.suchThat(i -> i * i >= 0)
.check()
.assertIsSatisfied();
通过Arbitrary.integer()方法创建一个随机数,然后通过Property.def()方法创建一个属性,然后通过forAll()方法传入随机数,然后通过suchThat()方法传入一个函数,函数的返回值为boolean,最后通过check()方法检查属性是否满足,最后通过assertIsSatisfied()方法断言属性是否满足。
模式匹配是一种功能,它允许您根据值的类型和结构执行不同的操作。模式匹配是函数式编程的一个重要特性,因为它允许您编写更简洁,更可读的代码。
final String s = Match(1).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(), "?")
);
Assert.equals(s,"one");
final String opt = Match(null).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(), "?")
);
Assert.equals(opt,"?");
单值匹配,通过Match(),如果值为null可以通过()匹配,如果值不为null可以通过(value)匹配
int input = 5;
String output = Match(input).of(
Case($(n -> n > 0 && n < 3), "Between 1 and 2"),
Case($(n -> n > 3 && n < 6), "Between 4 and 5"),
Case($(), "Other")
);
Assert.equals(output,"Between 4 and 5");
final String s = Match(2).of(
Case($(a -> a==1), "one"),
Case($(a-> a==2), "two"),
Case($(), "?")
);
Assert.equals(s,"two");
断言也就是使用lambda表达式Predicate进行匹配
Vavr内部还提供了一些常用的断言,比如is(),isIn(),isNotNull(),isNull(),isOneOf(),isZero()等等 比如
final String opt = Match(null).of(
Case($(is(1)), "one"),
Case($(is(2)), "two"),
Case($(), "?")
);
Assert.equals(opt,"?");