1

我有一堆这样的:

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

有一个明显的问题是,如果只有一个aorb是错误的,那么就有一个不必要的;. 但是还有一个更严重的问题:getError()如果验证有效则抛出异常。

有没有办法我可以编写这个 lambda(或在io.vavr.control.Validation 库中使用其他东西)而不使所有 4 种情况(a && b, a && !b, !a && b, !a && !b)都明确?


编辑

为了更清楚,我想要一个最终的结果Validation<String, Foo>。我认为它以这种方式表现得像一个“monad”,但我不确定。

4

4 回答 4

1

我认为您要实现的目标更容易在Either域中解决。

首先,将您的 s 流转换为Validations 流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.Right所有右值的(可能为空的)序列.

更新:

有一种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

于 2018-01-29T16:41:41.550 回答
0

如我所见,您的输出reduce是一个字符串,其中的错误列表由;.

您正在混合累加器参数:

  • a是电流减少的部分结果
  • b是您正在迭代的对象本身

我会做这样的事情:

Stream.of(a, b, c).reduce(
    "", //initial state,
    (prevState, validationObject) -> {
        if (validationObject.isInvalid())
            return prevState + ";" + validationObject.getError();
        else
            return prevState;
    }
)
于 2018-01-29T15:30:43.803 回答
0

这就是我最终要走的路,尽管我认为@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;
        }
    );
于 2018-01-29T22:14:03.713 回答
0

怎么样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;
        }


    }
}
于 2018-01-29T15:51:24.140 回答