问题描述
背景
我对处理异常的 Scott WLaschin's Railway Oriented Programming 模型很着迷:有一个侧通道,所有坏的东西都将在其中处理,并将好的东西保留在主轨道上。下图:
问题
日常代码中出现的常见模式如下:
问题
如何以类似于上面讨论的面向铁路的模型的方式做到这一点。
解决方法
[Somjit's][1] 答案显示了正确的方法,但是它使用外部变量 (errors
) 来累积错误。一般来说,这是不鼓励的,因为我们应该避免全局/外部状态。
您可以使用 vavr 的 [partition
][2] 方法将流分成两种:一种带有错误,一种带有经过验证的整数。它们都被放入一个元组中:
public void composeExceptions() {
final Tuple2<Stream<Either<IllegalArgumentException,Integer>>,Stream<Either<IllegalArgumentException,Integer>>> both = Stream.range(1,11)
.map(this::validate)
.partition(Either::isLeft);
both._1.map(Either::getLeft).forEach(e -> System.out.println("Got error: " + e.getMessage()));
both._2.map(Either::get).forEach(i -> System.out.println("Validated correctly: " + i));
}
编辑 实际上还有其他选项,例如:
Stream
.range(1,11)
.map(this::validate)
.toJavaStream()
.collect(Collectors.teeing(
Collectors.filtering(Either::isLeft,toList()),Collectors.filtering(Either::isRight,(errors,ints) -> new Tuple2<>(errors.stream().map(Either::getLeft),ints.stream().map(Either::get))));
使用了 teeing
,这是一个来自 Java API 的非常有趣的收集器。不幸的是,它混合了 vavr 和 java API,这不是很好,也不是很糟糕。
还有:
Stream
.range(1,11)
.map(this::validate)
.collect(
() -> new Tuple2<>(List.<RuntimeException>empty().asJavaMutable(),List.<Integer>empty().asJavaMutable()),(tuple,either) -> {
either.peekLeft(tuple._1::add);
either.peek(tuple._2::add);
},(t1,t2) -> {
t1._1.addAll(t2._1);
t1._2.addAll(t2._2);
}
)
.map((exceptions,integers) -> new Tuple2<>(List.ofAll(exceptions),List.ofAll(integers)));```
which uses vavr API only but underneath uses java `List` since a mutable structure is required here.
[1]: https://stackoverflow.com/a/67556075/542270
[2]: https://www.javadoc.io/doc/io.vavr/vavr/latest/io/vavr/collection/Traversable.html#partition(java.util.function.Predicate)
,
处理“侧轨”异常的一种简单方法是使用 Vavr 提供的 peekleft
方法,该方法消耗 Either.Left()
侧。我们可以在那里插入我们的异常处理逻辑,并将我们的 Either.right()
东西很好地留在主轨道上,没有任何丑陋。
我很确定这可以改进,并且很想改进它。
List<String> errors = new ArrayList<>();
@Test
public void composeExceptions() {
List<Integer> valids = IntStream.range(0,11).boxed()
.map(this::validate) // throws exceptions
.peek(this::handleExceptions) // process left/exceptions on the side
.flatMap(Value::toJavaStream) // flatmap is right based
.collect(Collectors.toList());
System.out.println("========= Good ones =========");
System.out.println(valids);
System.out.println("========= Bad Ones =========");
errors.forEach(System.out::println);
}
public void handleExceptions(Either<IllegalArgumentException,Integer> either) {
either.peekLeft(e -> errors.add(e.getMessage())); // is this a monadic bind ???
}
public Either<IllegalArgumentException,Integer> validate(Integer i) {
if (i % 2 == 0) return Either.right(i);
return Either.left(new IllegalArgumentException("odd one's out : " + i));
}