Lo he visto en paradigmas imperativos
f (x) + f (x)
podría no ser lo mismo que:
2 * f (x)
Pero en un paradigma funcional debería ser lo mismo. He tratado de implementar ambos casos en Python y Scheme , pero para mí se ven bastante directos.
¿Cuál sería un ejemplo que pudiera señalar la diferencia con la función dada?
f(x++)+f(x++)
podría no ser la misma que2*f(x++)
(? en C es especialmente bonito cuando cosas como que se oculta dentro de macros - hizo que me rompí la nariz en que se apuesta)f(x++)+f(x++)
puede ser absolutamente cualquier cosa, ya que invoca un comportamiento indefinido. Pero eso no está realmente relacionado con la transparencia referencial, lo que no ayudaría para esta convocatoria, está 'indefinido' para funciones referencialmente transparentes comosin(x++)+sin(x++)
también en. Podría ser 42, podría formatear su disco duro, podría tener demonios volando de la nariz del usuario ...Respuestas:
La transparencia referencial, referida a una función, indica que puede determinar el resultado de aplicar esa función solo observando los valores de sus argumentos. Puede escribir funciones referencialmente transparentes en cualquier lenguaje de programación, por ejemplo, Python, Scheme, Pascal, C.
Por otro lado, en la mayoría de los idiomas también puede escribir funciones transparentes no referenciales. Por ejemplo, esta función de Python:
no es referencialmente transparente, de hecho llama
y
producirá diferentes valores, para cualquier argumento
x
. La razón de esto es que la función usa y modifica una variable global, por lo tanto, el resultado de cada invocación depende de este estado cambiante, y no solo del argumento de la función.Haskell, un lenguaje puramente funcional, separa estrictamente la evaluación de expresiones en la que se aplican funciones puras y que siempre es referencialmente transparente, de la ejecución de la acción (procesamiento de valores especiales), que no es referencialmente transparente, es decir, ejecutar la misma acción puede tener cada vez que Resultado diferente.
Entonces, para cualquier función de Haskell
y cualquier número entero
x
, siempre es cierto queUn ejemplo de una acción es el resultado de la función de biblioteca
getLine
:Como resultado de la evaluación de expresiones, esta función (en realidad una constante) produce en primer lugar un valor puro de tipo
IO String
. Los valores de este tipo son valores como cualquier otro: puede pasarlos, colocarlos en estructuras de datos, componerlos usando funciones especiales, etc. Por ejemplo, puede hacer una lista de acciones de esta manera:Las acciones son especiales, ya que puede indicarle al tiempo de ejecución de Haskell que las ejecute escribiendo:
En este caso, cuando se inicia su programa Haskell, el tiempo de ejecución recorre la acción vinculada
main
y la ejecuta , posiblemente produciendo efectos secundarios. Por lo tanto, la ejecución de la acción no es referencialmente transparente porque ejecutar la misma acción dos veces puede producir resultados diferentes dependiendo de lo que el tiempo de ejecución obtenga como entrada.Gracias al sistema de tipos de Haskell, una acción nunca se puede usar en un contexto donde se espera otro tipo, y viceversa. Entonces, si desea encontrar la longitud de una cadena, puede usar la
length
función:devolverá 5. Pero si desea encontrar la longitud de una cadena leída desde el terminal, no puede escribir
porque obtiene un error de tipo:
length
espera una entrada de la lista de tipos (y una Cadena es, de hecho, una lista) perogetLine
es un valor de tipoIO String
(una acción). De esta manera, el sistema de tipos asegura que un valor de acción comogetLine
(cuya ejecución se lleva a cabo fuera del lenguaje central y que puede ser transparente no referencial) no puede ocultarse dentro de un valor de tipo de no acciónInt
.EDITAR
Para responder a una pregunta exizt, aquí hay un pequeño programa Haskell que lee una línea desde la consola e imprime su longitud.
La acción principal consta de dos subacciones que se ejecutan secuencialmente:
getline
de tipoIO String
,putStrLn
de tipoString -> IO ()
en su argumento.Más precisamente, la segunda acción es construida por
line
al valor leído por la primera acción,length
(calcular la longitud como un entero) y luegoshow
(convertir el entero en una cadena),putStrLn
al resultado deshow
.En este punto, se puede ejecutar la segunda acción. Si ha escrito "Hola", imprimirá "5".
Tenga en cuenta que si obtiene un valor de una acción usando la
<-
notación, solo puede usar ese valor dentro de otra acción, por ejemplo, no puede escribir:porque
show (length line)
tiene tipoString
mientras que la notación do requiere que una acción (getLine
de tipoIO String
) sea seguida por otra acción (por ejemplo,putStrLn (show (length line))
de tipoIO ()
).EDITAR 2
La definición de Jörg W Mittag de transparencia referencial es más general que la mía (he votado su respuesta). He usado una definición restringida porque el ejemplo en la pregunta se enfoca en el valor de retorno de las funciones y quería ilustrar este aspecto. Sin embargo, RT en general se refiere al significado de todo el programa, incluidos los cambios en el estado global y las interacciones con el entorno (IO) causadas por la evaluación de una expresión. Entonces, para una definición correcta y general, debe referirse a esa respuesta.
fuente
IO
tipo de Haskell con bastante facilidad en cualquier idioma con lambdas y genéricos, pero como cualquiera puede llamarprintln
directamente, la implementaciónIO
no garantiza la pureza; sería simplemente una convención.getLine
no ser referencialmente transparente es incorrecto. Está presentandogetLine
como si se evalúa o se reduce a una Cadena, cuya Cadena particular depende de la entrada del usuario. Esto es incorrecto.IO String
no contiene una cadena más deMaybe String
lo que lo hace.IO String
es una receta para tal vez, posiblemente obtener una Cadena y, como expresión, tan pura como cualquier otra en Haskell.Sin embargo, eso no es lo que significa Transparencia referencial. RT significa que puede reemplazar cualquier expresión en el programa con el resultado de evaluar esa expresión (o viceversa) sin cambiar el significado del programa.
Tomemos, por ejemplo, el siguiente programa:
Este programa es referencialmente transparente. Puedo reemplazar una o ambas ocurrencias de
f()
con2
y todavía funcionará igual:o
o
Todos se comportarán igual.
Bueno, en realidad, hice trampa. Debería poder reemplazar la llamada a
print
con su valor de retorno (que no tiene ningún valor) sin cambiar el significado del programa. Sin embargo, claramente, si solo elimino las dosprint
declaraciones, el significado del programa cambiará: antes, imprimió algo en la pantalla, después no lo hace. I / O no es referencialmente transparente.La regla general es: si puede reemplazar cualquier expresión, subexpresión o llamada de subrutina con el valor de retorno de esa expresión, subexpresión o llamada de subrutina en cualquier parte del programa, sin que el programa cambie su significado, entonces tiene referencial transparencia. Y lo que esto significa, prácticamente hablando, es que no puede tener ninguna E / S, no puede tener ningún estado mutable, no puede tener ningún efecto secundario. En cada expresión, el valor de la expresión debe depender únicamente de los valores de las partes constituyentes de la expresión. Y en cada llamada de subrutina, el valor de retorno debe depender únicamente de los argumentos.
fuente
print
ejemplo. Quizás una forma de ver esto, es que lo que está impreso en la pantalla es parte del "valor de retorno". Si puede reemplazarprint
con su función el valor de retorno y la escritura equivalente en el terminal, el ejemplo funciona.4
y2 + 2
no intercambiable, ya que tienen diferentes tiempos de funcionamiento, y el punto central de la transparencia referencial es que se puede sustituir una expresión con lo que se evalúa como. La consideración importante sería la seguridad del hilo.listOfSequence.append(n)
rendimientosNone
, por lo que debe ser capaz de reemplazar a cada llamadalistOfSequence.append(n)
conNone
sin cambiar el sentido de su programa. ¿Puedes hacer eso? Si no, entonces no es referencialmente transparente.Parte de esta respuesta se toma directamente de un tutorial inacabado sobre programación funcional , alojado en mi cuenta de GitHub:
Considere un ejemplo simple:
En un lenguaje funcional puro, el lado izquierdo y derecho del signo igual son sustituibles entre sí en ambos sentidos. Es decir, a diferencia de un lenguaje como C, la notación anterior realmente afirma una igualdad. Una consecuencia de esto es que podemos razonar sobre el código del programa al igual que las ecuaciones matemáticas.
De la wiki de Haskell :
Para contrastar esto, el tipo de operación realizada por lenguajes tipo C a veces se denomina asignación destructiva .
El término puro se usa a menudo para describir una propiedad de expresiones, relevante para esta discusión. Para que una función se considere pura,
De acuerdo con la metáfora de la caja negra, que se encuentra en numerosos libros de texto matemáticos, los elementos internos de una función están completamente sellados del mundo exterior. Un efecto secundario es cuando una función o expresión viola este principio, es decir, el procedimiento puede comunicarse de alguna manera con otras unidades del programa (por ejemplo, compartir e intercambiar información).
En resumen, la transparencia referencial es imprescindible para que las funciones se comporten como verdaderas funciones matemáticas también en la semántica de los lenguajes de programación.
fuente
[here](link to source)
..." seguido de 2) el formato correcto de las comillas (use comillas, o mejor aún,>
símbolo para eso). Tampoco pasaría nada si además de dar una orientación general, las direcciones de responder a la pregunta concreta preguntó acerca de, en este caso sobref(x)+f(x)
/2*f(x)
, ver Cómo Answer - de lo contrario puede parecer que es simplemente la publicidad de su página