En informática , se dice que una operación, función o expresión tiene un efecto secundario si tiene algún efecto observable distinto de su efecto principal de leer el valor de sus argumentos y devolver un valor al invocador de la operación. Los ejemplos de efectos secundarios incluyen modificar una variable no local , una variable local estática o un argumento mutable pasado por referencia ; generar errores o excepciones; realizar E/S ; o llamar a otras funciones con efectos secundarios. [1] En presencia de efectos secundarios, el comportamiento de un programa puede depender del historial; es decir, el orden de evaluación importa. Comprender y depurar una función con efectos secundarios requiere conocimiento sobre el contexto y sus posibles historias. [2] [3] Los efectos secundarios juegan un papel importante en el diseño y análisis de lenguajes de programación . El grado en que se utilizan los efectos secundarios depende del paradigma de programación. Por ejemplo, la programación imperativa se usa comúnmente para producir efectos secundarios, para actualizar el estado de un sistema. Por el contrario, la programación declarativa se usa comúnmente para informar sobre el estado del sistema, sin efectos secundarios.
La programación funcional tiene como objetivo minimizar o eliminar los efectos secundarios. La falta de efectos secundarios facilita la verificación formal de un programa. El lenguaje funcional Haskell elimina los efectos secundarios, como la E/S y otros cálculos con estado, reemplazándolos con acciones monádicas . [4] [5] Los lenguajes funcionales como Standard ML , Scheme y Scala no restringen los efectos secundarios, pero es habitual que los programadores los eviten. [6]
Los programadores de lenguaje ensamblador deben ser conscientes de los efectos secundarios ocultos : instrucciones que modifican partes del estado del procesador que no se mencionan en la mnemotecnia de la instrucción. Un ejemplo clásico de un efecto secundario oculto es una instrucción aritmética que modifica implícitamente los códigos de condición (un efecto secundario oculto) mientras modifica explícitamente un registro (el efecto deseado). Una desventaja potencial de un conjunto de instrucciones con efectos secundarios ocultos es que, si muchas instrucciones tienen efectos secundarios en una sola parte del estado, como los códigos de condición, entonces la lógica requerida para actualizar ese estado secuencialmente puede convertirse en un cuello de botella de rendimiento. El problema es particularmente agudo en algunos procesadores diseñados con canalización (desde 1990) o con ejecución fuera de orden . Un procesador de este tipo puede requerir circuitos de control adicionales para detectar efectos secundarios ocultos y detener la canalización si la siguiente instrucción depende de los resultados de esos efectos.
La ausencia de efectos secundarios es una condición necesaria, pero no suficiente, para la transparencia referencial. La transparencia referencial significa que una expresión (como una llamada a una función) puede reemplazarse por su valor. Esto requiere que la expresión sea pura , es decir, que la expresión debe ser determinista (siempre debe dar el mismo valor para la misma entrada) y libre de efectos secundarios.
Los efectos secundarios causados por el tiempo que tarda una operación en ejecutarse suelen ignorarse cuando se habla de efectos secundarios y transparencia referencial. Hay algunos casos, como en el caso de la sincronización o las pruebas de hardware, en los que las operaciones se insertan específicamente por sus efectos secundarios temporales, por ejemplo, sleep(5000)
o for (int i = 0; i < 10000; ++i) {}
. Estas instrucciones no cambian de estado, salvo que tardan una cantidad de tiempo en completarse.
Una subrutina con efectos secundarios es idempotente si múltiples aplicaciones de la subrutina tienen el mismo efecto en el estado del sistema que una sola aplicación, en otras palabras, si la función del espacio de estado del sistema a sí misma asociada con la subrutina es idempotente en el sentido matemático . Por ejemplo, considere el siguiente programa Python :
x = 0def setx ( n ): global x x = nsetx ( 3 ) afirmar x == 3 setx ( 3 ) afirmar x == 3
setx
es idempotente porque la segunda aplicación de setx
3 tiene el mismo efecto en el estado del sistema que la primera aplicación: x
ya estaba configurado en 3 después de la primera aplicación, y todavía está configurado en 3 después de la segunda aplicación.
Una función pura es idempotente si es idempotente en el sentido matemático . Por ejemplo, considere el siguiente programa Python:
def abs ( n ): devuelve - n si n < 0 de lo contrario nafirmar abs ( abs ( -3 ) ) == abs ( -3 )
abs
es idempotente porque la segunda aplicación del abs
valor de retorno de la primera aplicación a -3 devuelve el mismo valor que la primera aplicación a -3.
Una demostración común del comportamiento de los efectos secundarios es la del operador de asignación en C. La asignación a = b
es una expresión que evalúa el mismo valor que la expresión b
, con el efecto secundario de almacenar el valor R de b
en el valor L de a
. Esto permite la asignación múltiple:
a = ( b = 3 ); // b = 3 se evalúa como 3, que luego se asigna a a
Debido a que el operador derecho asocia , esto es equivalente a
a = b = 3 ;
Esto presenta un potencial problema para los programadores novatos que pueden confundirse.
mientras ( b == 3 ) {} // prueba si b se evalúa como 3
con
mientras ( b = 3 ) {} // b = 3 se evalúa como 3, que luego se convierte en verdadero, por lo que el bucle es infinito
El término efecto secundario se refiere a la modificación del entorno no local. Generalmente esto sucede cuando una función (o un procedimiento) modifica una variable global o argumentos pasados por parámetros de referencia. Pero aquí hay otras formas en las que se puede modificar el entorno no local. Consideramos las siguientes causas de efectos secundarios a través de una llamada de función: 1. Realizar E/S. 2. Modificar variables globales. 3. Modificar variables permanentes locales (como las variables estáticas en C). 4. Modificar un argumento pasado por referencia. 5. Modificar una variable local, ya sea automática o estática, de una función más arriba en la secuencia de llamada de función (normalmente a través de un puntero).