Так само, як java.util.Optional<T>
у Java 8 (дещо) еквівалент Option[T]
типу Scala , чи існує еквівалент Scala Either[L, R]
?
Відповіді:
Немає 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)
на початку обох заводських методів знак . Так само можна було б уявити додавання підтримки для порожнього.
Either
, тип в якомусь сенсі є "занадто великим", оскільки ваші left
і right
поля в принципі можуть бути порожніми або обидва бути визначеними. Ви приховали конструктори, які б це зробили можливим, але підхід все ще залишає потенційні помилки у вашій реалізації. У простих типів арифметичних термінів, ви намагаєтеся отримати a + b
з (1 + a) * (1 + b)
. Звичайно, це a + b
відбувається в результаті цього виразу, але так само є 1
і a * b
.
int
оскільки використання всього діапазону значень int
при використанні int
змінної є винятком, як приклад. Зрештою, Optional
робить те саме, застосовуючи інваріанти під час будівництва об'єкта.
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)
.
Optional.map
можливості повернення функції null
, перетворюючи її на порожню Optional
. Однак, крім можливості виявити це і кинути негайно, я не бачу жодного альтернативного рішення, яке було б "більш правильним". Afaik, немає посилальної поведінки, як у Scala, ти не можеш зіставити null
...
Right
шлях коду виконується взагалі для Left
екземпляра, хоча помилка однакова. І так, я віддав би перевагу невдачі одразу, а не отриманню <empty, empty>
та невдачі згодом. Але знову ж таки, це все лише питання смаку / стилю.
Див. Атласова фуга . Там є хороша реалізація Either
.
У стандартній бібліотеці Java немає жодного. Однак є реалізація Either у FunctionalJava , поряд з багатьма іншими приємними класами.
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.
Xor
було перейменовано на Either
Cyclops X: static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/…
Ні, немає.
Розробники мови Java прямо заявляють, що типи типу Option<T>
призначені для використання лише як тимчасові значення (наприклад, у результатах потокових операцій), тому, хоча вони такі самі, як і в інших мовах, вони не повинні використовуватися, як вони використовуються в інших мови. Тож не дивно, що такого поняття не існує, Either
оскільки воно не виникає природним шляхом (наприклад, з потокових операцій), як Optional
це відбувається.
Either
справді виникає природно. Можливо, я роблю це неправильно. Що ви робите, коли метод може повернути дві різні речі? Подобається Either<List<String>, SomeOtherClass>
,?
Існує окрема реалізація Either
в невеликій бібліотеці "ambivalence": http://github.com/poetix/ambivalence
Ви можете отримати його у Maven central:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>
лямбда-супутник має 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())