En informática , un parámetro de procedimiento es un parámetro de un procedimiento que es en sí mismo un procedimiento.
Este concepto es una herramienta de programación extremadamente poderosa y versátil , porque permite a los programadores modificar ciertos pasos de un procedimiento de biblioteca de formas arbitrariamente complicadas, sin tener que entender o modificar el código de ese procedimiento.
Esta herramienta es especialmente eficaz y conveniente en lenguajes que soportan la definición de funciones locales , tales como Pascal y lo moderno dialecto GNU de C . Lo es aún más cuando hay cierres de funciones disponibles. Los objetos en lenguajes de programación orientados a objetos proporcionan la misma funcionalidad (y más) , pero a un costo significativamente mayor.
Los parámetros de procedimiento están algo relacionados con los conceptos de función de primera clase y función anónima , pero son distintos de ellos. Estos dos conceptos tienen más que ver con cómo se definen las funciones, que con cómo se utilizan.
Concepto basico
En la mayoría de los lenguajes que proporcionan esta característica, un parámetro de procedimiento f de una subrutina P se puede llamar dentro del cuerpo de P como si fuera un procedimiento ordinario:
procedimiento P ( f ): return f (6,3) * f (2,1)
Al llamar a la subrutina P , se le debe dar un argumento, que debe ser alguna función previamente definida compatible con la forma en que P usa su parámetro f . Por ejemplo, si definimos
procedimiento más ( x , y ): return x + y
entonces podemos llamar a P ( más ), y el resultado será más (6,3) * más (2,1) = (6 + 3) * (2 + 1) = 27. Por otro lado, si definimos
procedimiento quot ( u , v ): return u / v
entonces la llamada P ( quot ) devolverá quot (6,3) * quot (2,1) = (6/3) * (2/1) = 4. Finalmente, si definimos
procedimiento mal ( z ) retorno z + 100
entonces la llamada P ( malvada ) no tendrá mucho sentido y puede marcarse como un error.
Detalles sintácticos
Algunos lenguajes de programación que tienen esta característica pueden permitir o requerir una declaración de tipo completa para cada parámetro de procedimiento f , incluyendo el número y tipo de sus argumentos, y el tipo de su resultado, si lo hubiera. Por ejemplo, en el lenguaje de programación C, el ejemplo anterior podría escribirse como
int P ( int ( * f ) ( int a , int b )) { return f ( 6 , 3 ) * f ( 2 , 1 ); }
En principio, la función real actf que se pasa como argumento cuando se llama a P debe ser compatible con el tipo declarado del parámetro de procedimiento f . Esto generalmente significa que ACTF y f deben devolver el mismo tipo de resultado, deben tener el mismo número de argumentos, y los argumentos correspondientes deben tener el mismo tipo. Sin embargo, los nombres de los argumentos no tienen por qué ser los mismos, como se muestra en los ejemplos más y comillas anteriores. Sin embargo, algunos lenguajes de programación pueden ser más restrictivos o más liberales a este respecto.
Alcance
En lenguajes que permiten parámetros de procedimiento, las reglas de alcance generalmente se definen de tal manera que los parámetros de procedimiento se ejecutan en su alcance nativo. Más precisamente, suponga que la función actf se pasa como argumento a P , como su parámetro de procedimiento f ; y f se llama a continuación, desde el interior del cuerpo de P . Mientras se ejecuta actf , ve el entorno de su definición. [ ejemplo necesario ]
La implementación de estas reglas de alcance no es trivial. En el momento en que actf finalmente se ejecuta, los registros de activación donde viven sus variables de entorno pueden estar arbitrariamente profundos en la pila. Este es el llamado problema de funarg hacia abajo .
Ejemplo: ordenación por inserción genérica
El concepto de parámetro de procedimiento se explica mejor con ejemplos. Una aplicación típica es la siguiente implementación genérica del algoritmo de ordenación por inserción , que toma dos parámetros enteros a , by dos parámetros de procedimiento prec , swap :
procedimiento isort ( a , b , prec , swap ): entero i , j ; i ← a ; mientras que i ≤ b hacer j ← i ; mientras que j > una y prec ( j , j -1) hacer intercambio ( j , j -1); j ← j −1; i ← i +1;
Este procedimiento puede usarse para ordenar los elementos x [ a ] a x [ b ] de algún arreglo x , de tipo arbitrario , en un orden especificado por el usuario. Los parámetros PREC y de intercambio debe ser de dos funciones , definidos por el cliente , tanto la toma de dos números enteros r , s entre una y b . La función prec debe devolver verdadero si y solo si los datos almacenados en x [ r ] deben preceder a los datos almacenados en x [ s ], en el orden definido por el cliente. El intercambio de función debe intercambiar el contenido de x [ r ] y x [ s ], y regresar ningún resultado.
Mediante la elección adecuada de las funciones prec y swap , se puede utilizar el mismo procedimiento isort para reordenar matrices de cualquier tipo de datos, almacenados en cualquier medio y organizados en cualquier estructura de datos que proporcione acceso indexado a elementos individuales de la matriz. (Sin embargo, tenga en cuenta que existen algoritmos de ordenación que son mucho más eficientes que la ordenación por inserción para matrices grandes).
Ordenar números de punto flotante
Por ejemplo, podemos ordenar una matriz z de 20 números de punto flotante, z [1] a z [20] en orden creciente llamando a isort (1, 20, zprec , zswap ), donde las funciones zprec y zswap se definen como
procedimiento zprec ( r , s ): return ( z [ r ] < z [ s ]);procedimiento zswap ( r , s ): float t ; t ← z [ r ]; z [ r ] ← z [ s ]; z [ s ] ← t
Ordenar filas de una matriz
Para otro ejemplo, sea M una matriz de números enteros con 10 filas y 20 columnas, con índices que comienzan en 1. El siguiente código reorganizará los elementos en cada fila para que todos los valores pares estén antes de todos los valores impares:
entero i procedimiento eoprec ( r , s ): return ( M [ i , r ] mod 2) <( M [ i , s ] mod 2);procedimiento eoswap ( r , s ): entero t ; t ← M [ i , r ]; M [ i , r ] ← M [ i , s ]; M [ i , s ] ← t ;para i de 1 a 10 do isort (1, 20, eoprec, eoswap);
Tenga en cuenta que los efectos de eoprec y eoswap dependen del número de fila i , pero el procedimiento de isort no necesita saberlo.
Procedimiento de clasificación de vectores
El siguiente ejemplo usa isort para definir un procedimiento vecsort que toma un entero n y un vector entero v con elementos v [0] a v [ n −1] y los ordena en orden creciente o decreciente, dependiendo de si un tercer parámetro incr es verdadero o falso , respectivamente:
procedimiento vecsort ( n , v , incr ): procedimiento vprec ( r , s ): si incr luego de regreso v [ r ] < v [ s ]; de lo contrario, devuelve v [ r ]> v [ s ]; procedimiento vswap ( r , s ): entero t ; t ← v [ r ]; v [ r ] ← v [ s ]; v [ s ] ← t isort (0, n −1, vprec , vswap );
Tenga en cuenta el uso de definiciones de funciones anidadas para obtener una función vprec cuyo efecto depende del parámetro incr pasado a vecsort . En lenguajes que no permiten definiciones de funciones anidadas, como el estándar C, obtener este efecto requeriría un código bastante complicado y / o inseguro para subprocesos .
Ejemplo: fusionar dos secuencias
El siguiente ejemplo ilustra el uso de parámetros de procedimiento para procesar estructuras de datos abstractas independientemente de su implementación concreta. El problema es fusionar dos secuencias ordenadas de registros en una sola secuencia ordenada, donde el cliente puede elegir la naturaleza de los registros y el criterio de orden. La siguiente implementación asume solo que cada registro puede ser referenciado por una dirección de memoria, y hay una "dirección nula" Λ que no es la dirección de ningún registro válido. El cliente debe proporcionar las direcciones A , B de los primeros registros de cada secuencia y las funciones prec , next y append , que se describirán más adelante.
procedimiento de combinación de ( A , B , prec , nextA , appendA , nextB , AppendB ): dirección ini , aleta , t ini ← Λ; fin ← Λ mientras A ≠ Λ o B ≠ Λ hacer si B = Λ o ( A ≠ Λ y B ≠ Λ y prec ( A , B )) entonces t ← nextA ( A ) fin ← appendA ( A , fin ); si ini = Λ entonces ini ← fin A ← t else t ← nextB ( B ) fin ← appendB ( B , fin ); si ini = Λ entonces ini ← fin B ← t return ini
La función prec debe tomar las direcciones r , s de dos registros, uno de cada secuencia, y devolver verdadero si el primer registro debe ir antes que el otro en la secuencia de salida. La función nextA debe tomar la dirección de un registro de la primera secuencia y devolver la dirección del siguiente registro en la misma secuencia, o Λ si no hay ninguno. La función appendA debe agregar el primer registro de la secuencia A a la secuencia de salida; sus argumentos son la dirección A del registro que se agregará y la dirección fin del último registro de la lista de salida (o Λ si esa lista aún está vacía). El procedimiento appendA debería devolver la dirección actualizada del elemento final de la lista de salida. Los procedimientos nextB y appendB son análogos para la otra secuencia de entrada.
Fusionar listas vinculadas
Para ilustrar el uso del procedimiento de combinación genérica, aquí es el código para la fusión de dos simples listas enlazadas , a partir de los nodos en las direcciones R , S . Aquí asumimos que cada registro x contiene un campo entero x . INFO y un campo de dirección x . SIGUIENTE que apunta al siguiente nodo; donde los campos de información están en orden creciente en cada lista. Las listas de entrada son desmanteladas por la fusión y sus nodos se utilizan para construir la lista de salida.
procedimiento listmerge ( R , S ): procedimiento prec ( r , s ): return r . INFO < s . INFO procedimiento siguiente ( x ): devuelve x . SIGUIENTE procedimiento append ( x , fin ) si fin ≠ Λ entonces fin . SIGUIENTE ← x x . SIGUIENTE ← Λ regresa x return merge ( R , S , prec , next , append , next , append )
Combinar vectores
El siguiente código ilustra la independencia del procedimiento de combinación genérico de la representación real de las secuencias. Fusiona los elementos de dos matrices ordinarias U [0] a U [ m −1] y V [0] a V [ n −1] de números de coma flotante, en orden decreciente. Las matrices de entrada no se modifican y la secuencia combinada de valores se almacena en un tercer vector W [0] a W [ m + n −1]. Como en el lenguaje de programación C, asumimos que la expresión "& V " da como resultado la dirección de la variable V , "* p " da como resultado la variable cuya dirección es el valor de p , y que "& ( X [ i ])" es equivalente a "& ( X [0]) + i " para cualquier matriz X y cualquier entero i .
procedimiento de fusión de matriz ( U , m , V , n , W ): procedimiento prec ( r , s ): return (* r )> (* s ) procedimiento nextU ( x ): si x = & ( U [ m −1]) entonces retorna Λ si no retorna x + 1 procedimiento nextV ( x ): si x = & ( V [ n −1]) entonces retorna Λ si no retorna x + 1 procedimiento append ( x , fin ) si fin = Λ entonces fin ← & ( W [0]) (* fin ) ← (* x ) devuelve fin + 1 si m = 0 entonces U ← Λ si n = 0, entonces V ← Λ retorno de combinación de ( U , V , prec , nextU , append , NexTV , append )
Ejemplo: integral definida
Integrar en un intervalo
El siguiente procedimiento calcula la integral aproximada f ( x ) d x de una función de valor real dada f en un intervalo dado [ a , b ] de la línea real . El método numérico utilizado es la regla del trapecio con un número n de pasos dado ; los números reales se aproximan mediante números de coma flotante.
procedimiento Intg ( f , a , b , n ): float t , x , s ; entero i si b = a entonces devuelve 0 x ← a ; s ← f ( a ) / 2; para i de 1 a n -1 hacer t ← i / ( n 1); x ← (1− t ) * a + t * b ; s ← s + f ( x ) s ← f ( b ) / 2 return ( b - a ) * s / n
Integrar sobre un disco
Considere ahora el problema de integrar una función dada , con dos argumentos, en un disco con centro dado) y radio dado . Este problema se puede reducir a dos integrales de una sola variable anidadas mediante el cambio de variables
El siguiente código implementa la fórmula del lado derecho :
procedimiento DiskIntg ( g , xc , yc , R , n ) procedimiento gring ( z ): procedimiento gpolar ( t ): float x , y x ← xc + z * cos ( t ) y ← yc + z * sin ( t ) return g ( x , y ) entero m ← round ( n * z / R ) return z * Intg ( gpolar , 0, 2 * π, m ) return Intg ( gring , 0, R , n )
Este código utiliza el procedimiento de integración Intg en dos niveles. El nivel externo (última línea) usa Intg para calcular la integral de por variando de 0 a . El nivel interno (penúltima línea) definecomo la integral de línea de sobre el círculo con centro y radio .
Historia
Los parámetros de procedimiento fueron inventados antes de la era de las computadoras electrónicas, por el matemático Alonzo Church , como parte de su modelo de cálculo lambda .
Los parámetros de procedimiento como una característica del lenguaje de programación fueron introducidos por ALGOL 60 . De hecho, ALGOL 60 tenía un poderoso mecanismo de paso de parámetros de " llamada por nombre " que podría simplificar algunos usos de los parámetros de procedimiento; ver Dispositivo de Jensen .
Los parámetros de procedimiento fueron una característica esencial del lenguaje de programación LISP , que también introdujo el concepto de cierre de función o funarg . El lenguaje de programación C permite que los punteros de función se pasen como parámetros, que logran el mismo fin, y a menudo se utilizan como devoluciones de llamada en la programación dirigida por eventos y como controladores de errores. Sin embargo, solo unos pocos compiladores de C modernos permiten definiciones de funciones anidadas, por lo que sus otros usos son relativamente poco comunes. Los parámetros de procedimiento también se proporcionaron en Pascal, junto con definiciones de procedimiento anidadas; sin embargo, dado que Pascal estándar no permitía la compilación por separado, la función también se usó poco en ese lenguaje.
Ver también
- Puntero de función
- Programación funcional
- Problema de Funarg
- Patrones de diseño (informática)