Capítulo 7. Abstracciones (subprogramas). Lenguajes de Programación Carlos Ureña Almagro Curso 2011-12 Contents 1 Introducción 2 2 Procedimientos 3 3 Funciones 6 4 Parámetros 12 4.1 Mecanismos de paso de parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 4.2 Mecanismos en Ada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.3 Mecanismos en C/C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 4.4 Mecanismos en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.5 Mecanismos en C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 5 Efectos laterales 20 6 Orden de evaluación 21 6.1 Operadores o funciones no estrictas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 22 1 1 INTRODUCCIÓN Introducción El concepto de abstracción En general, una abstracción es un cálculo (asociado a un nombre) que puede ser utilizado en distintas partes de un programa. Se llama abstracción porque el programador puede usarla abstrayéndose de cómo se lleva a cabo el cálculo, y centrándose en que efectos tiene. Ventajas de las abstracciones Las abstracciones son esenciales para: • Mejorar la legibilidad: permite programas más cortos, más simples. • Mejorar la facilidad de escritura: permite trasladar más fácilmente los diseños a código. • Permitir o facilitar la reusabilidad. • Particionar los programas, disminuyendo la cohesión. • Facilitar el trabajo en equipo. Tipos de abstracciones Las abstracciones se pueden clasificar en base al tipo de cálculo asociado, aquí veremos estos dos tipos: • Abstracciones procedurales (procedimientos): el cálculo es la ejecución de una determinada sentencia. Constituyen abstracciones sobre sentencias. • Abstracciones funcionales (funciones): el cálculo es la evaluación de una determinada expresión. Constituyen abstracciones sobre expresiones. Declaración de abstracciones Las abstracciones pueden verse como asociaciones entre un nombre y el cálculo correspondiente. • Para establecer esta asociación es necesario escribir un declaración. • En la declaración se especifica el nombre de la abstracción y el cálculo asociado. 2 2 2 PROCEDIMIENTOS Procedimientos El concepto de procedimiento Una procedimiento es, en general, una abstracción sobre una orden. • Un procedimiento tiene asociado un nombre. Esta asociación se produce mediante una declaración. • El nombre del procedimiento se puede usar (en el ámbito de su declaración) como una orden más. A estas apariciones del nombre se les denomina llamadas al procedimiento Declaración de procedimiento Una declaración de un procedimiento es un tipo de declaración que crea el procedimiento y le da un nombre. Consta de: • El nombre del procedimiento (un identificador: id) • Los parámetros formales (opcionalmente) • La sentencia asociada (posiblemente compuesta, y con declaraciones locales) (la llamaremos O). Declaraciones de procedimientos. • En el ámbito de la declaración, id se puede usar como una orden, completamente equivalente a O • A la orden O se le suele llamar el cuerpo del procedimiento. • La elaboración de la declaración no suele conllevar ningún procesamiento en tiempo de ejecución, excepto en lenguajes interpretados Declaraciones de procedimientos. Supongamos una declaración de un procedimiento con estos elementos: • Nombre: actumax • Sentencia: O = if ( x > y ) max = x ; else max = y ; • Esta declaración debe aparecer en el ámbito de la declaración de max, x e y • En el ámbito de la declaración, el identificador actumax() se puede usar como una sentencia, equivalente a la sentencia if 3 2 PROCEDIMIENTOS Llamadas a procedimientos En el ámbito de la declaración, se puede escribir actumax() como una sentencia más, siendo equivalente a su sustitución por el cuerpo del procedimiento: Estas sentencias: Son equiv. a estas: x = 1 ; y = 2 ; actumax ( ) ; x = 1 ; y = 2 ; i f ( x > y ) max = x ; e l s e max = y ; z = max ; z = max ; Declaraciones de procedimientos en Ada En Ada, la declaración de procedimientos es un tipo de declaración que puede aparecer en el lugar de cualquier declaración de variables o tipos, su sintaxis es esta: p r o c e d u r e ident [ parametros ] i s [ declaraciones ] begin ordenes end [ ident ] ; • Los parámetros y las declaraciones son opcionales, • La orden es obligatoria (puede ser skip, que no hace nada). Declaraciones de procedimientos en C/C++/C#/Java En estos lenguajes, las declaraciones de procedimientos son como cualquier otra declaración, excepto que no pueden aparecer dentro de una declaración de procedimiento o función. v o i d ident ( [ parametros ] ) { [ declaraciones − y − ordenes ] } • Los parámetros, las ordenes y/o las declaraciones son opcionales • La paréntesis son obligatorios Declaraciones de procedimientos en Python En este lenguaje, la declaración de un procedimiento es un tipo de sentencia 4 2 PROCEDIMIENTOS • Cuando se ejecuta la sentencia de declaración, se asocia el identificador a la sentencia o sentencias que forman el cuerpo. a partir de ese momento, el identificador es usable para llamar al procedimiento • El ámbito de la declaración de subprogramas es similar al de las variables: – una declaración de un subprograma P, incluida en el cuerpo de un subprograma Q, es local a Q (se destruye al acabar Q) – una declaración de un subpr. no incluida en otro es global: permanece hasta el final del programa (o hasta que se haga otra asociación al identificador) Declaraciones de procedimientos en Python Hay dos sintaxis posible, una es en una sola línea: d e f ident ( [ parametros ] ) : orden o en varias: d e f ident ( [ parametros ] ) : orden1 orden2 ··· ordenn Declaraciones de procedimientos en Python Respecto a la sintaxis anterior: • En la segunda forma, la sentencias que forman el cuerpo son todas aquellas con indentación superior a la indentación de def • al menos debe haber una sentencia en el cuerpo • La construcción puede aparecer en el lugar de cualquier sentencia • Es importante tener en cuenta que un identificador asociado a una función puede asociarse a otra función o a una variable en cualquier momento, lo cual puede lugar a errores Asociación dinámica parcial en Python En Python, los identificadores libres del cuerpo se interpretan en el entorno formado por las variables globales existentes en la llamada, excluyendose las locales • Se puede considerar una forma de asociación dinámica no completa, o asociación dinámica parcial • El comportamiento puede ser inesperado a veces 5 3 FUNCIONES Asociación dinámica limitada en Python Este ejemplo ilustra como se interpretan los identificadores libres en el cuerpo de un subprograma: def escribe_zz ( ) : p r i n t zz def llamar_escribe_zz ( ) : zz = ( 4 , 4 , 4 , 4 ) escribe_zz () l l a m a r _ e s c r i b e _ z z ( ) # e r r o r ( no e x i s t e ’ z z ’ ) z z =1; l l a m a r _ e s c r i b e _ z z ( ) # e s c r i b e " 1 " z z=" h o l a " ; l l a m a r _ e s c r i b e _ z z ( ) # e s c r i b e " h o l a " Ejemplo de declaración de procedimiento en Python El siguiente es un ejemplo que ilustra el hecho de que las declaraciones de procedimientos son sentencias, y por tanto un identificador puede asociarse a distintos subprogramas en distintas ejecuciones (o incluso en una ejecución) x = ... y = ... if x > y : def proce ( ) : p r i n t " hola " else : def proce ( ) : p r i n t " adios " proce ( ) def proce ( ) : x = 2 3 # ok : r e d e f i n e ’ p r o c e ’ Funciones El concepto de función • Una función es, en general, una abstracción sobre un proceso de cálculo que produce un valor único como resultado (cada vez que se realiza dicho proceso de cálculo). También se le llama en la literatura abstracción funcional. • Una función tiene asociado un nombre. Esta asociación se produce mediante una declaración. • El nombre de la función se puede usar como una expresión más (en el ámbito de su declaración) • También se le llaman abstracciones funcionales 6 3 FUNCIONES Funciones en los paradigmas funcional e imperativo • En los lenguajes funcionales, una función es una abstracción sobre una expresión (el proceso de cálculo es la evaluación de la expresión). • En los lenguajes imperativos, un función es una abstracción sobre un tipo especial de orden que produce un valor resultado (el proceso de cálculo es la ejecución de dicha orden para obtener el valor). El término función • El término función hace referencia a un determinado cálculo, completamente distinto a una función matemática, aplicación, o correspondencia (que es un tipo especial de relación entre elementos de dos conjuntos matemáticos) • En un programa, las abstracciones funcionales son la forma natural implementar las funciones matemáticas Órdenes que producen un resultado. • En algunos lenguajes (p.ej. Eiffel, Pascal), estas órdenes incluyen necesariamente una asignación a una variable local especial que almacena el resultado. • Esta variable local no está declarada implicitamente • En Pascal, su nombre coincide con el de la función • Al finalizar la ejecución de la orden, el valor de dicha variable es el valor resultado. Órdenes que producen un resultado. • En otros lenguajes (Ada,C/C++, Java, C#) estas órdenes incluyen necesariamente una orden especial: return e • Aquí, e es una expresión del mismo tipo que la función • La ejecución de la órden return e dentro de una orden (posiblemente compuesta) O supone: – La evaluación de e y la obtención de un valor v – La finalización de la ejecución de O, con v como valor resultado. 7 3 FUNCIONES Declaraciones de funciones Una declaración de una función es un tipo de declaración que crea la función y le da un nombre. • La declaración permite escribir la expresión u orden una vez, pero usarla varias veces como una expresión en distintos puntos del programa. • Usar la expresión u orden quiere decir aquí evaluar la expresión (o ejecutar la orden) para obtener un valor. A esto se suele denominar llamar o invocar a la función. Elementos de las declaraciones de funciones: La declaración de una función consta de: • El nombre de la función (un identificador ident) • El nombre (o descriptor en algunos casos) del tipo del valor resultado, referenciando un tipo T • Los parámetros formales (opcionalmente). • Una expresión e o una sentencia que produce un valor O Declaraciones de funciones • En el ámbito de la declaración, id se puede usar como una expresión, completamente equivalente a e • A la expresión e o a la orden O se les suele llamar el cuerpo de la función. • La elaboración de la declaración no suele conllevar ningún procesamiento. Ejemplo de declaración de una función (supone asociación estática) • El identificador es difxy • La expresión que forma el cuerpo es x-y (si el cuerpo estuviese formado por una sentencia, seria x-y) • La declaración debe aparecer en el ámbito de la declaración de x e y • En su ámbito, difxy() constituye una expresión equivalente a la expresión x-y 8 3 FUNCIONES Llamadas a funciones. En el ámbito de la declaración, se puede escribir difxy() como una expresión más, siendo equivalente a su sustitución por x-y Estas sentencias: Son equiv. a estas: x = z ; y = 2 ∗ h ; z = h ∗ difxy () + 3 ; x = z ; y = 2 ∗ h ; z = h ∗ (x − y) + 3 ; Ejemplo de función con una órden Supongamos que ahora el cuerpo de una función area es una orden, como por ejemplo c=c+1 ; return 2*pi*r ; Estas sentencias: Son equiv. a estas: r = 35.67 ; a = ( k +10.0)∗ area ( ) + 1 ; r = 35.67 ; tmp1 = k +10.0 c = c+1 ; tmp2 = 2∗ p i ∗ r ; a = tmp1 ∗ tmp2 +1; . Declaraciones de funciones en Ada La forma de estas declaraciones (que pueden aparecer donde cualquier declaración de variable o tipos) es: f u n c t i o n ident [ parametros ] r e t u r n identtipo i s [ declaraciones ] begin ordenes end [ ident ] ; • El tipo devuelto no puede ser anónimo • Los parámetros y las declaraciones son opcionales, • La orden es obligatoria, y debe incluir return Declaraciones de procedimientos en C/C++/C#/Java En estos lenguajes, la declaración puede aparecer en el lugar de una declaración de variable o tipo, excepto dentro de otro subprograma. 9 3 FUNCIONES descriptortipo ident ( [ parametros ] ) { [ declaraciones − y − ordenes ] } • Los parámetros, las ordenes y/o las declaraciones son opcionales. • Los paréntesis son obligatorios • La orden puede incluir return Declaraciones de funciones en Python En este lenguaje, no se distingue entre procedimientos y funciones, sino que simplemente contempla declaraciones de subprogramas en general. • Por tanto, la sintaxis usada para declarar subprogramas es la que ya vimos para los procedimientos. • Cualquier declaración de subprograma puede incluir sentencias return. • Si un subprograma siempre acaba con un return de un valor de un tipo concreto T podemos ver ese subprograma como una función de tipo T. Declaraciones de funciones en Python • En distintas ejecuciones de un subprograma, pueden ejecutarse sentencias return con expresiones de tipos distintos, o no ejecutarse ningun return en absoluto (el subprograma acaba como los procedimientos) • Por tanto, el intérprete no puede asignar un tipo a un subprograma, ni clasificarlo como procedimiento o función, aún cuando eventualmente devuelva un valor via un return (las "funciones" no tienen asociado un tipo). Llamadas a subprogramas en Python Se puede escribir una llamada a un subprograma en el lugar de una expresión o en el de una sentencia • Una llamada que no acaba con return y que se usa como expresión produce el valor None de tipo NoneType. • Una llamada que acaba con return y que se usa como sentencia implica que el valor resultado se ignora (no se almacena en ningún sitio). 10 3 FUNCIONES Funciones lambda en Python Al igual que muchos lenguajes funcionales, el lenguaje Python permite escribir expresiones de tipo función. • Estas expresiones, al evaluarse, producen como resultado una función. A estas funciones se les suele llamar funciones lambda • Las funciones así obtenidas son anónimas, ya que no tienen asociado un nombre • Estas funciones pueden almacenarse en variables o parámetros (que serán entonces de tipo función), de forma que pueden ser invocadas usando el nombre de la variable o el parámetro Expresiones de tipo ’función lambda’ La sintaxis de estas expresiones es como sigue: lambda [ parametros ] : expresion • Al evaluarse, estas expresiones producen como resultado una función (o mas exactamente, una referencia a una función), cuyo cuerpo es la expresión que se indique. • Pueden aparece en tres contextos: – a la izquierda de los parámetros actuales a los que se va a aplicar – a la derecha de una asignación – en una lista de parámetros actuales. Ejemplo de expresiones de tipo ’función lambda’ Aquí hay ejemplos de funciones lambda (sin parámetros) p r i n t ( lambda : " h o l a " # e s c r i b e " hola " )() x = 2 ; y = 3 p r i n t 4+2∗( lambda : x ∗y ) ( ) − 1 # escribe ’15 ’ x = 4 ; y = 4 f = lambda : x+y+2 p r i n t 2∗ f ( ) + 3 # escribe ’23 ’ 11 4 4 PARÁMETROS Parámetros Utilidad de los parámetros En la forma descrita, los subprogramas leen o modifican siempre las mismas variables. • Si queremos que un subprograma actué sobre distintas variables, debemos de usar unas variables especificas asociadas al subprograma. • Dichas variables deben ser inicializadas antes de la llamada, y/o leídas después de la misma Utilidad de los parámetros Ejemplo: guardar en una variable entera (c) el máximo de otras dos (a y b) x = a ; y = b ; // inicializacion actumax ( ) ; c = max ; / / lectura del resultado Aquí, actumax se define supone definifo como en las transparencias anteriores. Utilidad de los parámetros Otro ejemplo es una función de nombre maximo para calcular el máximo de dos variables enteras, de nombres x e y. El cuerpo de la función es la siguiente sentencia (escrita en C/C++) return y < x ? x : y ; Un ejemplo de uso sería el siguiente: x = a ; y = b ; d = 2∗ maximo + 1 ; return y < x ? x : y ; Utilidad de los parámetros. En los ejemplos anteriores, x,y y max se usan como variables ligadas al procedimiento actumax, mientras que x e y, además, también esta ligadas a la función maximo. Hay que tener en cuenta que, respecto a estas variables: • su ámbito debe incluir tanto declaración de actumax como la llamada a dicho procedimiento • la escritura/lectura añade sentencias al programa (lo hace más complejo). • no deberían ser usadas fuera del subprograma. 12 4 PARÁMETROS Utilidad de los parámetros. Para poder hacer esto de forma más simple, evitando los problemas descritos, se diseñó e implementó el concepto de parámetros de los subprogramas. El concepto de parámetros. Los parámetros son variables o valores (constantes) locales a la orden o a la expresión incluida en el cuerpo de la definición de un subprograma. • La inicialización se hace en la llamada al subprograma, incluyéndose las expresiones adecuadas. • La lectura de los resultados se hace al finalizar el subprograma Ventajas de los parámetros. El uso de parámetros hace a los programas más simples, legibles, escribibles y fiables, ya que, al usarlos: • No es necesario escribir explícitamente las asignaciones al inicio o al final (el programa es más legible y más corto) • Al ser locales al subprograma, no hay que preocuparse por el ámbito de los parámetros (mas flexibilidad) • No es posible usar los parámetros fuera del subprograma (menos errores por colisiones de nombres) Parámetros formales y parámetros actuales • A las variables (o constentes) locales se les denomina parámetros formales • Al igual que las variables o constantes, cada parámetro tiene asociado un nombre (y, en muchos lenguajes, un tipo). • A las expresiones de inicialización incluidas en la llamada, se les denomina parámetros actuales • Los parámetros actuales, cuando sirven para recoger un resultado al final, deben ser accesos a variables. Llamadas a subprogramas con parámetros: • En la llamada, se especifican los parámetros actuales • En la inmensa mayoría de los lenguajes, los parámetros actuales se incluyen entre paréntesis tras el nombre del subprograma, separados por comas. • En algunos pocos lenguajes (p.ej. Ada), los parámetros actuales pueden estar precedidos por el nombre del parámetro formal 13 4.1 Mecanismos de paso de parámetros 4 PARÁMETROS Ejemplo de función con parámetros La función maximo que hemos visto antes se puede definir con dos parámetros, que sirven para "recoger" los valores sobre los que se quiere calcular el máximo de ellos. • Los parametros serán de nombre px y py, y de tipo entero • El cuerpo de la función será la sentencia: return px > py ? px : py ; Ejemplo de función con parámetros La orden: z = maximo ( h +1 , 3 ) + 1 ; es equivalente a esta otra orden: i n t tmp ; { i n t px = h +1 , py = 3 ; tmp = px > py ? px : py ; } z = tmp + 1 ; las variables locales px y py (son los parametros formaes) se destruyen al acabar el bloque (ya que están en el stack). La variable (también local) tmp se usa exclusivamente para almacenar temporalmente el resultado. 4.1 Mecanismos de paso de parámetros Mecanismos de paso de parámetros • Existen varias alternativas posibles para interpretar la declaración de parámetros formales, como declaraciones de constantes o variables locales al cuerpo de la función. • A las distintas formas se les denomina mecanismos de paso de parámetros. • En concreto, un parámetro formal puede ser: 1. Una constante 2. Una variable (que no es una referencia) 3. Una variable de un tipo referencia 14 4.2 Mecanismos en Ada 4 PARÁMETROS Mecanismos de parámetros: 1. Paso por copia En los casos en los que el parámetro formal es una variable local (no referencia), se crea una variable local al cuerpo, se le llama paso por copia, y, a su vez, hay varias opciones: (1.a) La variable local se inicializa al comienzo con el resultado de evaluar el parámetro actual. Se denomina paso por copia de valor. (1.b) La variable no se inicializa a la entrada, pero su valor al final se copia sobre el parámetro actual (que en este caso debe ser un acceso a una variable). Se denomina paso por copia de resultado (1.c) Se incializa al inicio y se copia al final (tambien aquí el parametro actual debe ser una referencia a una variable). Se llama paso por copia de valor-resultado Mecanismos de parámetros: 2. Paso por constante En otros casos, la entidad declarada localmente es una constante, luego el identificador local esta asociado, en cada llamada, al valor resultado de evaluar el correspondiente parámetro actual. Se le llama paso por constante) y se puede implementar de dos formas semánticamente equivalentes: (2.a) Copiando el valor a una variable local de solo lectura (esto se suele hacer para tipos primitivos) (2.b) Copiando una referencia a una zona de memoria con el valor u objeto especificado en el parámetro actual (se suele usar para tipos array, registros, o tipos-referencia en general). Los accessos (de lectura) se hacen implicitamente vía esa referencia. Mecanismos de parámetros: 3. Paso por referencia Otra posibilidad es que el parámetro formal sea una referencia a otra variable de un tipo fijo. Este mecanismo se denomina paso por referencia. • El parámetro actual tiene que ser forzosamente un acceso a una variable. • Es el mecanismo que se usa para los tipos-referencia en los lenguajes que los contemplan (Java/C#) • La referencia es una variable local que puede ser: (3.a) modificable (puede pasar referenciar a otras variables distintas del parámetro actual) (3.b) no modificable (referencia siempre al parámetro actual) 4.2 Mecanismos en Ada Paso de parámetros en Ada Por copia de resultado (caso 1.b), o copia de valor-resultado (1.c) 15 4.2 Mecanismos en Ada 4 PARÁMETROS ident : o u t nombre_tipo ; −− c o p i a de r e s u l t . ident : i n o u t nombre_tipo ; −− c o p i a de v a l o r − r e s u l t . • nombre_tipo es un nombre de un tipo primitivo (no referencias) existente Paso de parámetros en Ada Por constante (caso 2) ident : nombre_tipo ; ident : i n nombre_tipo ; • nombre_tipo es un nombre de un tipo cualquiera existente (no referencias) • si nombre_tipo es un tipo primitivo, se usa el esquema (2.a) • si nombre_tipo es registro o array, se usa (2.b) Paso de parámetros en Ada Por referencia (caso 3.b), para tipos access (hay dos posibilidades) ident : a c c e s s nombre_tipo ; ident : nombre_tipo_access ; • nombre_tipo_re f erencia es un nombre de un tipo referencia (access) previamente declarado, • nombre_tipo es cualquier tipo primitivo: en este segundo caso – el parámetro actual no puede ser la referencia nula – se crea un tipo anónimo nuevo, local al subprograma. Paso de parámetros en Ada Por referencia (caso 3.b), para tipos registro o array ident : o u t nombre_tipo ; ident : i n o u t nombre_tipo ; • nombre_tipo es el nombre de un tipo array o registro • ambas formas son equivalentes • esto fue fijado en Ada 95 (en Ada 83 había cierta ambigüedad en esto). 16 4.3 Mecanismos en C/C++ 4.3 4 PARÁMETROS Mecanismos en C/C++ Paso de parámetros en C/C++ Los mecanismos de paso son más sencillos, ya que: • La sintáxis y semántica de la declaración de parámetros es exactamente igual a la sintaxis y semántica de la declaración de variables (se cumple estrictamente el principio de correspondencia) • En todos los casos, una declaración de un parámetro es equivalente a una declaración local de una variable o constante con la misma sintaxis • No existe copia de resultado (1.b) ni copia de valor-resultado (1.c) • El paso por referencia se hace con punteros o referencias-C++ Paso por copia de valor en C/C++ La siguiente declaración de parámetro en C/C++ nombre_tipo_prim ident , supone paso por copia de valor (1.a), aquí nombre_tipo_ident es el nombre o alias de algún tipo primitivo Paso por referencia en C/C++ Este mecanismo puede obtener mediante alguna de estas declaraciones de parámetros: tipo tipo tipo tipo ∗ ident / / c a s o ( 3 . a ) ∗ c o n s t ident / / c a s o ( 3 . b ) & ident / / c a s o ( 3 . b ) −− r e f . no n u l a ident [ [ expresion ] ] / / c a s o ( 3 . b ) Paso por constante en C/C++ Al igual que para las variables, los parámetros constantes se declaran anteponiendo const al descriptor de tipo. Hay estas formas: const const const const const tipo tipo tipo tipo tipo ident / / c a s o ( 2 . a ) ∗ ident / / c a s o ( 2 . b ) ∗ c o n s t ident / / c a s o ( 2 . b ) & ident / / c a s o ( 2 . b ) ( r e f . no n u l a ) ident [ [ expresion ] ] / / c a s o ( 2 . b ) 17 4.4 4.4 Mecanismos en Java 4 PARÁMETROS Mecanismos en Java Paso de parámetros en Java En este lenguaje solo hay dos posibilidades: • Los tipos-valor se pasan por copia de valor • Los tipos-referencia se pasan por copia de referencia • No existe paso por copia de resultado ni valor-resultado • No existe paso por constante Al igual que en C/C++, las declaraciones de parámetros son (sintáctica y semánticamnte) semejantes a las declaraciones de variables. 4.5 Mecanismos en C# Paso de parámetros en C# El esquema es similar al de Java, pero además: • Los tipos-valor se pueden pasar por referencia si se indica explícitamente • Existen dos formas de paso por referencia de tipos-valor – una se corresponde con la habitual de otros lenguajes – otra sirve específicamente para paso por referencia de parámetros de salida (parámetros con resultados), en los cuales no es posible leer el valor inicial de entrada Mecanismos de paso de parámetros en C# Paso por copia de valor. nombre_tipo_valor ident , Se crea una variable local, modificable. Mecanismos de paso de parámetros en C# Paso por referencia. Existen estas posibilidades: nombre_tipo_re f i d , nombre_tipo [ ] i d , r e f nombre_tipo_valor ident , o u t nombre_tipo_valor ident , 18 4.5 Mecanismos en C# 4 PARÁMETROS Si se usan las dos últimas formas, es necesario incluir ref u out en la llamada, precediendo al parámetro actual. Esto mejora la legibilidad. Mecanismos de paso de parámetros en C# Paso por referencia de salida Cuando se usa la forma: o u t nombre_tipo_valor ident , • ident constituye una referencia local a una variable v con un valor que se considera indeterminado a la entrada • en el subprograma, el parámetro no se puede leer si no es en sentencias que siguan a alguna asignación al mismo. Mecanismos de paso de parámetros en Python • En Python, los subprogramas y las funciones lambda admiten parámetros formales • Los parámetros formales (al igual que las variables) no tienen asociado un tipo (el tipo del parámetro actual y, por tanto, el del valor que se pasa pueden ser distinto en cada llamada) • Para tipos inmutables (cadenas, números, tuplas) : se pasa una referencia al valor resultado de evaluar el parámetro formal. Dicho valor en memoria no es modificable, así que el mecanismo puede considerarse como una forma de paso por constante implementada con referencias (caso 2.b). • Para tipos mutables (listas, diccionarios, clases) : el paso es por referencia a todos los efectos, se puede modificar la variable referenciada, con efectos permanentes, o bien usar la referencia local para apuntar a otro valor (caso 3.a). Valores inmutables como parámetros en Python Cuando se usa como parámetro actual un valor de tipos inmutables en una llamada a un subprograma: • El parámetro formal es (en esa llamada) una referencia local que puede usarse para apuntar a otro valor mediante una asignación a la misma de dicho nuevo valor • El nuevo valor estará almacenado en otra localización de memoria, el original no se modifica. • Por tanto, estas modificaciones no afectan al parámetro actual (no tiene efectos a la salida). 19 5 EFECTOS LATERALES Ejemplo de valores inmutables como parámetros en Python Este ejemplo ilustra como funciona el paso de parámetros inmutables: def incrementa ( n ) : n = n + 1 print n incrementa (10) m = 20 i n c r e m e n t a (m) print m 5 ## e s c r i b e ’11 ’ ## e s c r i b e ## e s c r i b e ’21 ’ ’20 ’ Efectos laterales Efectos laterales en expresiones y funciones • Un efecto lateral en una expresión cuando ocurre cuando difieren los estados de ejecución previo y posterior a la evaluación de la expresión (es decir, se han modificado, creado o destruido alguna variable) • Si la expresión es el cuerpo de una función, diremos que la función tiene efectos laterales. • La funciones con efectos laterales actúan, además de como expresiones, como órdenes Desventajas de los efectos laterales Los efectos laterales: • Disminuyen la legibilidad de los programas • Desvirtúan el concepto de función, que tiende a confundirse con el de procedimiento. • Hacen incorrectas ciertas premisas, por ejemplo la conmutatividad de la suma: si f y g son funciones enteras (sin parámetros) con efectos laterales, la expresión f () + g() no es necesariamente equivalente g() + f () • Impiden ciertas optimizaciones, como, por ejemplo, si e es una expresión de tipo entero con efectos laterales, e + e no puede sustituirse por 2 ∗ e Evitar efectos laterales Los efectos laterales: • Deben ser evitados por el programador, mejorando así la legibilidad y fiabilidad de los programas 20 6 ORDEN DE EVALUACIÓN • No pueden ser detectados en un programa (ni siquiera ejecutándolo) por un procesador de un lenguaje imperativo, excepto en casos triviales 6 Orden de evaluación Los ordenes de evaluación son distintas formas de decidir cuando se evalúa un operando o un parámetro formal durante la evaluación de una expresión. Hay tres posibilidades: • Orden ambicioso • Orden normal • Orden perezoso Orden ambicioso Por lo descrito hasta ahora: • La evaluación de una expresión e1 ope2 supone la evaluación de e1 y e2 antes de realizar el cálculo asociado al operador op • La evaluación de un expresión de llamada f (e1 , e2 ) supone la evaluación de e1 y e2 antes de realizar el calculo asociado a la función de nombre f A esto se le llama evaluación ambiciosa de las expresiones o funciones (eager evaluation) Evaluación en orden normal Otra posibilidad es la evaluación en orden normal • La evaluación de una expresión e1 ope2 supone la evaluación de e1 y/o e2 cada vez que sea necesario durante el calculo asociado al operador op • La evaluación de un sentencia de llamada f (e1 , e2 ) supone la evaluación de e1 y e2 cada vez que se acceda al valor del correspondiente parámetro formal dentro del cuerpo de la función Evaluación en orden normal • puede ocurrir que durante la evaluación de una expresión, algún parámetro actual y/o algún operando no llegue a ser evaluado (no es posible con el orden ambicioso). • el uso de memoria y el tiempo de calculo puede diferir respecto del orden ambicioso 21 6.1 Operadores o funciones no estrictas 6 ORDEN DE EVALUACIÓN Orden perezoso Una tercera posibilidad es la evaluación en orden perezoso (lazy evaluation) • Es similar a la evaluación en orden normal, excepto que tras la primera evaluación de un parámetro formal u operando, el valor obtenido se almacena y se usa de nuevo en futuras referencias, durante el cálculo. • Constituye una opción intermedia entre las dos formas anteriores. Orden perezoso Características de la evaluación en orden perezoso • Al igual que el orden normal, la evaluación no ocurre necesariamente siempre. • Al igual que el orden ambicioso, el parámetro actual o el operando se evalúan una sola vez como mucho. Equivalencia de órdenes de evlauación Los tres ordenes no son equivalentes. Supongamos la siguiente declaración de dos funciones en C int c = 0 ; int f () { c=c+1 ; r e t u r n c ; } i n t g ( i n t a ) { c=1 ; r e t u r n a+a ; } Equivalencia de órdenes de evlauación Los tres ordenes de evaluación no son equivalentes. La evaluación de g(f()) usando cada orden de evaluación produciría: • orden ambicioso : 2 ( f se evalúa una vez, con c==0, a vale 1) • orden perezoso: 4 ( f se evalúa una vez, con c==1, a vale 2) • orden normal: 5 ( f se evalúa dos veces, con c == 1 y 2, a vale 2 y 3) 6.1 Operadores o funciones no estrictas Operadores y funciones no estrictas 22 6.1 Operadores o funciones no estrictas 6 ORDEN DE EVALUACIÓN Operadores y funciones no estrictas en alguno de sus parámetros u operandos Se dice que una función u operador es no estricto en alguno de sus parámetros u operandos si la evaluación de una llamada a la función o al operador puede llevarse a cabo en algunos casos sin evaluar dicho parámetro u operando El operador and then Este es un operador binario lógico con dos operandos lógicos, parecido a and , pero no exactamente igual. Evaluar e1 and then e2 supone evaluar e1 , y después • si el resultado es f alse, la evaluación acaba con el valor f alse, • si el resultado es true, se evalúa e2 , obteniéndose el valor b. La evaluación acaba con el valor b como resultado El operador or else Este es un operador binario lógico con dos operandos lógicos, parecido a or , pero no exactamente igual. Evaluar e1 or else e2 supone evaluar e1 , y después • si el resultado es true, la evaluación acaba con el valor true • si el resultado es f alse, se evalúa e2 (obteniéndose el valor b), y la evaluación acaba con el valor b Operadores no estrictos y estrictos • and then y or else son no estrictos en su segundo operando, su implementación supone evaluación en orden normal. • and y or son ambos estrictos en sus dos operandos, su implementación supone orden ambicioso. por tanto, and no es equivalente a and then , ni or es equivalente a or else Ejemplo de ausencia de equivalencia. A modo de ejemplo de la anterior afirmación, supongamos que b es un array de n valores lógicos, con índices entre 0 y n − 1, e i es una variable que vale n: • La evaluación de: (i < n and b[i ]) falla • La evaluación de: (i < n and then b[i ]) produce f alse 23 6.1 Operadores o funciones no estrictas 6 ORDEN DE EVALUACIÓN Subprogramas estrictos en lenguajes imperativos • Cuando el orden de evaluación de las llamadas es ambicioso, no es posible declarar funciones no estrictas en alguno de sus parámetros (los parámetros actuales se evalúan siempre). • Este es el caso de los lenguajes imperativos más comunes. Equivalencia de órdenes de evaluación: Los tres órdenes difieren únicamente debido alguno de estos dos motivos: • la presencia de efectos efectos laterales • la evaluación de una expresión falla en orden ambicioso, pero no en orden normal o perezoso (aún cuando no haya efectos laterales) Equivalencia de órdenes de evaluación La propiedad de Church-Roses. En ausencia de efectos laterales, se cumple que: • si una expresión se evalúa sin errores en algún orden, entonces se evalúa sin errores en orden normal • si una expresión se evalúa sin errores en más de un orden de evaluación, entonces todos esos órdenes de evaluación producen el mismo resultado. fin del capítulo. 24