stringtranslate.com

Parámetro de procedimiento

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 particularmente efectiva y conveniente en lenguajes que admiten definiciones de funciones locales , como Pascal y el dialecto GNU moderno de C. Es aún más así cuando se dispone de cierres de funciones . 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 procedimentales están relacionados de alguna manera 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 básico

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 ): devuelve  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 y compatible con la forma en que P utiliza su parámetro f . Por ejemplo, si definimos

procedimiento  más ( x , y ): devuelve  x + y

entonces podemos llamar 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 ): devuelve  u / v

entonces la llamada P ( quot ) devolverá quot (6,3)* quot (2,1) = (6/3)*(2/1) = 4. Finalmente, si definimos

procedimiento  malvado ( z ) devuelve 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 hay. Por ejemplo, en el lenguaje de programación C el ejemplo anterior podría escribirse como

int P ( int ( * f )( int a , int b )) { devolver f ( 6 , 3 ) * f ( 2 , 1 ); }          

En principio, la función actual 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 la misma cantidad de argumentos y los argumentos correspondientes deben tener el mismo tipo. Sin embargo, los nombres de los argumentos no necesitan ser los mismos, como se muestra en los ejemplos de más y quot anteriores. Sin embargo, algunos lenguajes de programación pueden ser más restrictivos o más liberales en este sentido.

Alcance

En los lenguajes que permiten parámetros procedimentales, las reglas de alcance suelen definirse de tal forma que los parámetros procedimentales se ejecutan en su ámbito nativo. Más precisamente, supongamos que la función actf se pasa como argumento a P , como su parámetro procedimental f ; y f se llama entonces desde dentro del cuerpo de P . Mientras actf se ejecuta, ve el entorno de su definición. [ ejemplo necesario ]

La implementación de estas reglas de alcance no es trivial. Para cuando finalmente se ejecuta actf , los registros de activación donde se encuentran sus variables de entorno pueden estar en una profundidad arbitraria en la pila. Este es el llamado problema funarg descendente .

Ejemplo: ordenación por inserción genérica

El concepto de parámetro procedimental se explica mejor con ejemplos. Una aplicación típica es la siguiente implementación genérica del algoritmo de ordenamiento por inserción , que toma dos parámetros enteros a , b y dos parámetros procedimentales prec , swap :

procedimiento  isort ( a , b , prec , swap ): entero  i , j ; ia ; mientras  ib  hacer  ji ; mientras  j > a  y  prec ( j , j −1) hacer  swap ( j , j −1); jj −1; ii +1;

