¿Cuáles son los beneficios de la transparencia referencial para un programador?

18

En programación, ¿cuáles son los beneficios de la transparencia referencial ?

RT hace una de las principales diferencias entre los paradigmas funcionales e imperativos, y los defensores del paradigma funcional lo utilizan a menudo como una clara ventaja sobre el imperativo; pero en todos sus esfuerzos, estos defensores nunca explican por qué es un beneficio para mí como programador .

Claro, tendrán sus explicaciones académicas de cuán "puro" y "elegante" es, pero ¿cómo lo hace mejor que un código menos "puro"? ¿Cómo me beneficia en mi programación diaria?

Nota: Esto no es un duplicado de ¿Qué es la transparencia referencial? Este último aborda el tema de lo que es RT, mientras que esta pregunta aborda sus beneficios (que pueden no ser tan intuitivos).

Eyal Roth
fuente
Relacionado: stackoverflow.com/questions/210835/…
meriton - en huelga el
3
La transparencia referencial le permite utilizar el razonamiento equitativo para: 1) Probar las propiedades del código y 2) escribir programas. Hay algunos libros sobre Haskell donde los autores deberían saber cómo puede comenzar a partir de algunas ecuaciones que desea que se complete una función y, utilizando solo el razonamiento equitativo, terminará obteniendo una implementación de dicha función, que es, por lo tanto, ciertamente correcta. Ahora, cuánto puede aplicarse esto en la programación "diaria" probablemente depende del contexto ...
Bakuriu
2
@err ¿Le gusta el código que es más fácil de refactorizar porque sabe si llamar a una función dos veces es lo mismo que almacenar su valor en una variable, y luego usar dicha variable dos veces? ¿Diría que es un beneficio para su programación diaria?
Andres F.
El beneficio es que no necesita perder tiempo pensando en la no transparencia referencial. Algo así como los beneficios de las variables es que no necesita perder tiempo pensando en la asignación de registros.
user253751

Respuestas:

37

El beneficio es que las funciones puras hacen que su código sea más fácil de razonar. O, en otras palabras, los efectos secundarios aumentan la complejidad de su código.

Tome un ejemplo de computeProductPricemétodo.

Un método puro le pediría una cantidad de producto, una moneda, etc. Usted sabe que siempre que se llame al método con los mismos argumentos, siempre producirá el mismo resultado.

  • Incluso puede almacenarlo en caché y usar la versión en caché.
  • Puede hacerlo flojo y posponer su llamada cuando realmente lo necesite, sabiendo que el valor no cambiará mientras tanto.
  • Puede llamar al método varias veces, sabiendo que no tendrá efectos secundarios.
  • Puede razonar sobre el método en sí mismo en un aislamiento del mundo, sabiendo que todo lo que necesita son los argumentos.

Un método no puro será más complejo de usar y depurar. Dado que depende del estado de las variables que no sean los argumentos y posiblemente las altere, significa que podría producir resultados diferentes cuando se llama varias veces, o no tener el mismo comportamiento cuando no se llama o se llama demasiado pronto o demasiado tarde.

Ejemplo

Imagine que hay un método en el marco que analiza un número:

decimal math.parse(string t)

No tiene transparencia referencial, porque depende de:

  • La variable de entorno que especifica el sistema de numeración, que es Base 10 u otra cosa.

  • La variable dentro de la mathbiblioteca que especifica la precisión de los números para analizar. Entonces, con el valor de 1, el análisis de la cadena "12.3456"dará 12.3.

  • La cultura, que define el formato esperado. Por ejemplo, con fr-FR, el análisis "12.345"dará 12345, porque el carácter de separación debería ser ,, no.

Imagine lo fácil o difícil que sería trabajar con tal método. Con la misma entrada, puede tener resultados radicalmente diferentes dependiendo del momento en que llame al método, porque algo, en algún lugar, cambió la variable de entorno o cambió la cultura o estableció una precisión diferente. El carácter no determinista del método conduciría a más errores y más pesadillas de depuración. Llamar math.parse("12345")y obtener 5349una respuesta ya que algún código paralelo estaba analizando números octales no es bueno.

¿Cómo arreglar este método obviamente roto? Mediante la introducción de la transparencia referencial. En otras palabras, deshaciéndose del estado global y moviendo todo a los parámetros del método:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

Ahora que el método es puro, sabe que no importa cuándo lo llame, siempre producirá el mismo resultado para los mismos argumentos.

