0%

Java函数式编程利器 --- vavr

相关概念

1. 什么是函数式编程

    函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。

2. 闭包

    闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

3. 可变集合与不可变集合

    顾名思义,一个可变(mutable)集合能够更新甚至扩展空间,这意味着你能改变,增加,或者删除一个集合的元素。一个不可变(immutable)集合,刚好相反,不能改变。你仍然可以做一些类似的增加,删除,或者更新,但是实际上它返回了一个新的对象,这里面就是指返回了一个新的集合,而老的集合没有改变。

4. 元组

    元组(tuple)是一种不可变数据集的组合,与集合不同的是元组可以存放不同的数据类型,根据存储的数据个数可分为二元组、三元组、四元组……

5. 链式编程

    所谓的链式编程就是可以通过”点”语法,将需要执行的代码块连续的书写下去,使得代码简单易读,书写方便。

6. 惰性求值

    在开发中,我们经常会遇到一些需要延迟计算的情形,比如某些运算非常消耗资源,如果提前算出来却没有用到,会得不偿失。在计算机科学中,有个专门的术语形容它:惰性求值。惰性求值是一种求值策略,也就是把求值延迟到真正需要的时候。

7. 柯里化(Currying)

    在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

8. 偏函数(partial function)

    对给定的输入参数类型,偏函数只能接受该类型的某些特定的值。

9. Functor、Monad、Applicative


vavr相关

vavr库层次结构

vavr

  • Option(Some None)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Optional<String> maybeFoo = Optional.of("foo"); 
then(maybeFoo.get()).isEqualTo("foo");
Optional<String> maybeFooBar = maybeFoo.map(s -> (String)null)
.map(s -> s.toUpperCase() + "bar");
then(maybeFooBar.isPresent()).isFalse();

Option<String> maybeFoo = Option.of("foo");
then(maybeFoo.get()).isEqualTo("foo");
try {
maybeFoo.map(s -> (String) null) // Some(null)
.map(s -> s.toUpperCase() + "bar");
Assert.fail();
} catch (NullPointerException e) {
// this is clearly not the correct approach
}
  • Tuple(元组)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建元组
// (Java, 8)
Tuple2<String, Integer> java8 = Tuple.of("Java", 8);
// "Java"
String _1 = java8._1;
// 8
Integer _2 = java8._2;

// (vavr, 1)
Tuple2<String, Integer> that = java8.map(
s -> s.substring(2) + "vr",
i -> i / 8
);

// (vavr, 1)
Tuple2<String, Integer> that = java8.map(
(s, i) -> Tuple.of(s.substring(2) + "vr", i / 8)
);

// "vavr 1"
String that = java8.apply(
(s, i) -> s.substring(2) + "vr " + i / 8
);
  • Functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// sum.apply(1, 2) = 3
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;

// Use Function3.of to create
Function3<String, String, String, String> function3 =
Function3.of(this::methodWhichAccepts3Parameters);

// 组合函数(y = f(x); g(y) = g(f(x)))
Function1<Integer, Integer> plusOne = a -> a + 1;

Function1<Integer, Integer> multiplyByTwo = a -> a * 2;

// andThen a -> (a + 1) * 2
Function1<Integer, Integer> add1AndMultiplyBy2 = plusOne.andThen(multiplyByTwo);

// compose
Function1<Integer, Integer> add1AndMultiplyBy2 = multiplyByTwo.compose(plusOne);

then(add1AndMultiplyBy2.apply(2)).isEqualTo(6);

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

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);

// partial application
Function5<Integer, Integer, Integer, Integer, Integer, Integer> sum = (a, b, c, d, e) -> a + b + c + d + e;
// fix a, b, c
Function2<Integer, Integer, Integer> add6 = sum.apply(2, 3, 1);

then(add6.apply(4, 3)).isEqualTo(13);

// Currying
Function3<Integer, Integer, Integer, Integer> sum = (a, b, c) -> a + b + c;
Function1<Integer, Function1<Integer, Integer>> add2 = sum.curried().apply(2);

then(add2.apply(4).apply(3)).isEqualTo(9);
  • Try(Success Failure)
1
2
// no need to handle exceptions
Try.of(() -> bunchOfWork()).getOrElse(other);
1
2
3
4
5
6
7
8
9
10
import static io.vavr.API.*;        // $, Case, Match
import static io.vavr.Predicates.*; // instanceOf

