Tak jak java.util.Optional<T>
w Javie 8 jest (w pewnym stopniu) odpowiednikiem typu Scali Option[T]
, czy istnieje odpowiednik Scali Either[L, R]
?
Odpowiedzi:
Nie ma Either
typu Java 8, więc musisz utworzyć ją samodzielnie lub skorzystać z biblioteki innej firmy.
Możesz zbudować taką funkcję za pomocą nowego Optional
typu (ale przeczytaj do końca tej odpowiedzi):
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);
}
}
Przykład użycia:
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));}
));
Z perspektywy czasu, Optional
rozwiązanie oparte bardziej przypomina akademicki przykład, ale nie jest zalecane. Jednym z problemów jest traktowanie null
jako „pustego”, co jest sprzeczne ze znaczeniem „albo”.
Poniższy kod przedstawia kod Either
uwzględniający null
możliwą wartość, więc jest to ściśle „albo”, lewy lub prawy, nawet jeśli wartość to 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; };
}
}
Łatwo to zmienić na ścisłe odrzucenie null
, po prostu wstawiając znak Objects.requireNonNull(value)
na początku obu metod fabrycznych. Podobnie, można sobie wyobrazić dodanie obsługi pustego obu.
Either
, typ jest w pewnym sensie „za duży”, ponieważ pola left
i right
pola mogą w zasadzie być puste lub oba mogą być zdefiniowane. Ukryłeś konstruktory, które by to umożliwiały, ale to podejście nadal pozostawia potencjalne błędy w twojej implementacji. W typie prostych arytmetycznych warunkach, starasz się dostać a + b
z (1 + a) * (1 + b)
. Jasne, a + b
pojawia się w wyniku tego wyrażenia, ale tak jest 1
i a * b
.
int
ponieważ używanie całego zakresu wartości, int
gdy użycie int
zmiennej jest wyjątkiem, tak jak na przykładzie. W końcu Optional
robi to samo, wymuszając niezmienniki podczas budowy obiektu.
Either.left(42).map(left -> null, right -> right)
rzuca NoSuchElementException
(poprawne) na this.right.get()
(niepoprawne). Można również ominąć wymuszanie i wytwarzanie niezmienników Either<empty, empty>
do Either.left(42).mapLeft(left -> null)
. Lub po złożeniu, znowu nie Either.left(42).mapLeft(left -> null).map(left -> left, right -> right)
.
Optional.map
możliwości powrotu funkcji null
, zmieniając ją w pustą Optional
. Jednak poza możliwością wykrycia tego i natychmiastowego wyrzucenia nie widzę żadnego alternatywnego rozwiązania, które byłoby „bardziej poprawne”. Ahaik, nie ma zachowania referencyjnego, tak jak w Scali, nie możesz zmapować do null
…
Right
ścieżka kodu jest w ogóle wykonywana na Left
przykład, mimo że błąd jest ten sam. I tak, wolałbym ponieść porażkę od razu, zamiast otrzymać <empty, empty>
i oblać później. Ale znowu wszystko jest kwestią gustu / stylu.
W chwili pisania tego tekstu vavr (dawniej javaslang) jest prawdopodobnie najpopularniejszą funkcjonalną biblioteką Java 8. Jest całkiem podobny do lambda-companion albo w mojej drugiej odpowiedzi.
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
Zobacz Atlassian Fugue . Jest tam dobra realizacja Either
.
W bibliotece Java Standard Library nie ma żadnego żadnego. Jednak istnieje implementacja Either w FunctionalJava , wraz z wieloma innymi ładnymi klasami.
Cyklop reagują ma „prawo” tendencyjne albo realizację nazwie 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
Istnieje również pokrewny typ Ior, który może działać jako albo albo krotka2.
Xor
została zmieniona na Either
w Cyclops X: static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/ ...
Nie, nie ma.
Programiści języka Java wyraźnie stwierdzają, że typy takie jak Option<T>
mają być używane tylko jako wartości tymczasowe (np. W wynikach operacji strumieniowych), więc chociaż są tym samym, co w innych językach, nie powinny być używane tak, jak są używane w innych Języki. Nic więc dziwnego, że nie ma czegoś takiego jakEither
to, że nie powstaje naturalnie (np. Z operacji strumieniowych) tak jak Optional
ma.
Either
pojawia się naturalnie. Może robię to źle. Co robisz, gdy metoda może zwrócić dwie różne rzeczy? Podobnie jak Either<List<String>, SomeOtherClass>
?
W Either
małej bibliotece istnieje samodzielna implementacja „ambiwalencji”: http://github.com/poetix/ambivalence
Możesz go zdobyć z centrali Maven:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>
lambda-companion ma Either
typ (i kilka innych funkcjonalnych typów np. Try
)
<dependency>
<groupId>no.finn.lambda</groupId>
<artifactId>lambda-companion</artifactId>
<version>0.25</version>
</dependency>
Korzystanie z niego jest łatwe:
final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())