减少状态之一抛出异常的简洁方法
Concise way to reduce where one of the states throws an exception
我有一堆:
Validation<String, Foo> a;
Validation<String, Foo> b;
Validation<String, Foo> c;
以下是他们的一些方法:
boolean isValid();
boolean isInvalid(); // === !isValid()
String getError();
现在,我正在尝试这样做:
Stream.of(a, b, c).reduce(
Validation.valid(foo),
(a, b) -> a.isValid() && b.isValid()
? Validation.valid(foo)
: String.join("; ", a.getError(), b.getError())
);
有一个明显的问题,如果a
或b
中只有一个错误,那么就会有一个不必要的;
。但是还有一个更严重的问题:getError()
如果验证有效则抛出异常。
有没有一种方法可以编写这个 lambda(或在 io.vavr.control.Validation library 中使用其他东西)而不需要全部 4 种情况(a && b
、a && !b
、!a && b
, !a && !b
) 显式?
编辑
说得更清楚一点,我最后想要一个 Validation<String, Foo>
的结果。我认为它的行为类似于 "monad,",但我不确定。
如我所见,您的 reduce
的输出是一个 字符串 ,错误列表由 ;
分隔。
您正在混合累加器参数:
a
是当前减少的部分结果
b
是您正在迭代的对象本身
我会这样做:
Stream.of(a, b, c).reduce(
"", //initial state,
(prevState, validationObject) -> {
if (validationObject.isInvalid())
return prevState + ";" + validationObject.getError();
else
return prevState;
}
)
那Collectors.groupingBy()呢。我认为你仍然可以改进字符串的假部分集并加入它们,下面代码的输出是:
{false=[SomeException, ReallyBadProblem], true=[3, 5]}
代码示例:
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toSet;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Validation<String,Integer> a = new Validation<>(true, null, 3);
Validation<String,Integer> b = new Validation<>(true, null, 5);
Validation<String,Integer> c = new Validation<>(false, "SomeException", null);
Validation<String,Integer> d = new Validation<>(false, "ReallyBadProblem", null);
//Stream.of(a,b,c).collect(Collectors.groupingBy(v->v.isValid(), v->v.));
TreeMap<Boolean, Set<Object>> map = Stream.of(a,b,c,d).collect((groupingBy(v->v.isValid(), TreeMap::new,
mapping(v-> { return v.isValid() ? v.valid() : v.getError();}, toSet()))));
System.out.println(map);
}
public static class Validation<E, T>{
boolean valid;
T validVal;
String error;
public Validation(boolean valid, String error, T validVal) {
super();
this.valid = valid;
this.error = error;
this.validVal = validVal;
}
/**
* @return the valid
*/
public boolean isValid() {
return valid;
}
/**
* @param valid the valid to set
*/
public void setValid(boolean valid) {
this.valid = valid;
}
/**
* @return the error
*/
public String getError() {
return error;
}
/**
* @param error the error to set
*/
public void setError(String error) {
this.error = error;
}
public T valid() {
return validVal;
}
}
}
我认为您要实现的目标在 Either
域中更容易解决。
首先,将您的 Validation
流转换为 Either
流:
Stream<Either<String, Foo>> eithers = Stream.of(a, b, c)
.map(Validation::toEither);
然后合并它们:
Either<String, Foo> result = Either.sequence(eithers)
.mapLeft(seq -> seq.collect(Collectors.joining("; ")))
.map(combinator); // fill in with combinator function that converts
// a Seq<Foo> into a single Foo
由于您没有指定如何将多个有效 Foo
对象合并为一个对象,因此我将其打开供您填写上面示例中的组合器函数。
Either.sequence(...)
将通过返回包含左值序列的 Either.Left
将许多 either 减少为一个,如果提供的 eithers 中的 any 是一个左,或者 Either.Right
包含所有右值的(可能为空)序列,如果 none 提供的任一个是左值。
更新:
有一种 Validation.sequence(...)
方法可以在不转换为 Either 域的情况下完成(我在创建原始答案时不知何故错过了 - 感谢您指出):
Validation<Seq<String>, Seq<Foo>> validations = Validation.sequence(
Stream.of(a, b, c)
.map(v -> v.mapError(List::of))
);
Validation<String, Foo> result = validations
.mapError(errors -> errors.collect(Collectors.joining("; ")))
.map(combinator); // fill in with combinator function that converts
// a Seq<Foo> into a single Foo
你说 Foo
个实例是相同的,这意味着你可以使用 Seq::head
代替组合函数。但是你需要注意不要使用空的验证序列作为输入,因为在这种情况下它会导致 Seq::head
抛出 NoSuchElementException
。
这就是我最终选择的方式,尽管我认为@Nandor(接受的答案)是正确的。 (他的解决方案基于 io.vavr.control.Validation
的最新版本,而不是我可用的版本(仍然是 javaslang.control.Validation
)。我发现 mapLeft
已重命名为 mapErrors
,但是是一些与 Seq
操作相关的缺失位。由于对 Java 不熟悉,我无法解决我走这条路的错误。)
Validation<String, AdRequest> validation =
Stream.of(
validateTargetingRequest(adRequest),
validateFlightRequest(adRequest, ad),
validateCreativeRequest(adRequest)
).reduce(
Validation.valid(adRequest),
(a, b) -> {
if (a.isValid() && b.isValid()) {
return Validation.valid(adRequest);
}
if (a.isInvalid() && b.isInvalid()) {
return Validation.invalid(String.join("; ", a.getError(), b.getError()));
}
// This seemingly overcomplicated structure was necessitated by the fact that getError
// throws an exception when called on an Valid form of Validation.
return a.isInvalid() ? a : b;
}
);
我有一堆:
Validation<String, Foo> a;
Validation<String, Foo> b;
Validation<String, Foo> c;
以下是他们的一些方法:
boolean isValid();
boolean isInvalid(); // === !isValid()
String getError();
现在,我正在尝试这样做:
Stream.of(a, b, c).reduce(
Validation.valid(foo),
(a, b) -> a.isValid() && b.isValid()
? Validation.valid(foo)
: String.join("; ", a.getError(), b.getError())
);
有一个明显的问题,如果a
或b
中只有一个错误,那么就会有一个不必要的;
。但是还有一个更严重的问题:getError()
如果验证有效则抛出异常。
有没有一种方法可以编写这个 lambda(或在 io.vavr.control.Validation library 中使用其他东西)而不需要全部 4 种情况(a && b
、a && !b
、!a && b
, !a && !b
) 显式?
编辑
说得更清楚一点,我最后想要一个 Validation<String, Foo>
的结果。我认为它的行为类似于 "monad,",但我不确定。
如我所见,您的 reduce
的输出是一个 字符串 ,错误列表由 ;
分隔。
您正在混合累加器参数:
a
是当前减少的部分结果b
是您正在迭代的对象本身
我会这样做:
Stream.of(a, b, c).reduce(
"", //initial state,
(prevState, validationObject) -> {
if (validationObject.isInvalid())
return prevState + ";" + validationObject.getError();
else
return prevState;
}
)
那Collectors.groupingBy()呢。我认为你仍然可以改进字符串的假部分集并加入它们,下面代码的输出是:
{false=[SomeException, ReallyBadProblem], true=[3, 5]}
代码示例:
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toSet;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Validation<String,Integer> a = new Validation<>(true, null, 3);
Validation<String,Integer> b = new Validation<>(true, null, 5);
Validation<String,Integer> c = new Validation<>(false, "SomeException", null);
Validation<String,Integer> d = new Validation<>(false, "ReallyBadProblem", null);
//Stream.of(a,b,c).collect(Collectors.groupingBy(v->v.isValid(), v->v.));
TreeMap<Boolean, Set<Object>> map = Stream.of(a,b,c,d).collect((groupingBy(v->v.isValid(), TreeMap::new,
mapping(v-> { return v.isValid() ? v.valid() : v.getError();}, toSet()))));
System.out.println(map);
}
public static class Validation<E, T>{
boolean valid;
T validVal;
String error;
public Validation(boolean valid, String error, T validVal) {
super();
this.valid = valid;
this.error = error;
this.validVal = validVal;
}
/**
* @return the valid
*/
public boolean isValid() {
return valid;
}
/**
* @param valid the valid to set
*/
public void setValid(boolean valid) {
this.valid = valid;
}
/**
* @return the error
*/
public String getError() {
return error;
}
/**
* @param error the error to set
*/
public void setError(String error) {
this.error = error;
}
public T valid() {
return validVal;
}
}
}
我认为您要实现的目标在 Either
域中更容易解决。
首先,将您的 Validation
流转换为 Either
流:
Stream<Either<String, Foo>> eithers = Stream.of(a, b, c)
.map(Validation::toEither);
然后合并它们:
Either<String, Foo> result = Either.sequence(eithers)
.mapLeft(seq -> seq.collect(Collectors.joining("; ")))
.map(combinator); // fill in with combinator function that converts
// a Seq<Foo> into a single Foo
由于您没有指定如何将多个有效 Foo
对象合并为一个对象,因此我将其打开供您填写上面示例中的组合器函数。
Either.sequence(...)
将通过返回包含左值序列的 Either.Left
将许多 either 减少为一个,如果提供的 eithers 中的 any 是一个左,或者 Either.Right
包含所有右值的(可能为空)序列,如果 none 提供的任一个是左值。
更新:
有一种 Validation.sequence(...)
方法可以在不转换为 Either 域的情况下完成(我在创建原始答案时不知何故错过了 - 感谢您指出):
Validation<Seq<String>, Seq<Foo>> validations = Validation.sequence(
Stream.of(a, b, c)
.map(v -> v.mapError(List::of))
);
Validation<String, Foo> result = validations
.mapError(errors -> errors.collect(Collectors.joining("; ")))
.map(combinator); // fill in with combinator function that converts
// a Seq<Foo> into a single Foo
你说 Foo
个实例是相同的,这意味着你可以使用 Seq::head
代替组合函数。但是你需要注意不要使用空的验证序列作为输入,因为在这种情况下它会导致 Seq::head
抛出 NoSuchElementException
。
这就是我最终选择的方式,尽管我认为@Nandor(接受的答案)是正确的。 (他的解决方案基于 io.vavr.control.Validation
的最新版本,而不是我可用的版本(仍然是 javaslang.control.Validation
)。我发现 mapLeft
已重命名为 mapErrors
,但是是一些与 Seq
操作相关的缺失位。由于对 Java 不熟悉,我无法解决我走这条路的错误。)
Validation<String, AdRequest> validation =
Stream.of(
validateTargetingRequest(adRequest),
validateFlightRequest(adRequest, ad),
validateCreativeRequest(adRequest)
).reduce(
Validation.valid(adRequest),
(a, b) -> {
if (a.isValid() && b.isValid()) {
return Validation.valid(adRequest);
}
if (a.isInvalid() && b.isInvalid()) {
return Validation.invalid(String.join("; ", a.getError(), b.getError()));
}
// This seemingly overcomplicated structure was necessitated by the fact that getError
// throws an exception when called on an Valid form of Validation.
return a.isInvalid() ? a : b;
}
);