A result = Try.of(this::bunchOfWork)
.recover(x -> Match(x).of(
Case($(instanceOf(Exception_1.class)), t -> somethingWithException(t)),
Case($(instanceOf(Exception_2.class)), t -> somethingWithException(t)),
Case($(instanceOf(Exception_n.class)), t -> somethingWithException(t))
))
.getOrElse(other);
1
2
String path = Try.ofCallable(file::getCanonicalPath)
.getOrElseThrow(t -> new CrashException("Error accure.", t))
  • Lazy
1
2
3
4
5
6
7
Lazy<Double> lazy = Lazy.of(Math::random);
lazy.isEvaluated(); // = false
lazy.get(); // = 0.123 (random generated)
lazy.isEvaluated(); // = true
lazy.get(); // = 0.123 (memoized)

CharSequence chars = Lazy.val(() -> "Yay!", CharSequence.class);
  • Either(Left Right)
1
2
3
// If the result of compute() is Right(1), the value is Right(2).
// If the result of compute() is Left("error"), the value is Left("error").
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
  • Future
1
2
// future *value*, result of an async calculation
Future<T> future = Future.of(...);
  • Validation(Valid Invalid)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class PersonValidator {

private static final String VALID_NAME_CHARS = "[a-zA-Z ]";
private static final int MIN_AGE = 0;

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 CharSeq.of(name).replaceAll(VALID_NAME_CHARS, "").transform(seq -> seq.isEmpty()
? Validation.valid(name)
: Validation.invalid("Name contains invalid characters: '"
+ seq.distinct().sorted() + "'"));
}

private Validation<String, Integer> validateAge(int age) {
return age < MIN_AGE
? Validation.invalid("Age must be at least " + MIN_AGE)
: Validation.valid(age);
}
}

PersonValidator personValidator = new PersonValidator();

// Valid(Person(John Doe, 30))
Validation<Seq<String>, Person> valid = personValidator.validatePerson("John Doe", 30);

// Invalid(List(Name contains invalid characters: '!4?', Age must be greater than 0))
Validation<Seq<String>, Person> invalid = personValidator.validatePerson("John? Doe!4", -1);
  • List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<String> list = List.of(
"Java", "PHP", "Jquery", "JavaScript", "JShell", "JAVA");

// List(Jquery, JavaScript, JShell, JAVA)
List list1 = list.drop(2);

// List(Java, PHP, Jquery, JavaScript)
List list2 = list.dropRight(2);

// List(JShell, JAVA)
List list3 = list.dropUntil(s -> s.contains("Shell"));

// List(PHP, Jquery, JavaScript, JShell, JAVA)
List list4 = list.dropWhile(s -> s.length() >= 4);
  • Stream
1
2
// 2, 4, 6, ...
Stream.from(1).filter(i -> i % 2 == 0);
  • Pattern Matching
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import static io.vavr.API.*;
import static io.vavr.Patterns.*;

String s = Match(i).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(), "?")
);

String s = Match(i).of(
Case($(is(1)), "one"),
Case($(is(2)), "two"),
Case($(), "?")
);

Match(option).of(
Case($Some($()), "defined"),
Case($None(), "empty")
);

vavr应用实例

  1. 经典word count问题(取出词频最高的前10条数据)
1
2
3
4
5
6
7
8
9
10
11
String content = "Please send this message to those people who mean something to you,"
+ " to those who have touched your life in one way or another, to those who make you smile "
+ "when you really need it, to those that make you see the brighter side of things when you are "
+ "really down, to those who you want to let them know that you appreciate their friendship. "
+ "And if you don’t, don’t worry,nothing bad will happen to you,you will just miss out on the "
+ "opportunity to brighten someone’s day with this message.";
String[] arr = content.split("\\s+");
List.of(content.split("\\s+")).map(String::trim)
.groupBy(s -> s).mapValues(List::size)
.toList().sortBy(t -> -t._2()).take(10)
.forEach(tup -> System.out.println(tup._1 + "-----" + tup._2));

2.求阶乘 n! = 1×2×3…×n

1
2
3
4
int number = 6;
int n = List.rangeClosed(1, number).reduce((x, y) -> x * y);
// 720
System.out.println(n);

3.找出100个人中年龄在28岁及以上的男性

1
2
3
4
Random random = new Random();
// genger true: 男性 false 女性
List.rangeClosed(1, 100).map(i -> new Person("aaa", random.nextInt(100), random.nextBoolean()))
.filter(p -> p.getAge() >= 28 && p.isGender()).toJavaList();