Este procedimiento se puede utilizar para ordenar los elementos x [ a ] ​​a x [ b ] de alguna matriz x , de tipo arbitrario , en un orden especificado por el usuario. Los parámetros prec y swap deben ser dos funciones , definidas por el cliente, ambas tomando dos enteros r , s entre a 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. La función swap debe intercambiar los contenidos de x [ r ] y x [ s ], y no devolver 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, almacenadas en cualquier medio y organizadas en cualquier estructura de datos que proporcione acceso indexado a elementos individuales de la matriz. (Sin embargo, tenga en cuenta que existen algoritmos de ordenamiento que son mucho más eficientes que el ordenamiento 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 ; tz [ r ]; z [ r ] ← z [ s ]; z [ s ] ← t

Ordenar filas de una matriz

Para otro ejemplo, supongamos que M es 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 de modo que todos los valores pares estén antes que 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 ; tM [ i , r ]; M [ ​​i , r ] ← M [ i , s ]; M [ ​​i , s ] ← t ;para  i del 1 al 10 hacer  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 isort no necesita saberlo.

Procedimiento de clasificación de vectores

El siguiente ejemplo utiliza 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  entonces  devuelve  v [ r ] < v [ s ]; de lo contrario  devuelve  v [ r ] > v [ s ]; procedimiento  vswap ( r , s ): entero  t ; tv [ 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 C estándar, obtener este efecto requeriría un código bastante complicado y/o no seguro para subprocesos .


Ejemplo: fusionar dos secuencias

El siguiente ejemplo ilustra el uso de parámetros procedimentales para procesar estructuras de datos abstractas independientemente de su implementación concreta. El problema consiste en fusionar dos secuencias ordenadas de registros en una única secuencia ordenada, donde la naturaleza de los registros y el criterio de ordenación pueden ser elegidos por el cliente. La siguiente implementación supone únicamente que cada registro puede ser referenciado por una dirección de memoria y que existe 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  merge ( A , B , prec , nextA , appendA , nextB , appendB ): dirección  ini , fin , t  ini ← Λ; fin ← Λ mientras  A ≠ Λ o B ≠ Λ hacer  si  B = Λ o ( A ≠ Λ y  B ≠ Λ y  prec ( A , B )) entonces  tnextA ( A ) fin ← appendA ( A , fin ); si  ini = Λ entonces  inifin  At  de lo contrario  tnextB ( B ) finappendB ( B , fin ); si  ini = Λ entonces  inifin  Bt  devolver  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 venir 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 debe 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 enlazadas

Para ilustrar el uso del procedimiento de fusión genérico, aquí está el código para fusionar dos listas enlazadas simples , comenzando con 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.NEXT que apunta al siguiente nodo; donde los campos de información están en orden creciente en cada lista. Las listas de entrada se desmantelan mediante la fusión y sus nodos se utilizan para construir la lista de salida.

procedimiento  listmerge ( R , S ): procedimiento  prec ( r , s ): devuelve  r . INFO < s . INFO procedimiento  siguiente ( x ): devuelve  x . SIGUIENTE procedimiento  append ( x , fin ) si  fin ≠ Λ entonces  fin . NEXTx  x . NEXT ← Λ devuelve  x  devolver  fusión ( R , S , anterior , siguiente , anexar , siguiente , anexar )

Fusión de vectores

El código siguiente ilustra la independencia del procedimiento de fusió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 punto flotante, en orden decreciente. Las matrices de entrada no se modifican y la secuencia fusionada de valores se almacena en un tercer vector W [0] a W [ m + n −1]. Como en el lenguaje de programación C, suponemos que la expresión "& V " produce la dirección de la variable V , "* p " produce 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  arraymerge ( U , m , V , n , W ): procedimiento  prec ( r , s ): return (* r ) > (* s ) procedimiento  nextU ( x ): si  x = &( U [ m −1]) entonces  devuelve Λ de lo contrario  devuelve  x + 1 procedimiento  nextV ( x ): si  x = &( V [ n −1]) entonces  devuelve Λ de lo contrario  devuelve  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 ← Λ devolver  fusión ( U , V , prec , nextU , append , nextV , append )

Ejemplo: Integral definida

Integración sobre un intervalo

El siguiente procedimiento calcula la integral aproximada f ( x ) d x de una función real f dada en un intervalo dado [ a , b ] de la recta real . El método numérico utilizado es la regla del trapecio con un número dado de pasos n ; los números reales se aproximan mediante números de punto flotante.

procedimiento  Intg ( f , a , b , n ): float  t , x , s ; entero  i  si  b = a  entonces  devuelve 0 xa ; sf ( a )/2; para i de 1 a  n −1 hace  ti /( n +1); x ← (1− t )* a + t * b ; ss + f ( x ) sf ( b )/2 devuelve ( ba )* s / n

Integración sobre un disco

Consideremos ahora el problema de integrar una función dada , con dos argumentos, sobre un disco con centro dado ( ) y radio dado . Este problema se puede reducir a dos integrales anidadas de una sola variable 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  xxc + z * cos ( t ) yyc + z * sin ( t ) devuelve  g ( x , y ) entero  mronda ( n * z / R ) retorno  z * Intg ( gpolar , 0, 2*π, m ) devolver  Intg ( gring , 0, R , n )

Este código utiliza el procedimiento de integración Intg en dos niveles. El nivel externo (última línea) utiliza Intg para calcular la integral de para los que varían de 0 a . El nivel interno (penúltima línea) define como 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 procedimentales como 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 podía simplificar algunos usos de los parámetros procedimentales; véase Dispositivo de Jensen .

Los parámetros procedimentales 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 se utilizan a menudo como devoluciones de llamadas en la programación basada en 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 procedimentales también se proporcionaron en Pascal, junto con las definiciones de procedimientos anidados; sin embargo, dado que Pascal estándar no permitía la compilación por separado, la característica también se usó poco en ese lenguaje.

Véase también