Todavía no hay implementación estándar después de todo este tiempo :(
Cherry
Siempre hay java.lang.Object ... triste.
Alex R
Respuestas:
68
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 classEither<L,R>
{
publicstatic <L,R> Either<L,R> left(L value) {
returnnew Either<>(Optional.of(value), Optional.empty());
}
publicstatic <L,R> Either<L,R> right(R value) {
returnnew Either<>(Optional.empty(), Optional.of(value));
}
private final Optional<L> left;
private final Optional<R> right;
privateEither(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)
{
returnnew Either<>(left.map(lFunc),right);
}
public <T> Either<L,T> mapRight(Function<? super R, ? extends T> rFunc)
{
returnnew Either<>(left, right.map(rFunc));
}
publicvoidapply(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:
abstractclassEither<L,R>
{
publicstatic <L,R> Either<L,R> left(L value) {
returnnew Either<L,R>() {
@Overridepublic <T> T map(Function<? super L, ? extends T> lFunc,
Function<? super R, ? extends T> rFunc) {
return lFunc.apply(value);
}
};
}
publicstatic <L,R> Either<L,R> right(R value) {
returnnew Either<L,R>() {
@Overridepublic <T> T map(Function<? super L, ? extends T> lFunc,
Function<? super R, ? extends T> rFunc) {
return rFunc.apply(value);
}
};
}
privateEither() {}
publicabstract <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) {
returnthis.<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) {
returnthis.<Either<L,T>>map(t -> (Either<L,T>)this, t -> right(lFunc.apply(t)));
}
publicvoidapply(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); returnnull; };
}
}
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.
Tenga en cuenta que si bien esto se comporta así 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.
Dan misterioso
6
@Mysterious Dan: prohibir cierto tipo de estado durante la construcción de objetos es la forma preferida en Java. De lo contrario, tendría que inventar un nuevo tipo de "rango válido" para casi todos los casos de 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.
Holger
1
@Holger: 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).
charlie
2
@charlie: esta solución no considera que 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…
Holger
1
@Holger: Estoy de acuerdo en que no hay una forma "más correcta", simplemente no me gustó el hecho de que la 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.
charlie
26
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();
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.
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.
@akroy, esto parece ser correcto, Brian Goetz escribió tanto en esta respuesta: enlace .
RonyHe
2
Para mí Eithersurge naturalmente. Quizás lo estoy haciendo mal. ¿Qué haces cuando un método puede devolver dos cosas diferentes? Como Either<List<String>, SomeOtherClass>?
tamas.kenez
3
También objetaría que Either surge naturalmente, para mí en una secuencia donde una operación de mapa podría generar una excepción, por lo que mapeo a una secuencia de Either <Exception, Result>
java.lang.Object
... triste.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 denull
"vacío" que contradice el significado de "cualquiera".El siguiente código muestra un
Either
que consideranull
un valor posible, por lo que es estrictamente "o", izquierda o derecha, incluso si el valor esnull
: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 unObjects.requireNonNull(value)
al principio de ambos métodos de fábrica. Del mismo modo, sería imaginable agregar soporte para uno vacío.fuente
Either
, el tipo es en cierto sentido "demasiado grande", ya que sus camposleft
yright
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 dea + b
salir(1 + a) * (1 + b)
. Claro,a + b
ocurre en el resultado de esa expresión, pero también lo es1
ya * b
.int
uso, ya que usar todo el rango de valoresint
cuando se usa unaint
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)
lanzaNoSuchElementException
(correcto) enthis.right.get()
(incorrecto). Además, se puede evitar la aplicación de invariantes y producirEither<empty, empty>
porEither.left(42).mapLeft(left -> null)
. O cuando se junta, vuelve a fallarEither.left(42).mapLeft(left -> null).map(left -> left, right -> right)
.Optional.map
permita que la función regresenull
, volviéndola vacíaOptional
. 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 anull
…Right
ruta del código se ejecute en absoluto para unaLeft
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();
fuente
Ver Atlassian Fugue . Hay una buena implementación de
Either
allí.fuente
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.
fuente
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.
fuente
Xor
fue renombrado aEither
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 existaEither
porque no surge de forma natural (por ejemplo, de operaciones de flujo) como loOptional
hace.fuente
Either
surge naturalmente. Quizás lo estoy haciendo mal. ¿Qué haces cuando un método puede devolver dos cosas diferentes? ComoEither<List<String>, SomeOtherClass>
?Hay una implementación independiente de
Either
en una pequeña biblioteca, "ambivalencia": http://github.com/poetix/ambivalencePuedes conseguirlo en Maven central:
<dependency> <groupId>com.codepoetics</groupId> <artifactId>ambivalence</artifactId> <version>0.2</version> </dependency>
fuente
lambda-companion tiene un
Either
tipo (y algunos otros tipos funcionales, por ejemploTry
)<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())
fuente