La programación funcional en Scala explica el impacto de un efecto secundario en la ruptura de la transparencia referencial:
efecto secundario, lo que implica alguna violación de la transparencia referencial.
He leído parte de SICP , que analiza el uso del "modelo de sustitución" para evaluar un programa.
Como entiendo a grandes rasgos el modelo de sustitución con transparencia referencial (RT), puede descomponer una función en sus partes más simples. Si la expresión es RT, puede descomponer la expresión y obtener siempre el mismo resultado.
Sin embargo, como dice la cita anterior, el uso de efectos secundarios puede / romperá el modelo de sustitución.
Ejemplo:
val x = foo(50) + bar(10)
Si foo
y bar
no tiene efectos secundarios, la ejecución de cualquiera de las funciones siempre devolverá el mismo resultado x
. Pero, si tienen efectos secundarios, alterarán una variable que interrumpe / arroja una llave en el modelo de sustitución.
Me siento cómodo con esta explicación, pero no la entiendo completamente.
Por favor, corríjame y complete los agujeros con respecto a los efectos secundarios que rompen la RT, discutiendo también los efectos en el modelo de sustitución.
fuente
RT
impide usar el.substitution model.
El gran problema de no poder usarsubstitution model
es el poder de usarlo para razonar sobre un programa.Imagine que está tratando de construir un muro y le han dado una variedad de cajas de diferentes tamaños y formas. Necesita llenar un agujero particular en forma de L en la pared; ¿Debería buscar una caja en forma de L o puede sustituir dos cajas rectas del tamaño apropiado?
En el mundo funcional, la respuesta es que cualquiera de las soluciones funcionará. Al construir su mundo funcional, nunca tiene que abrir las cajas para ver qué hay dentro.
En el mundo imperativo, es peligroso construir su muro sin inspeccionar el contenido de cada caja y compararlos con el contenido de cada otra caja:
Creo que me detendré antes de perder su tiempo con metáforas más improbables, pero espero que el punto esté claro; Los ladrillos funcionales no contienen sorpresas ocultas y son completamente predecibles. Debido a que siempre puede usar bloques más pequeños del tamaño y forma correctos para sustituir a uno más grande y no hay diferencia entre dos cuadros del mismo tamaño y forma, tiene transparencia referencial. Con los ladrillos imperativos, no es suficiente tener algo del tamaño y la forma correctos, debe saber cómo se construyó el ladrillo. No es referencialmente transparente.
En un lenguaje funcional puro, todo lo que necesita ver es la firma de una función para saber qué hace. Por supuesto, es posible que desee mirar hacia adentro para ver qué tan bien funciona, pero no tiene que mirar.
En un lenguaje imperativo, nunca se sabe qué sorpresas pueden esconderse dentro.
fuente
(a, b) -> a
solo puede ser lafst
función y que una función de tipoa -> a
solo puede ser laidentity
función, pero no se puede decir necesariamente nada sobre una función de tipo(a, a) -> a
, por ejemplo.Sí, la intuición es bastante correcta. Aquí hay algunos consejos para ser más precisos:
Como dijiste, cualquier expresión RT debería tener un
single
"resultado". Es decir, dada unafactorial(5)
expresión en el programa, siempre debe producir el mismo "resultado". Por lo tanto, si ciertofactorial(5)
está en el programa y produce 120, siempre debe producir 120 independientemente de qué "orden de pasos" se expanda / calcule, independientemente del tiempo .Ejemplo: la
factorial
función.Hay algunas consideraciones con esta explicación.
En primer lugar, tenga en cuenta que los diferentes modelos de evaluación (ver el orden aplicativo versus el orden normal) pueden producir diferentes "resultados" para la misma expresión RT.
En el código anterior,
first
ysecond
son referencialmente transparentes, y sin embargo, la expresión al final produce diferentes "resultados" si se evalúa bajo orden normal y orden aplicativo (bajo este último, la expresión no se detiene)..... lo que lleva al uso de "resultado" entre comillas. Como no se requiere que una expresión se detenga, es posible que no produzca un valor. Así que usar "resultado" es algo borroso. Se puede decir que una expresión RT siempre produce lo mismo
computations
bajo un modelo de evaluación.En tercer lugar, puede ser necesario ver dos que
foo(50)
aparecen en el programa en diferentes ubicaciones como diferentes expresiones, cada una de las cuales produce sus propios resultados que pueden diferir entre sí. Por ejemplo, si el lenguaje permite un alcance dinámico, ambas expresiones, aunque léxicamente idénticas, son diferentes. En perl:El alcance dinámico confunde porque hace que sea fácil pensar
x
que la única entrada esfoo
, cuando en realidad, esx
yy
. Una forma de ver la diferencia es transformar el programa en uno equivalente sin alcance dinámico, es decir, pasar explícitamente los parámetros, por lo que en lugar de definirfoo(x)
, definimosfoo(x, y)
y pasamosy
explícitamente en las personas que llaman.El punto es que siempre estamos bajo una
function
mentalidad: dada una cierta entrada para una expresión, se nos da un "resultado" correspondiente. Si damos la misma entrada, siempre debemos esperar el mismo "resultado".Ahora, ¿qué pasa con el siguiente código?
El
foo
procedimiento rompe RT porque hay redefiniciones. Es decir, definimosy
en un punto, y luego, redefinimos eso mismoy
. En el ejemplo anterior de perl, losy
s son enlaces diferentes aunque comparten el mismo nombre de letra "y". Aquí losy
s son en realidad los mismos. Es por eso que decimos que la (re) asignación es una meta operación: de hecho, está cambiando la definición de su programa.Aproximadamente, las personas generalmente representan la diferencia de la siguiente manera: en un entorno sin efectos secundarios, tiene un mapeo
input -> output
. En un entorno "imperativo", tieneinput -> ouput
en el contexto de unastate
que puede cambiar con el tiempo.Ahora, en lugar de simplemente sustituir las expresiones por sus valores correspondientes, uno también tiene que aplicar transformaciones a
state
cada operación que lo requiera (y, por supuesto, las expresiones pueden consultar esostate
para realizar cálculos).Entonces, si en un programa libre de efectos secundarios todo lo que necesitamos saber para calcular una expresión es su entrada individual, en un programa imperativo, necesitamos conocer las entradas y el estado completo, para cada paso computacional. El razonamiento es el primero en sufrir un gran golpe (ahora, para depurar un procedimiento problemático, necesita la entrada y el volcado del núcleo). Ciertos trucos se vuelven poco prácticos, como la memorización. Pero también, la concurrencia y el paralelismo se vuelven mucho más desafiantes.
fuente