Arseni Mourzenko
fuente
44
Solo un apéndice: la transparencia referencial se aplica a todas las expresiones en un idioma, no solo a las funciones.
cabeza de jardín
3
Tenga en cuenta que existen limitaciones sobre cuán transparente puede ser. Hacer packet = socket.recv()referencialmente transparente en lugar de derrotar el punto de la función.
Mark
1
Debe ser cultura = culturas.invariante. A menos que desee crear accidentalmente un software que solo funcione correctamente en los EE. UU.
user253751
@immibis: hm, buena pregunta. ¿Para qué serían las reglas de análisis invariant? O son los mismos que para en_us, en cuyo caso, por qué molestarse, o corresponden a algún otro país, en cuyo caso, cuál y por qué este en lugar de en_us, o tienen sus reglas específicas que no coinciden con ningún país de todos modos , lo que sería inútil. Realmente no hay una "respuesta verdadera" entre 12,345.67y 12 345,67: cualquier "regla predeterminada" funcionará para algunos países, y no funcionará para otros.
Arseni Mourzenko
3
@ArseniMourzenko Generalmente es un "mínimo común denominador" y similar a la sintaxis que usan muchos lenguajes de programación (que también es invariante de la cultura). 12345analiza como 12345, 12 345o 12,345o 12.345es un error. 12.345analizado como un número invariante de coma flotante siempre produce 12.345, de acuerdo con la convención del lenguaje de programación de usar. como el separador decimal. Las cadenas se ordenan por sus puntos de código Unicode y distinguen entre mayúsculas y minúsculas. Y así.
user253751
11

¿A menudo agrega un punto de interrupción a un punto en su código y ejecuta la aplicación en el depurador para averiguar qué está sucediendo? Si lo hace, eso se debe principalmente a que no está utilizando la transparencia referencial (RT) en sus diseños. Y así que tengo que ejecutar el código para resolver lo que hace.

Todo el punto de RT es que el código es altamente determinista, es decir, puede leer el código y calcular lo que hace, cada vez, para el mismo conjunto de entradas. Una vez que comience a agregar variables mutantes, algunas de las cuales tienen un alcance más allá de una sola función, no puede simplemente leer el código. Dicho código debe ejecutarse, ya sea en la cabeza o en el depurador, para determinar cómo funciona realmente.

Cuanto más simple sea leer y razonar el código, más sencillo será mantener y detectar errores, de modo que ahorre tiempo y dinero para usted y su empleador.

David Arno
fuente
10
"Una vez que comienzas a agregar variables mutantes, algunas de las cuales tienen más allá de una sola función, no puedes simplemente leer el código, tienes que ejecutarlo, ya sea en tu cabeza o en el depurador, para descubrir cómo funciona realmente. ": Buen punto. En otras palabras, la transparencia referencial no solo significa que una pieza de código siempre producirá el mismo resultado para las mismas entradas, sino también que el resultado producido es el único efecto de esa pieza de código, que no hay otro lado oculto efecto como cambiar alguna variable que se ha definido muy lejos en otro módulo.
Giorgio
Ese es un buen punto. Tengo un pequeño problema con cuanto más simple es el código para leer / razonar argumento, ya que más simple de leer o razonar es un atributo de código algo vago y subjetivo.
Eyal Roth
Una vez que comience a agregar variables de mutación, algunas de las cuales tienen un alcance más allá de una sola función, pero ¿por qué se desaconseja la operación de asignación incluso cuando el alcance de la variable es local para funcionar?
rahulaga_dev
9

La gente usa el término "más fácil de razonar", pero nunca explica lo que eso significa. Considere el siguiente ejemplo:

result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)

Son result1y result2el mismo o diferente? Sin transparencia referencial, no tienes idea. En realidad, debe leer el cuerpo de foopara asegurarse, y posiblemente el cuerpo de cualquier función foo, y así sucesivamente.

Las personas no se dan cuenta de esta carga porque están acostumbradas, pero si vas a trabajar en un entorno puramente funcional durante un mes o dos, regresas y la sientes, y es un gran problema .

Hay tantos mecanismos de defensa que las personas hacen para solucionar la falta de transparencia referencial. Para mi pequeño ejemplo, es posible que desee mantener la result1memoria, porque no sabría si cambiaría. Luego tengo un código con dos estados: antes result1se almacenó y después. Con la transparencia referencial, puedo recalcularlo fácilmente, siempre que el recálculo no lleve mucho tiempo.

Karl Bielefeldt
fuente
1
Usted ha mencionado que la transparencia referencial que permite a la razón sobre el resultado de las llamadas a foo () y saber si result1y result2son los mismos. Otro aspecto importante es que si foo("bar", 12)es referencialmente transparente, entonces no tiene que preguntarse si esta llamada ha producido algunos efectos en otro lugar (¿establecer algunas variables? ¿Eliminar un archivo? Lo que sea).
Giorgio
La única "integridad referencial" con la que estoy familiarizado involucra bases de datos relacionales.
Mark
1
@ Mark Es un error tipográfico. Karl se refería a la transparencia referencial, como se desprende del resto de su respuesta.
Andres F.
6

Yo diría: la transparencia referencial no solo es buena para la programación funcional, sino para todos los que trabajan con funciones porque sigue el principio del mínimo asombro.

Tiene una función y puede razonar mejor sobre lo que hace porque no hay factores externos que deba tener en cuenta, para una entrada dada, la salida siempre será la misma. Incluso en mi lenguaje imperativo trato de seguir este paradigma tanto como sea posible, lo siguiente que básicamente se deduce automáticamente de esto es: pequeñas funciones fáciles de entender en lugar de las horribles funciones de más de 1000 líneas que a veces ejecuto.

Esas grandes funciones hacen magia y me da miedo tocarlas porque pueden romperse de manera espectacular.

Entonces, las funciones puras no son algo solo para la programación funcional, sino para todos los programas.

Pieter B
fuente