El dispositivo de Jensen es una técnica de programación informática que explota la llamada por nombre . Fue ideado por el informático danés Jørn Jensen , que trabajó con Peter Naur en Regnecentralen . Trabajaron en el compilador GIER ALGOL , una de las primeras implementaciones correctas de ALGOL 60 . ALGOL 60 utiliza llamada por nombre. [1] [2] [3] Durante su discurso del Premio Turing, Naur menciona su trabajo con Jensen en GIER ALGOL.
"El dispositivo de Jensen aprovecha la llamada por nombre y los efectos secundarios ". La llamada por nombre es una convención de paso de argumentos que retrasa la evaluación de un argumento hasta que realmente se usa en el procedimiento, lo cual es una consecuencia de la regla de copia para procedimientos. ALGOL introdujo la llamada por nombre.
Un ejemplo clásico del dispositivo de Jensen es un procedimiento que calcula la suma de una serie: [ 4] [5] [6]
procedimiento real Suma(k, l, u, ak) valor l, u; entero k, l, u; real ak; los comentarios k y ak se pasan por nombre; comenzar real s; s := 0; para k: = l paso 1 hasta que lo hagas s := s + ak; Suma := s fin ;
En el procedimiento, la variable de índice k
y el término de suma ak
se pasan por nombre. La llamada por nombre permite que el procedimiento cambie el valor de la variable de índice durante la ejecución del for
bucle. La llamada por nombre también hace que el ak
argumento se reevalúe durante cada iteración del ciclo. Por lo general, ak
dependerá del cambio (efectos secundarios) k
.
Por ejemplo, el código para calcular la suma de los primeros 100 términos de una matriz real V[]
sería:
Suma(i, 1, 100, V[i]).
Durante la ejecución de Sum
, el argumento real i
se incrementará durante cada paso del for
ciclo, y cada una de las evaluaciones del procedimiento de ak
utilizará el valor actual de i
para acceder a los elementos sucesivos de la matriz V[i]
.
El dispositivo de Jensen es general. Una doble suma se puede hacer como:
Suma(i, l, m, Suma(j, l, n, A[i,j]))
La Sum
función se puede emplear para funciones arbitrarias simplemente empleando las expresiones apropiadas. Si se deseara una suma de números enteros, la expresión sería simplemente Sum(i,1,100,i);
, si se desea una suma de cuadrados de números enteros, entonces Sum(i,1,100,i*i);
, y así sucesivamente. [7] Una ligera variación sería adecuada para iniciar una integración numérica de una expresión mediante un método muy similar al de Sum
.
La evaluación de ak
se implementa con un procesador , que es esencialmente una subrutina con un entorno. El golpe seco es un cierre sin argumentos. Cada vez que un procedimiento necesita el valor de su argumento formal, simplemente llama al procesador. El procesador evalúa el argumento real en el alcance del código de llamada (no en el alcance del procedimiento).
En ausencia de esta función de paso por nombre, sería necesario definir funciones que incorporen aquellas expresiones que se pasarán de acuerdo con los protocolos del lenguaje informático, o crear una función compendio junto con algún arreglo para seleccionar la expresión deseada para cada uso.
Otro ejemplo es el GPS (General Problem Solver), descrito en el ALGOL 60 confidencial de DE Knuth y JN Merner . [8]
procedimiento real GPS(I, N, Z, V); reales I, N, Z, V; comenzar por I := 1 paso 1 hasta N hacer Z := V; GPS := 1 extremo ;
A continuación se muestra una declaración única que encuentra m-ésimo primo usando GPS.
I := GPS(I, si I=0 entonces -1.0 más I, P, si I=1 entonces 1.0 más si GPS(A, I, Z, si A=1 entonces 1.0 más si entier(A)×(entier (I)÷entier(A))=entier(I) ∧ A<I entonces 0.0 más Z) = Z entonces ( si P<m entonces P+1 más I×GPS(A, 1.0, I, -1.0)) si no P)
(Nota: en el artículo original, la expresión cerca del final es GPS(A, 1.0. I, 0.0)
, debido a un caso de esquina en la especificación de la semántica de la declaración for de ALGOL 60 ).
El dispositivo de Jensen se basa en la llamada por nombre, pero la llamada por nombre es sutil y tiene algunos problemas. En consecuencia, llamar por nombre no está disponible en la mayoría de los idiomas. Knuth comenta que ALGOL 60 no puede expresar un increment(n)
procedimiento que aumente su argumento en uno; la llamada increment(A[i])
no realiza la acción esperada si i
es un funcional que cambia con cada acceso. [9] Knuth dice: "El uso de funciones de definición 'macro' para ampliar el lenguaje, en lugar de depender únicamente de procedimientos para este propósito, da como resultado un programa en ejecución más satisfactorio".
Otros señalan que un procedimiento de llamada por nombre que intercambia su argumento puede tener problemas sutiles. [10] Un procedimiento de intercambio obvio es:
procedimiento swap(a, b) entero a, b; comenzar temperatura entera; temperatura := a; a := b; b := temperatura; fin;
El procedimiento funciona bien para muchos argumentos, pero la invocación de swap(i,A[i])
es problemática. El uso de la regla de copia conduce a las tareas:
temperatura := yo; yo := A[yo]; A[i] := temperatura;
El problema es que la segunda asignación cambia i
, por lo que A[i]
la tercera asignación probablemente no será el mismo elemento de matriz que al principio. Si, por otro lado, el procedimiento se codificara al revés (guardándose b en temporal en lugar de a ), entonces se produciría la acción deseada, a menos que se invocara como swap(A[i],i)
. (Uno más seguro swap()
sería suficiente temp1 := a; temp2 := b; a := temp2; b := temp1;
.)
Sum
requiere un real
argumento para el término, por lo que se supone una conversión de tipos.increment(A[increment(j)])
se incrementará j
dos veces.