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 Either
tipo 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 Optional
tipo 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 Optional
solució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 Either
que considera null
un 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 null
simplemente 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 left
y right
podrí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 + b
salir (1 + a) * (1 + b)
. Claro, a + b
ocurre en el resultado de esa expresión, pero también lo es 1
y a * b
.
int
uso, ya que usar todo el rango de valores int
cuando se usa una int
variable es la excepción, solo como un ejemplo. Después de todo, Optional
hace 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.map
permita 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
…
Right
ruta del código se ejecute en absoluto para una Left
instancia, 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 Either
allí.
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.
Xor
fue renombrado a Either
en 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 Either
porque no surge de forma natural (por ejemplo, de operaciones de flujo) como lo Optional
hace.
Either
surge 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 Either
en 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 Either
tipo (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())