¿Qué significa el término transparencia referencial ? Lo escuché descrito como "significa que puedes reemplazar iguales por iguales", pero esto parece una explicación inadecuada.
¿Qué significa el término transparencia referencial ? Lo escuché descrito como "significa que puedes reemplazar iguales por iguales", pero esto parece una explicación inadecuada.
Respuestas:
El término "transparencia referencial" proviene de la filosofía analítica , la rama de la filosofía que analiza construcciones del lenguaje natural, declaraciones y argumentos basados en los métodos de la lógica y las matemáticas. En otras palabras, es el tema más cercano fuera de la informática a lo que llamamos semántica del lenguaje de programación . El filósofo Willard Quine fue responsable de iniciar el concepto de transparencia referencial, pero también estuvo implícito en los enfoques de Bertrand Russell y Alfred Whitehead.
En esencia, la "transparencia referencial" es una idea muy simple y clara. El término "referente" se utiliza en filosofía analítica para hablar sobre lo que se refiere a una expresión . Es más o menos lo que queremos decir con "significado" o "denotación" en la semántica del lenguaje de programación. Usando el ejemplo de Andrew Birkett ( publicación de blog ), el término "la capital de Escocia" se refiere a la ciudad de Edimburgo. Ese es un ejemplo directo de un "referente".
Un contexto en una oración es "referencialmente transparente" si reemplazar un término en ese contexto por otro término que se refiera a la misma entidad no altera el significado. Por ejemplo
El Parlamento escocés se reúne en la capital de Escocia.
significa lo mismo que
El Parlamento escocés se reúne en Edimburgo.
Entonces, el contexto "El Parlamento escocés se reúne en ..." es un contexto referencialmente transparente. Podemos reemplazar "la capital de Escocia" con "Edimburgo" sin alterar el significado. En otras palabras, al contexto solo le importa a qué se refiere el término y nada más. Ese es el sentido en el que el contexto es "referencialmente transparente".
Por otro lado, en la oración,
Edimburgo ha sido la capital de Escocia desde 1999.
No podemos hacer tal reemplazo. Si lo hiciéramos, obtendríamos "Edimburgo ha sido Edimburgo desde 1999", lo cual es una locura que decir, y no transmite el mismo significado que la oración original. Entonces, parece que el contexto "Edimburgo ha sido ... desde 1999" es referencialmente opaco (lo opuesto a referencialmente transparente). Aparentemente le importa algo más de lo que se refiere el término. ¿Qué es?
Cosas como "la capital de Escocia" se llaman términos definidos y no le dieron mucho dolor de cabeza a los lógicos y filósofos durante mucho tiempo. Russell y Quine los resolvieron diciendo que en realidad no son "referenciales", es decir, es un error pensar que los ejemplos anteriores se usan para referirse a entidades. La forma correcta de entender "Edimburgo ha sido la capital de Escocia desde 1999" es decir
Escocia ha tenido una capital desde 1999 y esa capital es Edimburgo.
Esta oración no puede transformarse en una nuez. ¡Problema resuelto! El punto de Quine era decir que el lenguaje natural es desordenado, o al menos complicado, porque está hecho para ser conveniente para el uso práctico, pero los filósofos y los lógicos deben aportar claridad al comprenderlos de la manera correcta. La transparencia referencial es una herramienta que se utiliza para aportar tanta claridad de significado. .
¿Qué tiene que ver todo esto con la programación? No mucho, en realidad. Como dijimos, la transparencia referencial es una herramienta para ser utilizada en la comprensión del lenguaje, es decir, en la asignación de significado . Christopher Strachey , quien fundó el campo de la semántica del lenguaje de programación, lo usó en su estudio del significado. Su documento fundamental " Conceptos fundamentales en lenguajes de programación " está disponible en la web. Es un papel hermoso y todos pueden leerlo y comprenderlo. Entonces, por favor hazlo. Estarás muy iluminado. Introduce el término "transparencia referencial" en este párrafo:
Una de las propiedades más útiles de las expresiones es la llamada por la transparencia referencial de Quine. En esencia, esto significa que si deseamos encontrar el valor de una expresión que contiene una sub-expresión, lo único que necesitamos saber sobre la sub-expresión es su valor. Cualquier otra característica de la subexpresión, como su estructura interna, el número y la naturaleza de sus componentes, el orden en que se evalúan o el color de la tinta en la que están escritos, son irrelevantes para el valor del principal expresión.
El uso de "en esencia" sugiere que Strachey lo está parafraseando para explicarlo en términos simples. Los programadores funcionales parecen entender este párrafo a su manera. Hay otros 9 casos de "transparencia referencial" en el documento, pero no parecen preocuparse por ninguno de los otros. De hecho, todo el artículo de Strachey está dedicado a explicar el significado de los lenguajes de programación imperativos . Pero, hoy, los programadores funcionales afirman que los lenguajes de programación imperativos no son referencialmente transparentes. Strachey se estaría revolviendo en su tumba.
Podemos salvar la situación. Dijimos que el lenguaje natural es "desordenado, o al menos complicado" porque está hecho para ser conveniente para el uso práctico. Los lenguajes de programación son de la misma manera. Son "desordenados, o al menos complicados" porque están hechos para ser convenientes para el uso práctico. Eso no significa que tengan que confundirnos. Solo deben entenderse de la manera correcta, utilizando un metalenguaje que sea referencialmente transparente para que tengamos claridad de significado. En el artículo que cité, Strachey hace exactamente eso. Explica el significado de los lenguajes de programación imperativos desglosándolos en conceptos elementales, sin perder claridad en ningún lado. Una parte importante de su análisis es señalar que las expresiones en los lenguajes de programación tienen dos tipos de "valores",valores r . Antes del artículo de Strachey, esto no se entendía y la confusión reinaba supremamente. Hoy, la definición de C lo menciona rutinariamente y cada programador de C comprende la distinción. (Es difícil decir si los programadores en otros idiomas lo entienden igualmente bien).
Tanto Quine como Strachey estaban preocupados por el significado de las construcciones del lenguaje que implican alguna forma de dependencia del contexto. Por ejemplo, nuestro ejemplo "Edimburgo ha sido la capital de Escocia desde 1999" significa el hecho de que la "capital de Escocia" depende del momento en que se está considerando. Tal dependencia del contexto es una realidad, tanto en lenguajes naturales como en lenguajes de programación. Incluso en la programación funcional, las variables libres y enlazadas deben interpretarse con respecto al contexto en el que aparecen. La dependencia del contexto de cualquier tipo bloquea la transparencia referencial de una forma u otra. Si intentas comprender el significado de los términos sin tener en cuenta los contextos de los que dependen, nuevamente terminarás confundido. A Quine le preocupaba el significado de la lógica modal. Sostuvo quela lógica modal era referencialmente opaca y debería limpiarse traduciéndola a un marco referencialmente transparente (por ejemplo, considerando la necesidad como demostrabilidad). En gran medida perdió este debate. Tanto los lógicos como los filósofos encontraron que la posible semántica mundial de Kripke era perfectamente adecuada. Una situación similar también reina con la programación imperativa. La dependencia del estado explicada por Strachey y la dependencia de la tienda explicada por Reynolds (de manera similar a la posible semántica mundial de Kripke) son perfectamente adecuadas. Los programadores funcionales no saben mucho de esta investigación. Sus ideas sobre la transparencia referencial deben tomarse con un gran grano de sal.
[Nota adicional: Los ejemplos anteriores ilustran que una frase simple como "capital de Escocia" tiene múltiples niveles de significado. En un nivel, podríamos estar hablando de la capital en el momento actual. En otro nivel, podríamos hablar de todas las capitales posibles que Escocia podría haber tenido a lo largo del tiempo. Podemos "acercarnos" a un contexto particular y "alejar" para abarcar todos los contextos con bastante facilidad en la práctica normal. La eficiencia del lenguaje natural hace uso de nuestra capacidad para hacerlo. Los lenguajes de programación imperativos son eficientes de la misma manera. Podemos usar una variable x en el lado derecho de una asignación (el valor r ) para hablar sobre su valor en un estado particular. O podríamos hablar sobre su valor lque abarca todos los estados Las personas rara vez se confunden con tales cosas. Sin embargo, pueden o no ser capaces de explicar con precisión todas las capas de significado inherentes a las construcciones del lenguaje. Todas esas capas de significado no son necesariamente 'obvias' y es una cuestión científica estudiarlas adecuadamente. Sin embargo, la falta de articulación de la gente común para explicar dichos significados en capas no implica que estén confundidos acerca de ellos.]
Un "postscript" separado a continuación relaciona esta discusión con las preocupaciones de la programación funcional e imperativa .
Transparencia referencial, un término comúnmente utilizado en la programación funcional, significa que dada una función y un valor de entrada, siempre recibirá la misma salida. Es decir, no se utiliza ningún estado externo en la función.
Aquí hay un ejemplo de una función transparente referencial:
int plusOne(int x)
{
return x+1;
}
Con una función transparente referencial, dada una entrada y una función, puede reemplazarla con un valor en lugar de llamar a la función. Entonces, en lugar de llamar a plusOne con un parámetro de 5, podríamos reemplazarlo con 6.
Otro buen ejemplo es la matemática en general. En matemáticas, dada una función y un valor de entrada, siempre se asignará al mismo valor de salida. f (x) = x + 1. Por lo tanto, las funciones en matemáticas son referencialmente transparentes.
Este concepto es importante para los investigadores porque significa que cuando se tiene una función referencialmente transparente, se presta a una fácil paralelización y almacenamiento en caché.
La transparencia referencial se usa siempre en lenguajes funcionales como Haskell.
-
En contraste, existe el concepto de opacidad referencial. Esto significa lo contrario. Llamar a la función puede no siempre producir la misma salida.
//global G
int G = 10;
int plusG(int x)
{//G can be modified externally returning different values.
return x + G;
}
Otro ejemplo, es una función miembro en un lenguaje de programación orientado a objetos. Las funciones miembro comúnmente operan en sus variables miembro y por lo tanto serían referenciales opacas. Sin embargo, las funciones de los miembros pueden, por supuesto, ser referencialmente transparentes.
Otro ejemplo más es una función que lee un archivo de texto e imprime el resultado. Este archivo de texto externo podría cambiar en cualquier momento, por lo que la función sería referencialmente opaca.
Una función referencialmente transparente es aquella que solo depende de su entrada.
[Esta es una posdata a mi respuesta del 25 de marzo, en un esfuerzo por acercar la discusión a las preocupaciones de la programación funcional / imperativa.]
La idea de los programadores funcionales de transparencia referencial parece diferir de la noción estándar en tres formas:
Mientras que los filósofos / lógicos usan términos como "referencia", "denotación", "designatum" y " bedeutung " (término alemán de Frege), los programadores funcionales usan el término "valor". (Esto no es del todo de ellos. Noto que Landin, Strachey y sus descendientes también usaron el término "valor" para hablar de referencia / denotación. Puede ser solo una simplificación terminológica que introdujeron Landin y Strachey, pero parece hacer una gran diferencia cuando se usa de manera ingenua.)
Los programadores funcionales parecen creer que estos "valores" existen dentro del lenguaje de programación, no fuera. Al hacer esto, difieren tanto de los filósofos como de los semánticos del lenguaje de programación.
Parecen creer que se supone que estos "valores" se obtienen por evaluación.
Por ejemplo, el artículo de Wikipedia sobre transparencia referencial dice, esta mañana:
Se dice que una expresión es referencialmente transparente si puede reemplazarse por su valor sin cambiar el comportamiento de un programa (en otras palabras, producir un programa que tenga los mismos efectos y resultados en la misma entrada).
Esto está completamente en desacuerdo con lo que dicen los filósofos / lógicos. Dicen que un contexto es referencial o referencialmente transparente si una expresión en ese contexto puede ser reemplazada por otra expresión que se refiera a la misma cosa (una expresión de referencia central ). ¿Quiénes son estos filósofos / lógicos? Incluyen a Frege , Russell , Whitehead , Carnap , Quine , Churche innumerables otros. Cada uno de ellos es una figura imponente. El poder intelectual combinado de estos lógicos es devastador, por decir lo menos. Todos ellos son unánimes en la posición de que los referentes / denotaciones existen fuera del lenguaje formal y las expresiones dentro del lenguaje solo pueden hablar sobre ellos. Entonces, todo lo que uno puede hacer dentro del lenguaje es reemplazar una expresión por otra expresión que se refiera a la misma entidad. Los referentes / denotaciones en sí mismos no existen dentro del lenguaje. ¿Por qué los programadores funcionales se desvían de esta tradición bien establecida?
Se podría suponer que los semánticos del lenguaje de programación podrían haberlos engañado. Pero no lo hicieron.
Landin :
(a) cada expresión tiene una estructura de subexpresión anidada, (b) cada subexpresión denota algo (generalmente un número, valor de verdad o función numérica) , (c) lo que denota una expresión, es decir, su "valor", depende solo del valores de sus subexpresiones, no en otras propiedades de ellos. [Énfasis añadido]
Stoy :
Lo único que importa sobre una expresión es su valor, y cualquier subexpresión puede ser reemplazada por cualquier otro valor igual [énfasis agregado]. Además, el valor de una expresión es, dentro de ciertos límites, el mismo cada vez que ocurre ".
el valor de una expresión depende solo de los valores de sus expresiones constituyentes (si las hay) y estas subexpresiones pueden ser reemplazadas libremente por otras que posean el mismo valor [énfasis agregado].
Entonces, en retrospectiva, los esfuerzos de Landin y Strachey para simplificar la terminología mediante la sustitución de "referencia" / "denotación" con "valor" podrían haber sido perjudiciales. Tan pronto como uno escucha un "valor", existe la tentación de pensar en un proceso de evaluación que lo conduzca. Es igualmente tentador pensar en lo que sea que la evaluación produzca como el "valor", aunque podría estar bastante claro que esa no es la denotación. Eso es lo que deduzco que sucedió con el concepto de "transparencia referencial" a los ojos de los programadores funcionales. Pero el "valor" del que hablaban los primeros semánticos no es el resultado de una evaluación o el resultado de una función o algo por el estilo. Es la denotación del término.
Una vez que entendemos el llamado "valor" de una expresión ("referencia" o "denotación" en el discurso de los filósofos clásicos) como un objeto matemático / conceptual complejo, se abren todo tipo de posibilidades.
La renuencia de los programadores funcionales a llamar a estos lenguajes "referencialmente transparentes" simplemente implica que son reacios a admitir objetos matemáticos / conceptuales complejos como "valores". Por otro lado, parecen perfectamente dispuestos a llamar a un transformador de estado un "valor" cuando se coloca en su propia sintaxis favorita y se viste con una palabra de moda como "mónada". Tengo que decir que están siendo completamente inconsistentes, incluso si les garantizamos que su idea de "transparencia referencial" tiene cierta coherencia.
Un poco de historia podría arrojar algo de luz sobre cómo surgieron estas confusiones. El período entre 1962 y 1967 fue muy intenso para Christopher Strachey. Entre 1962 y 1965, tomó un trabajo de medio tiempo como asistente de investigación con Maurice Wilkes para diseñar e implementar el lenguaje de programación que se conoció como CPL. Este era un lenguaje de programación imperativo, pero también debía tener potentes capacidades funcionales de lenguaje de programación. Landin, que era empleado de Strachey en su empresa de consultoría, tuvo una gran influencia en la visión de Strachey de los lenguajes de programación. En el histórico documento de 1965 "Los siguientes 700 lenguajes de programación ", Landin promueve descaradamente los lenguajes de programación funcionales (llamándolos denotativoslenguajes) y describe los lenguajes de programación imperativos como su "antítesis". En la discusión subsiguiente, encontramos a Strachey planteando dudas sobre la fuerte posición de Landin.
... Los DL forman un subconjunto de todos los idiomas. Son un subconjunto interesante, pero que es incómodo de usar a menos que esté acostumbrado. Los necesitamos porque por el momento no sabemos cómo construir pruebas con lenguajes que incluyan imperativos y saltos. [Énfasis añadido]
En 1965, Strachey tomó la posición de lector en Oxford y parece haber trabajado esencialmente a tiempo completo en el desarrollo de una teoría de los imperativos y los saltos. En 1967, estaba listo con una teoría, que enseñó en su curso sobre " Conceptos fundamentales en lenguajes de programación " en una escuela de verano de Copenhague. Se suponía que las notas de la conferencia debían publicarse, pero "desafortunadamente, debido a la edición dilatoria, los procedimientos nunca se materializaron; sin embargo, como gran parte del trabajo de Strachey en Oxford, el periódico tuvo una circulación privada influyente". ( Martin Campbell-Kelly )
La dificultad de obtener los escritos de Strachey podría haber llevado a la propagación de las confusiones, con personas que dependen de fuentes secundarias y rumores. Pero, ahora que los " Conceptos fundamentales " están fácilmente disponibles en la web, no hay necesidad de recurrir a adivinar el trabajo. Deberíamos leerlo y decidir qué quería decir Strachey. En particular:
Cualquier conversación sobre "transparencia referencial" sin comprender la distinción entre valores L, valores R y otros objetos complejos que pueblan el universo conceptual del programador imperativo es fundamentalmente errónea.
Una función referencialmente transparente es aquella que actúa como una función matemática; Dadas las mismas entradas, siempre producirá las mismas salidas. Implica que el estado pasado no se modifica y que la función no tiene estado propio.
Para aquellos que necesitan una explicación concisa, voy a arriesgar una (pero lea la divulgación a continuación).
La transparencia referencial en un lenguaje de programación promueve el razonamiento equitativo: cuanta más transparencia referencial tenga, más fácil será hacer un razonamiento equitativo. Por ejemplo, con una definición de (pseudo) función,
fx = x + x,
la facilidad con la que puede reemplazar (con seguridad) f (foo) con foo + foo en el alcance de esta definición, sin tener demasiadas restricciones sobre dónde puede realizar esta reducción, es una buena indicación de cuánta transparencia referencial tiene su lenguaje de programación tiene.
Por ejemplo, si foo fuera x ++ en el sentido de la programación en C, entonces no podría realizar esta reducción de forma segura (es decir, si realizara esta reducción no terminaría con el mismo programa con el que comenzó).
En los lenguajes de programación prácticos no verá una transparencia referencial perfecta, pero a los programadores funcionales les importa más que a la mayoría (cf Haskell, donde es un objetivo central).
(Divulgación completa: soy un programador funcional, por lo que, según la respuesta principal, debería tomar esta explicación con un grano de sal).
Si está interesado en la etimología (es decir, por qué este concepto tiene este nombre en particular), eche un vistazo a mi blog en el tema. La terminología proviene del filósofo / lógico Quine.
En 1 hay una claridad de dos idiomas en cuestión:
En 2, gracias a la cercanía del objeto y los metalenguajes, pueden confundirse.
Como implementador del lenguaje, encuentro que necesito recordar constantemente esta distinción.
Entonces, Prof. Reddy, ¿puedo parafrasearlo así? :-)
En los contextos de programación funcional y semántica, el término Transparencia referencial no es referencialmente transparente.
La siguiente respuesta, espero, agrega y califica las controvertidas 1ª y 3ª respuestas.
Permitamos que una expresión denote o se refiera a algún referente. Sin embargo, una pregunta es si estos referentes pueden codificarse isomórficamente como parte de las expresiones mismas, llamando a tales expresiones 'valores'. Por ejemplo, los valores de números literales son un subconjunto del conjunto de expresiones aritméticas, los valores de verdad son un subconjunto del conjunto de expresiones booleanas, etc. La idea es evaluar una expresión a su valor (si tiene una). Por lo tanto, la palabra 'valor' puede referirse a una denotación o a un elemento distinguido del conjunto de expresiones. Pero si hay un isomorfismo (una biyección) entre el referente y el valor, podemos decir que son lo mismo. (Dicho esto, hay que tener cuidado al definir los referentes y el isomorfismo, como lo demuestra el campo de la semántica denotacional. Para poner un ejemplo mencionado por las respuestas a la tercera respuesta,data Nat = Zero | Suc Nat
no corresponde como se esperaba al conjunto de números naturales).
Escribamos E[·]
para una expresión con un agujero, también conocido en algunos sectores como "contexto". Dos ejemplos de contexto para expresiones tipo C son [·]+1
y
[·]++
.
Escribamos [[·]]
para la función que toma una expresión (sin agujero) y entrega su significado (referente, denotación, etc.) en algún universo que proporciona significado. (Estoy tomando prestada la notación del campo de la semántica denotacional).
Adaptemos formalmente la definición de Quine de la siguiente manera: un contexto E[·]
es referencialmente transparente si se dan dos expresiones E1
y E2
(sin agujeros) de modo que [[E1]] = [[E2]]
(es decir, las expresiones denotan / se refieren al mismo referente), entonces es el caso que [[E[E1]]] = [[E[E2]]]
(es decir, relleno -en el agujero con cualquiera E1
o E2
da como resultado expresiones que también denotan el mismo referente).
La regla de Leibniz de sustituir iguales por iguales se expresa típicamente como 'si
E1 = E2
entonces E[E1] = E[E2]
', que dice que E[·]
es una función. Una función (o, para el caso, un programa que computa la función) es un mapeo de una fuente a un objetivo, de modo que haya como máximo un elemento objetivo para cada elemento fuente. Las funciones no deterministas son nombres incorrectos, ya sea relaciones, funciones que entregan conjuntos, etc. Si en la regla de Leibniz la igualdad =
es denotacional, los dobles corchetes simplemente se dan por sentados y se eluyen. Entonces, un contexto referencialmente transparente es una función. Y la regla de Leibniz es el ingrediente principal del razonamiento equitativo, por lo que el razonamiento equitativo está definitivamente relacionado con la transparencia referencial.
Aunque [[·]]
es una función desde expresiones hasta denotaciones, podría ser una función desde expresiones hasta 'valores' entendidos como un subconjunto restringido de expresiones, y [[·]]
puede entenderse como evaluación.
Ahora, si E1
es una expresión y E2
es un valor, tenemos lo que creo que entiende la mayoría de las personas al definir la transparencia referencial en términos de expresiones, valores y evaluación. Pero como se ilustra en la primera y tercera respuestas en esta página, esta es una definición incorrecta.
El problema con contextos como el [·]++
no es el efecto secundario, sino que su valor no está definido en C isomorficamente a su significado. Las funciones no son valores (bueno, los punteros a las funciones sí lo son), mientras que en los lenguajes de programación funcional sí lo son. Landin, Strachey y los pioneros de la semántica denotacional fueron bastante inteligentes al usar mundos funcionales para dar significado.
Para lenguajes tipo C imperativos, podemos (aproximadamente) proporcionar semántica a las expresiones que usan la función [[·]] : Expression -> (State -> State x Value)
.
Value
es un subconjunto de Expression
. State
contiene pares (identificador, valor). La función semántica toma una expresión y entrega como significado una función del estado actual al par con el estado actualizado y un valor. Por ejemplo, [[x]]
es la función del estado actual al par cuyo primer componente es el estado actual y cuyo segundo componente es el valor de x. En contraste, [[x++]]
es la función desde el estado actual hasta el par cuyo primer componente es un estado en el que se incrementa el valor de x, y cuyo segundo componente es ese mismo valor. En este sentido, el contexto [·]++
es referencialmente transparente si satisface la definición dada anteriormente.
Creo que los programadores funcionales tienen derecho a utilizar la transparencia referencial en el sentido de que se recuperan naturalmente [[·]]
como una función de expresiones a valores. Las funciones son valores de primera clase y el estado también puede ser un valor, no una denotación. La mónada estatal es (en parte) un mecanismo limpio para pasar (o enhebrar) el estado.
Tenga en cuenta que este concepto de "significado" es algo que sucede en la mente del observador. Por lo tanto, la misma "referencia" puede significar cosas diferentes para diferentes personas. Entonces, por ejemplo, tenemos una página de desambiguación de Edimburgo en Wikipedia.
Un problema relacionado que puede aparecer en el contexto de la programación podría ser el polimorfismo.
Y quizás deberíamos tener un nombre para el caso especial de polimorfismo (o tal vez incluso fundición) donde, para nuestros propósitos, los diferentes casos polimórficos son semánticamente equivalentes (en lugar de simplemente ser similares. Por ejemplo, el número 1, que podría representarse usando un tipo entero, o un tipo complejo o cualquiera de una variedad de otros tipos, puede tratarse polimórficamente).
La definición de transparencia referencial en el libro " Estructura e implementación de programas informáticos " (el Libro del asistente) me pareció útil porque se complementa con una explicación de cómo se viola la transparencia referencial al introducir la operación de asignación . Eche un vistazo a la siguiente diapositiva que hice sobre el tema: https://www.slideshare.net/pjschwarz/introducing-assignment-invalidates-the-substitution-model-of-evaluation-and-violates-referential-transparency-as- explicado en sicp-the-wizard-book
La transparencia referencial se puede expresar simplemente como:
Por ejemplo, el lenguaje de programación Haskell es un lenguaje funcional puro; lo que significa que es referencialmente transparente.