Есть ли в Java 8 эквивалент Scala's Either?


81

Так же, как java.util.Optional<T>в Java 8 (в некоторой степени) эквивалентен Option[T]типу Scala , есть ли эквивалент Scala Either[L, R]?


Все еще нет стандартной реализации за все это время :(
Cherry

Всегда бывает java.lang.Object ... грустно.
Alex R

Ответы:


68

Нет Eitherтипа Java 8, поэтому вам нужно создать его самостоятельно или использовать какую-то стороннюю библиотеку.

Вы можете создать такую ​​функцию, используя новый Optionalтип (но прочитайте этот ответ до конца):

final class Either<L,R>
{
    public static <L,R> Either<L,R> left(L value) {
        return new Either<>(Optional.of(value), Optional.empty());
    }
    public static <L,R> Either<L,R> right(R value) {
        return new Either<>(Optional.empty(), Optional.of(value));
    }
    private final Optional<L> left;
    private final Optional<R> right;
    private Either(Optional<L> l, Optional<R> r) {
      left=l;
      right=r;
    }
    public <T> T map(
        Function<? super L, ? extends T> lFunc,
        Function<? super R, ? extends T> rFunc)
    {
        return left.<T>map(lFunc).orElseGet(()->right.map(rFunc).get());
    }
    public <T> Either<T,R> mapLeft(Function<? super L, ? extends T> lFunc)
    {
        return new Either<>(left.map(lFunc),right);
    }
    public <T> Either<L,T> mapRight(Function<? super R, ? extends T> rFunc)
    {
        return new Either<>(left, right.map(rFunc));
    }
    public void apply(Consumer<? super L> lFunc, Consumer<? super R> rFunc)
    {
        left.ifPresent(lFunc);
        right.ifPresent(rFunc);
    }
}

Пример использования:

new Random().ints(20, 0, 2).mapToObj(i -> (Either<String,Integer>)(i==0?
  Either.left("left value (String)"):
  Either.right(42)))
.forEach(either->either.apply(
  left ->{ System.out.println("received left value: "+left.substring(11));},
  right->{ System.out.println("received right value: 0x"+Integer.toHexString(right));}
));

В ретроспективе Optionalоснованное на ней решение больше похоже на академический пример, но не на рекомендуемый подход. Одна из проблем - это трактовка слова nullкак «пустой», что противоречит значению «либо».

Следующий код показывает, Eitherчто учитывает nullвозможное значение, поэтому оно строго «либо», левое или правое, даже если это значение null:

abstract class Either<L,R>
{
    public static <L,R> Either<L,R> left(L value) {
        return new Either<L,R>() {
            @Override public <T> T map(Function<? super L, ? extends T> lFunc,
                                       Function<? super R, ? extends T> rFunc) {
                return lFunc.apply(value);
            }
        };
    }
    public static <L,R> Either<L,R> right(R value) {
        return new Either<L,R>() {
            @Override public <T> T map(Function<? super L, ? extends T> lFunc,
                                       Function<? super R, ? extends T> rFunc) {
                return rFunc.apply(value);
            }

        };
    }
    private Either() {}
    public abstract <T> T map(
      Function<? super L, ? extends T> lFunc, Function<? super R, ? extends T> rFunc);

    public <T> Either<T,R> mapLeft(Function<? super L, ? extends T> lFunc) {
        return this.<Either<T,R>>map(t -> left(lFunc.apply(t)), t -> (Either<T,R>)this);
    }
    public <T> Either<L,T> mapRight(Function<? super R, ? extends T> lFunc) {
        return this.<Either<L,T>>map(t -> (Either<L,T>)this, t -> right(lFunc.apply(t)));
    }
    public void apply(Consumer<? super L> lFunc, Consumer<? super R> rFunc) {
        map(consume(lFunc), consume(rFunc));
    }
    private <T> Function<T,Void> consume(Consumer<T> c) {
        return t -> { c.accept(t); return null; };
    }
}

Это легко изменить на строгий отказ от него null, просто вставив Objects.requireNonNull(value)в начале обоих фабричных методов. Точно так же можно было бы вообразить добавление поддержки для пустого.


11
Имейте в виду , что в то время как это ведет себя как Eitherтип в каком - то смысле «слишком большой», так как ваши leftи rightполя в принципе может и быть пустым или оба определены. Вы скрыли конструкторы, которые сделали бы это возможным, но такой подход по-прежнему оставляет возможность ошибок в вашей реализации. Говоря простым языком арифметики, вы пытаетесь выйти a + bиз (1 + a) * (1 + b). Конечно, a + bвозникает в результате этого выражения, но то же самое 1и a * b.
Таинственный Дэн

6
@ Таинственный Дэн: запретить определенные состояния во время создания объекта - предпочтительный способ в Java. В противном случае вам пришлось бы изобретать новый тип «допустимого диапазона» почти для каждого варианта использования, intпоскольку использование всего диапазона значений intпри использовании intпеременной является исключением, как пример. В конце концов, он Optionalделает то же самое, обеспечивая соблюдение инвариантов во время создания объекта.
Хольгер

1
@Holger: Either.left(42).map(left -> null, right -> right)бросает NoSuchElementException(правильно) на this.right.get()(неверно). Кроме того, можно обойти принудительное применение инвариантов и произвести Either<empty, empty>путем Either.left(42).mapLeft(left -> null). Или когда все вместе, снова потерпеть неудачу Either.left(42).mapLeft(left -> null).map(left -> left, right -> right).
Чарли

2
@charlie: это решение не учитывает, что Optional.mapпозволяет функции возвращаться null, превращая ее в пустую Optional. Однако, кроме возможности обнаружить это и сразу бросить, я не вижу альтернативного решения, которое было бы «более правильным». Afaik, здесь нет эталонного поведения, так как в Scala вы не можете сопоставить с null
Хольгер

1
@Holger: Я согласен, что нет "более правильного" способа, мне просто не понравилось, что Rightпуть кода вообще выполняется для Leftэкземпляра, даже если ошибка та же. И да, я бы предпочел потерпеть неудачу сразу, а не получить <empty, empty>и потерпеть неудачу позже. Но опять же, все дело только в вкусе / стиле.
Чарли

26

На момент написания vavr (ранее javaslang), вероятно, была самой популярной функциональной библиотекой Java 8. Это очень похоже на lambda-companion Either в моем другом ответе.

Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();


17

В стандартной библиотеке Java нет Either. Однако есть реализация Either в FunctionalJava , наряду со многими другими классными классами.


Ссылка на Either, похоже, была удалена. Вы уверены, что проект все еще поддерживается?
Flame_Phoenix

Я обновил ссылку. Проект все еще активно поддерживается AFAIK.
Рави Киран

Это так, но документация ужасна. :(
Миша Тавхелидзе

10

cyclops-react имеет «правильную» смещенную реализацию под названием Xor .

 Xor.primary("hello")
    .map(s->s+" world")

 //Primary["hello world"]

 Xor.secondary("hello")
    .map(s->s+" world")

 //Secondary["hello"]

 Xor.secondary("hello")
    .swap()
    .map(s->s+" world")

 //Primary["hello world"]

Xor.accumulateSecondary(ListX.of(Xor.secondary("failed1"),
                                 Xor.secondary("failed2"),
                                 Xor.primary("success")),
                                 Semigroups.stringConcat)

//failed1failed2

Существует также связанный тип Ior, который может действовать как кортеж или как кортеж2.

  • раскрытие Я автор cyclops-react.


6

Нет, нет.

Разработчики Java язык явно утверждают , что типы , такие как Option<T>предназначены для использования только в качестве временных значений (например , в потоке операций результатов), так что пока они являются то же самое , как и в других языках, они не должны быть использованы , поскольку они используются в других языков. Поэтому неудивительно, что нет такой вещи, Eitherпотому что она не возникает естественным образом (например, из потоковых операций), как это Optionalпроисходит.


8
У вас есть источник об этом?
Cannoliopsida

3
@akroy, похоже, это правильно, Брайан Гетц написал об этом в своем ответе: ссылка .
RonyHe 01

2
Для меня Eitherвозникает естественно. Может я не так делаю. Что делать, если метод может возвращать две разные вещи? Типа Either<List<String>, SomeOtherClass>?
tamas.kenez

3
Я бы также возразил, что Either возникает естественным образом для меня в потоке, где операция карты потенциально может вызвать исключение, поэтому я сопоставляю поток Either <Exception, Result>
Оливье Жерарден

6

Существует автономная реализация Eitherв небольшой библиотеке «амбивалентности»: http://github.com/poetix/ambivalence

Вы можете получить его из Maven central:

<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>ambivalence</artifactId>
    <version>0.2</version>
</dependency>

5

лямбда-компаньон имеет Eitherтип (и несколько других функциональных типов, например Try)

<dependency>
    <groupId>no.finn.lambda</groupId>
    <artifactId>lambda-companion</artifactId>
    <version>0.25</version>
</dependency>

Пользоваться им просто:

final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())

1
Проект больше не находится на обслуживании.
Flame_Phoenix
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.