Al igual que java.util.Optional<T>en Java 8 es (algo) equivalente al Option[T]tipo de Scala , ¿hay un equivalente al de Scala Either[L, R]?
Respuestas:
No hay ningún Eithertipo que sea Java 8, por lo que debe crear uno usted mismo o usar alguna biblioteca de terceros.
Puede crear una característica de este Optionaltipo utilizando el nuevo tipo (pero lea hasta el final de esta respuesta):
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);
}
}
Ejemplo de caso de uso:
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));}
));
En retrospectiva, la Optionalsolución basada es más un ejemplo académico, pero no un enfoque recomendado. Un problema es el tratamiento de null"vacío" que contradice el significado de "cualquiera".
El siguiente código muestra un Eitherque considera nullun valor posible, por lo que es estrictamente "o", izquierda o derecha, incluso si el valor es 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; };
}
}
Es fácil cambiar eso a un rechazo estricto nullsimplemente insertando un Objects.requireNonNull(value)al principio de ambos métodos de fábrica. Del mismo modo, sería imaginable agregar soporte para uno vacío.
Either, el tipo es en cierto sentido "demasiado grande", ya que sus campos lefty rightpodrían, en principio, estar ambos vacíos o ambos definidos. Ha ocultado los constructores que lo harían posible, pero el enfoque aún deja potencial para errores en su implementación. En términos aritméticos de tipo simple, estás tratando de a + bsalir (1 + a) * (1 + b). Claro, a + bocurre en el resultado de esa expresión, pero también lo es 1y a * b.
intuso, ya que usar todo el rango de valores intcuando se usa una intvariable es la excepción, solo como un ejemplo. Después de todo, Optionalhace lo mismo, aplicando las invariantes durante la construcción del objeto.
Either.left(42).map(left -> null, right -> right)lanza NoSuchElementException(correcto) en this.right.get()(incorrecto). Además, se puede evitar la aplicación de invariantes y producir Either<empty, empty>por Either.left(42).mapLeft(left -> null). O cuando se junta, vuelve a fallar Either.left(42).mapLeft(left -> null).map(left -> left, right -> right).
Optional.mappermita que la función regrese null, volviéndola vacía Optional. Sin embargo, además de la oportunidad de detectarlo y lanzarlo de inmediato, no veo ninguna solución alternativa que sea “más correcta”. Afaik, no hay un comportamiento de referencia, como en Scala, no se puede asignar a null…
Rightruta del código se ejecute en absoluto para una Leftinstancia, aunque el error es el mismo. Y sí, preferiría fracasar de inmediato en lugar de recibir una <empty, empty>y fracasar más tarde. Pero de nuevo, todo es solo una cuestión de gusto / estilo.
En el momento de escribir este artículo, vavr (anteriormente javaslang) es probablemente la biblioteca funcional de Java 8 más popular. Es bastante similar a Either de lambda-companion en mi otra respuesta.
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
Ver Atlassian Fugue . Hay una buena implementación de Eitherallí.
No hay ninguno en la biblioteca estándar de Java. Sin embargo, hay una implementación de Either en FunctionalJava , junto con muchas otras clases interesantes.
cyclops-react tiene una implementación sesgada 'a la derecha' llamada 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
También hay un tipo Ior relacionado que puede actuar como una o una tupla2.
Xorfue renombrado a Eitheren Cyclops X: static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/...
No, no hay ninguno.
Los desarrolladores del lenguaje Java afirman explícitamente que los tipos como Option<T>están destinados a usarse solo como valores temporales (por ejemplo, en los resultados de las operaciones de flujo), por lo que si bien son lo mismo que en otros lenguajes, se supone que no deben usarse como se usan en otros Idiomas Por lo tanto, no es sorprendente que no exista Eitherporque no surge de forma natural (por ejemplo, de operaciones de flujo) como lo Optionalhace.
Eithersurge naturalmente. Quizás lo estoy haciendo mal. ¿Qué haces cuando un método puede devolver dos cosas diferentes? Como Either<List<String>, SomeOtherClass>?
Hay una implementación independiente de Eitheren una pequeña biblioteca, "ambivalencia": http://github.com/poetix/ambivalence
Puedes conseguirlo en Maven central:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>
lambda-companion tiene un Eithertipo (y algunos otros tipos funcionales, por ejemplo Try)
<dependency>
<groupId>no.finn.lambda</groupId>
<artifactId>lambda-companion</artifactId>
<version>0.25</version>
</dependency>
Usarlo es fácil:
final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())