Чи є еквівалент Scala's Either у Java 8?


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
@Mysterious Dan: заборона певного стану під час побудови об'єкта є найкращим способом у Java. В іншому випадку вам доведеться винаходити новий тип "дійсного діапазону" майже для кожного випадку використання, intоскільки використання всього діапазону значень intпри використанні intзмінної є винятком, як приклад. Зрештою, Optionalробить те саме, застосовуючи інваріанти під час будівництва об'єкта.
Holger

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...
Holger

1
@Holger: Я згоден, що не існує "більш правильного" способу, мені просто не сподобалося те, що Rightшлях коду виконується взагалі для Leftекземпляра, хоча помилка однакова. І так, я віддав би перевагу невдачі одразу, а не отриманню <empty, empty>та невдачі згодом. Але знову ж таки, це все лише питання смаку / стилю.
Чарлі

26

На момент написання статті vavr (раніше javaslang) - це, мабуть, найпопулярніша функціональна бібліотека Java 8. Це дуже схоже на лямбда-компаньйона Або в моїй іншій відповіді.

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


17

У стандартній бібліотеці Java немає жодного. Однак є реалізація Either у FunctionalJava , поряд з багатьма іншими приємними класами.


Здається, посилання на Either видалено. Ви впевнені, що проект все ще підтримується?
Flame_Phoenix

Я оновив посилання. Проект все ще активно підтримується AFAIK.
Раві Кіран,

Це так, але документація безглузда. :(
Міша Тавхелідзе

10

cyclops- response має «правильну» упереджену реалізацію під назвою 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.

  • розкриття інформації Я є автором реакції циклопа.


6

Ні, немає.

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


8
У вас є джерело про це?
Cannoliopsida

3
@akroy, це, здається, правильно, Брайан Гетц стільки ж написав у цій відповіді: посилання .
RonyHe

2
Для мене Eitherсправді виникає природно. Можливо, я роблю це неправильно. Що ви робите, коли метод може повернути дві різні речі? Подобається Either<List<String>, SomeOtherClass>,?
tamas.kenez

3
Я також заперечую проти того, що будь-яке з них виникає природним чином, для мене в потоці, де операція з картою може потенційно викликати виняток, тому я
прив'язую

6

Існує окрема реалізація Eitherв невеликій бібліотеці "ambivalence": 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.