lazy - ¿Tiene Java una evaluación perezosa?
lazy evaluation (9)
¿Se llama a isATrue () si isBTrue () devuelve true?
Sí, ambos se llaman.
Sé que Java tiene una evaluación inteligente / perezosa en este caso:
public boolean isTrue() {
boolean a = false;
boolean b = true;
return b || (a && b); // (a && b) is not evaluated since b is true
}
Pero que pasa:
public boolean isTrue() {
boolean a = isATrue();
boolean b = isBTrue();
return b || a;
}
¿ isATrue()
llama a isATrue()
incluso si isBTrue()
devuelve true?
Bueno, en lo que concierne al lenguaje, sí, ambas funciones se llaman.
Si reescribes la función a esto:
public boolean isTrue() {
return isBTrue() || isATrue();
}
entonces la segunda función no será llamada, si la primera es verdadera.
Pero esto es una evaluación de cortocircuito , no una evaluación perezosa . El caso de evaluación perezoso se vería así:
public interface LazyBoolean {
boolean eval();
}
class CostlyComparison implements LazyBoolean {
private int a, b;
public CostlyComparison(int a, int b) {
this.a=a;
this.b=b;
}
@Override
public boolean eval() {
//lots of probably not-always-necessary computation here
return a > b;
}
}
public LazyBoolean isATrue() {
return new CostlyComparison(10,30); //just an example
}
public boolean isTrue() { // so now we only pay for creation of 2 objects
LazyBoolean a = isATrue(); // but the computation is not performed;
LazyBoolean b = isBTrue(); // instead, it''s encapsulated in a LazyBoolean
return b.eval() || a.eval(); // and will be evaluated on demand;
// this is the definition of lazy eval.
}
En Java (y otros lenguajes tipo C), esto se conoce como evaluación de cortocircuito . *
Y sí, en el segundo ejemplo isATrue
siempre se llama. Es decir, a menos que el compilador / JVM pueda determinar que no tiene efectos secundarios observables, en cuyo caso puede elegir optimizar, pero en ese caso no notaría la diferencia de todos modos.
Originalmente sugerí que esto era bastante distinto de la evaluación perezosa, pero como @Ingo señala en los comentarios a continuación, es una aseveración dudosa. Uno puede ver a los operadores de cortocircuito en Java como una aplicación muy limitada de evaluación perezosa.
Sin embargo, cuando los lenguajes funcionales ordenan una semántica de evaluación perezosa, generalmente es por una razón muy diferente, a saber, la prevención de la recursión infinita (o al menos, excesiva).
No, Java solo tiene una ávida evaluación de los métodos definidos por el usuario. Algunas de las construcciones de lenguaje de Java implementan una evaluación no estricta a medida que observa. Otros incluyen if
, ?:
, while
.
Una vez aprendí [1] que hay cierta confusión en torno a lo que significa "tener una evaluación perezosa". En primer lugar, la evaluación perezosa significa la evaluación de la llamada por necesidad . Java no tiene nada como esto. Sin embargo, hay una tendencia común (que personalmente desaliento) a aflojar la definición de evaluación perezosa para incluir también la evaluación llamada por nombre . Las funciones como &&
no se pueden distinguir en la evaluación de llamada por necesidad frente a la de llamada por nombre; Sería lo mismo independientemente, lo que oscurece el asunto.
Teniendo en cuenta este aflojamiento, algunos argumentos contrapuestos adicionales al afirmar que Java tiene una evaluación perezosa por lo siguiente:
interface Thunk<A> {
A value();
}
Luego, puede escribir un &&
definido por el usuario como:
boolean and(boolean p, Thunk<Boolean> q) {
return p && q();
}
Luego se presenta la afirmación de que esto demuestra que Java tiene una evaluación perezosa. Sin embargo, esto no es una evaluación perezosa, incluso en el sentido general. El punto distintivo aquí es que el sistema de tipos de Java no unifica los tipos boolean
/ Boolean
y Thunk<Boolean>
. Intentar usar uno como el otro resultará en un error de tipo . En ausencia de un sistema de tipo estático, el código todavía fallaría. Es esta falta de unificación (escritura estática o no) lo que responde la pregunta; No, Java no tiene una evaluación perezosa definida por el usuario. Por supuesto, se puede emular como anteriormente, pero esta es una observación poco interesante que se sigue de la equivalencia de turing.
Un lenguaje como Scala tiene una evaluación llamada por nombre, que permite una función definida por el usuario and
que es equivalente a la función normal de &&
(teniendo en cuenta la terminación. Consulte [1]).
// note the => annotation on the second argument
def and(p: Boolean, q: => Boolean) =
p && q
Esto permite:
def z: Boolean = z
val r: Boolean = and(false, z)
Tenga en cuenta que este breve fragmento de programa termina al proporcionar un valor. También unifica los valores de tipo Boolean
como llamada por nombre. Por lo tanto, si se suscribe a la definición flexible de evaluación perezosa (y nuevamente, lo desaconsejo), podría decir que Scala tiene una evaluación perezosa. Ofrezco Scala aquí como un buen contraste. Recomiendo mirar a Haskell para una verdadera evaluación perezosa (llamada por necesidad).
¡Espero que esto ayude!
[1] http://blog.tmorris.net/posts/a-fling-with-lazy-evaluation/
No, no lo hace. isBTrue()
, independientemente del resultado de isATrue()
. Puede verificar esto usted mismo escribiendo dicho programa, con declaraciones impresas en cada uno de los métodos.
Para simplificar, puede utilizar la interfaz del proveedor de Java 8 de esta manera:
Supplier<SomeVal> someValSupplier = () -> getSomeValLazily();
Luego, en el código posterior puede tener:
if (iAmLazy)
someVal = someValSupplier.get(); // lazy getting the value
else
someVal = getSomeVal(); // non lazy getting the value
Sí se isATrue()
porque lo está llamando explícitamente en la línea boolean a = isATrue();
Pero no se llamará en el siguiente caso si isBTrue()
devuelve true
:
public boolean isTrue() {
return isBTrue() || isATrue();
}
SE8 (JDK1.8) ha introducido expresiones Lambda , que pueden hacer que las evaluaciones perezosas sean más transparentes. Considere la declaración en el método principal del siguiente código:
@FunctionalInterface
public interface Lazy<T> {
T value();
}
class Test {
private String veryLongMethod() {
//Very long computation
return "";
}
public static <T> T coalesce(T primary, Lazy<T> secondary) {
return primary != null? primary : secondary.value();
}
public static void main(String[] argv) {
String result = coalesce(argv[0], ()->veryLongMethod());
}
}
La función llamada coalesce devuelve el primer valor no nulo dado (como en SQL). El segundo parámetro en su llamada es una expresión Lambda. El método veryLongMethod () se llamará solo cuando argv [0] == null. La única carga útil en este caso es insertar ()->
antes del valor a evaluar perezosamente a pedido.
Solo quería agregar, además de lo que se ha mencionado en este hilo de preguntas, a continuación es de la documentación de Oracle en JVM
una implementación de la Máquina Virtual Java puede elegir resolver cada referencia simbólica en una clase o interfaz individualmente cuando se usa (resolución "perezosa" o "tardía"), o resolverlas todas de una vez cuando la clase está siendo verificada ("ansioso") o resolución "estática"). Esto significa que el proceso de resolución puede continuar, en algunas implementaciones, después de que se haya inicializado una clase o interfaz.
y como ejemplo de las clases que tienen implementación lenta es Stream, esto es de Oracle Documentation on Stream
Las corrientes son perezosas; el cálculo de los datos de origen solo se realiza cuando se inicia la operación del terminal, y los elementos de origen se consumen solo cuando es necesario.
Dicho esto, si haces lo siguiente, no se mostrará nada. A menos que agregue iniciador.
Steam.of(1, 2, 3, 4, 5).filter(number -> {
System.out.println("This is not going to be logged");
return true;
});