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

81

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

HRJ
источник
Все еще нет стандартной реализации за все это время :(
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