Como entiendo más o menos 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.
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 una factorial(5)
expresión en el programa, siempre debe producir el mismo "resultado". Por lo tanto, si cierto factorial(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.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
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.
def first(y, z):
return y
def second(x):
return second(x)
first(2, second(3)) # result depends on eval. model
En el código anterior, first
y second
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:
sub foo {
my $x = shift;
return $x + $y; # y is dynamic scope var
}
sub a {
local $y = 10;
return &foo(50); # expanded to 60
}
sub b {
local $y = 20;
return &foo(50); # expanded to 70
}
El alcance dinámico confunde porque hace que sea fácil pensar x
que la única entrada es foo
, cuando en realidad, es x
y y
. 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 definir foo(x)
, definimos foo(x, y)
y pasamos y
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?
def foo():
global y
y = y + 1
return y
y = 10
foo() # yields 11
foo() # yields 12
El foo
procedimiento rompe RT porque hay redefiniciones. Es decir, definimos y
en un punto, y luego, redefinimos eso mismo y
. En el ejemplo anterior de perl, los y
s son enlaces diferentes aunque comparten el mismo nombre de letra "y". Aquí los y
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", tiene input -> ouput
en el contexto de una state
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 eso state
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.
RT
impide usar el.substitution model.
El gran problema de no poder usarsubstitution model
es el poder de usarlo para razonar sobre un programa.