Paradigmas de la Programación
16 de marzo de 2020
Nota de descargo
1
Índice general
1. Introducción y Motivación 5
1.1. Nuestro objeto de estudio . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2. Cuestiones fundamentales . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3. Objetivos del curso “Paradigmas de la Programación” . . . . . . . . . . . . 5
1.4. Para qué sirve esta materia . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5. Estructura de este apunte . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4. Tipos de datos 15
4.1. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5. Estructura en bloques 17
5.1. Código estructurado vs. código spaghetti . . . . . . . . . . . . . . . . . . . 17
5.2. Estructura de bloque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.3. Activation records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.3.1. Detalle de ejecución de un activation record . . . . . . . . . . . . . 23
5.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
6. Control de la ejecución 27
6.1. Funciones y procedimientos . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.2. Pasaje de parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2
6.2.1. Semántica de pasaje por valor (call-by-value) . . . . . . . . . . . . . 28
6.2.2. Semántica de pasaje por referencia call-by-reference . . . . . . . . . 29
6.2.3. Sutilezas entre pasaje por valor y pasaje por referencia . . . . . . . 31
6.2.4. Semántica de pasaje por valor-resultado . . . . . . . . . . . . . . . 32
6.2.5. Pasaje de parámetros no estricto (perezoso) . . . . . . . . . . . . . 33
6.2.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.3. Alcance estático vs. alcance dinámico . . . . . . . . . . . . . . . . . . . . . 39
6.3.1. Ejemplo de diferencia entre los dos tipos de alcance . . . . . . . . . 39
6.3.2. Naturalidad y overhead del alcance estático . . . . . . . . . . . . . 40
6.3.3. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.4. Recursión a la cola . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.4.1. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.5. Alto orden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.5.1. Funciones de primera clase . . . . . . . . . . . . . . . . . . . . . . . 46
6.5.2. Pasar Funciones a otras Funciones . . . . . . . . . . . . . . . . . . . 47
6.6. Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.6.1. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.7. Recolección de basura (garbage collection) . . . . . . . . . . . . . . . . . . 58
6.7.1. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
7. Orientación a objetos 62
7.1. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
8. Frameworks de programación 84
8.1. Inversión de Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.1.1. Desacoplamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
8.1.2. Boilerplate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
8.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9. Paradigma funcional 89
9.1. Expresiones imperativas vs. expresiones funcionales . . . . . . . . . . . . . 89
9.2. Propiedades valiosas de los lenguajes funcionales . . . . . . . . . . . . . . . 91
9.3. Problemas naturalmente no declarativos . . . . . . . . . . . . . . . . . . . 93
9.4. Concurrencia declarativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
9.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
10.Programación lógica 99
10.1. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
3
12.Paradigma de scripting 110
12.1. Tipos de lenguajes de scripting . . . . . . . . . . . . . . . . . . . . . . . . 111
12.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
4
Capı́tulo 1
Introducción y Motivación
5
equipo de desarrollo del software está compuesto de personas con poca experiencia, será
mejor elegir un lenguaje que ayude a prevenir los errores humanos, como por ejemplo
Python.
6
Capı́tulo 2
7
Las funciones computables son la formalización de la noción intuitiva de algoritmo, en
el sentido de que una función es computable si existe un algoritmo que puede hacer el
trabajo de la función, es decir, dada una entrada del dominio de la función puede devolver
la salida correspondiente.
El concepto de función computable es intuitivo, se usa para hablar de computabilidad
sin hacer referencia a ningún modelo concreto de computación. Cualquier definición, sin
embargo, debe hacer referencia a algún modelo especı́fico de computación, como por ejem-
plo máquina de Turings, las funciones µ recursivas, el lambda cálculo o las máquinas de
registro.
La tesis de Church-Turing1 dice que las funciones computables son exactamente las
funciones que se pueden calcular utilizando un dispositivo de cálculo mecánico dada una
cantidad ilimitada de tiempo y espacio de almacenamiento. De manera equivalente, esta
tesis establece que cualquier función que tiene un algoritmo es computable.
Algunas función no computable famosas son Halting problem o calcular la Complejidad
de Kolmogorov.
8
5 < expresion > --> < expresion > * < expresion >
< expresion > --> < expresion > / < expresion >
El único sı́mbolo no terminal en esta gramática es expresion, que también es el sı́mbolo
inicial. Los terminales son {+,-,*,/,(,),numero}, donde numero representa cualquier
número válido.
La primera regla (o producción) dice que una <expresion> se puede reescribir como
(o ser reemplazada por) un número. Por lo tanto, un número es una expresión válida. La
segunda regla dice que una expresión entre paréntesis es una expresión válida, usando una
definición recursiva de expresión. El resto de reglas dicen que la suma, resta, producto o
división de dos expresiones también son expresiones válidas.
Pueden encontrar algunos ejemplos simples de gramáticas independientes de contexto
en https://www.cs.rochester.edu/~nelson/courses/csc_173/grammars/cfg.html
Algunos fenómenos de los lenguajes de programación no se pueden expresar con na-
turalidad mediante gramáticas independientes de contexto. Por ejemplo es difı́cil expre-
sar la obligación de que una variable sea declarada antes de ser usada, o bien descri-
bir la asignación múltiple de variables, como podemos hacer por ejemplo en Python:
(foo, bar, baz) = 1, 2, 3.
Pregunta 1:
Piense cómo serı́a una gramática independiente de contexto para expresar la
obligación de que una variable sea declarada antes de ser usada, o la asigna-
ción múltiple de variables. ¿Cómo es la gramática resultante? ¿Resulta fácil de
pensar, fácil de entender?
Las gramáticas que describen los lenguajes de programación se usan para verificar la
correctitud de un programa escrito en ese lenguaje, mediante un compilador. En un compi-
lador, estas limitaciones se solucionan en parte porque hay módulos de procesamiento del
programa posteriores a la gramática que tratan algunos de los fenómenos que las gramáticas
no pueden capturar.
Sin embargo, también se usan las gramáticas para describir los lenguajes para consumo
humano. En ese uso, uno desea que la gramática pueda expresar de forma compacta todas
las propiedades relevantes del lenguaje. Para conseguirlo, se suelen usar mecanismos ad-
hoc para aumentar su poder expresivo, como por ejemplo predicados asociados a reglas.
Por ejemplo, si queremos establecer una restricción de tipos en una determinada regla,
podemos hacerlo de la siguiente forma:
< expresion > --> numero
2 < expresion > --> ( < expresion > )
< expresion > --> < expresion > + < expresion >
4 < expresion > --> < expresion > - < expresion >
< expresion > --> < expresion > * < expresion >
6 < expresion > --> < expresion > / < expresion > ^ < expresion >
es_de_tipo Float
9
< expresion > --> < expresion > div < expresion > ^ < expresion >
es_de_tipo Int
En la práctica, las restricciones de tipos las realiza el análisis semántico del compilador.
Para ejercitar este mecanismo de expresividad, resuelvan el ejercicio 2.1.
Pregunta 2:
¿Por qué puede haber más de un tipo de semántica operacional, mientras que
el lambda cálculo y la semántica denotacional son sólo uno?
10
2.6. Ejercicios
2.1. En EBNF no podemos expresar lenguajes Turing completos, por esa razón necesita-
mos aumentar el poder expresivo de las gramáticas libres de contexto. Esto se hace
en la implementación con el paso posterior al análisis sintáctico, el análisis semántico.
Informalmente, podemos aumentar una gramática libre de contexto con predicados.
Escriba una EBNF aumentada con los predicados necesarios para poder expresar:
2.3. Cuando se usa una gramática independiente de contexto para describir un lenguaje
puede suceder que la gramática resulte ambigua. ¿Cuándo decimos que una gramática
es ambigua? ¿Qué problemas presenta la ambigüedad, y cómo se pueden solucionar?
2.5. De la misma forma que obtuvimos la derivación de una expresión algebraica usando
la gramática de dı́gitos, use la siguiente gramática:
1 program = ’ PROGRAM ’ , espacio , identificador , espacio ,
’ BEGIN ’ , espacio ,
3 { asignacion , " ; " , espacio } ,
’ END . ’ ;
5 identificador = caracter_alfabetico , {
caracter_alfabetico | digito } ;
numero = [ " -" ] , digito , { digito } ;
7 string = ’" ’ , { todos_caracteres - ’" ’ } , ’" ’ ;
11
asignacion = identificador , " := " , ( numero |
identificador | string ) ;
9 caracter_alfabetico = " A " | " B " | " C " | " D " | " E " | " F " |
"G"
| "H" | "I" | "J" | "K" | "L" | "M"
| "N"
11 | "O" | "P" | "Q" | "R" | "S" | "T"
| "U"
| "V" | "W" | "X" | "Y" | "Z" ;
13 digito = " 0 " | " 1 " | " 2 " | " 3 " | " 4 " | " 5 " | " 6 " | " 7 " |
"8" | "9" ;
espacio = ? caracteres_espacio ? ;
15 all caracteres = ? todos_caracteres_visibles ? ;
2.6. Muestre con un ejemplo que la siguiente gramática es ambigua, y modifı́quela para
que se convierta en una gramática inambigua. ¿Qué estrategias puede usar para
hacerlo?
1 < bloque > ::= { < sentencias > }
< sentencias > ::= < sentencias > < sentencia > | < sentencia >
3 < sentencia > ::= < asignacion > | < sentenciaIf > | < bloque >
< sentenciaIf > ::= if ( < expresion > ) < sentencia > |
5 if ( < expresion > ) < sentencia > else
< sentencia >
< asignacion > ::= < variable > := < expresion >
7 < expresion > ::= < variable > | < variable > < operador >
< variable >
< operador > ::= + | - | * | / | < | > | ’ and ’ | ’ or ’
9 < variable > ::= x | y | z
12
Capı́tulo 3
13
acceder a través de los otros identificadores.
Veamos un ejemplo:
1 x : int ;
y : int ;
3 x : = y + 3;
En la asignación, el valor almacenado en la variable y se añade a 3 y el resultado se
almacena en la ubicación para x. Notemos que las dos variables se utilizan de manera
diferente: usamos el valor almacenado en y, independientemente de su ubicación, pero en
cambio usamos la ubicación de x, independientemente del valor almacenado en x antes de
que ocurra la asignación.
La ubicación de una variable se llama su L-valor y el valor almacenado en esta ubicación
se llama el R-valor de la variable.
En ML, los L-valores y los R-valores tienen diferentes tipos. En otras palabras, una
región de la memoria asignable tiene un tipo diferente de un valor que no se puede cambiar.
En ML, un L-valor o región asignable de la memoria se llama celda de referencia. El tipo
de una celda de referencia indica que es una celda de referencia y especifica el tipo de valor
que contiene. Por ejemplo, una celda de referencia que contiene un número entero tiene
tipo int ref.
Cuando se crea una celda de referencia, se debe inicializar a un valor del tipo correcto.
ML no tiene variables no inicializadas o punteros colgantes. Cuando una asignación cambia
el valor almacenado en una celda de referencia, la asignación debe ser coherente con el tipo
de la celda de referencia: una celda de referencia de tipo entero siempre contendrá un
entero, una celda de referencia de tipo lista contendrá siempre (o hará referencia a) una
lista, y ası́.
ML tiene operaciones para crear celdas de referencia, para acceder a su contenido y
para cambiar su contenido: ref, ! y :=, que se comportan como sigue:
14
Capı́tulo 4
Tipos de datos
4.1. Ejercicios
4.1. En base al siguiente árbol de tipos, calcule el tipo de datos de la función implementada
en ML.
λ
& !
& !
& !
& !
!
& !
& @
&
& " !
& " !
" !
& !
& " !
& @
#
2
% #
%
% #
+ @
int → int → int
$ #
$ #
$ #
g h
λ 15
& !
& !
& !
& !
!
& !
& @
&
& " !
& " !
" !
4.3. Calcule el tipo de datos de las siguientes funciones en ML. Provea el árbol sintáctico
de la función y aplique de forma explı́cita el algoritmo de inferencia de tipos, ya sea
sobre el árbol mismo o como sistema de ecuaciones.
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué
consiste. Cómo podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de
tipado débil?
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué
consiste. Cómo podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de
tipado débil?
4.6. En el siguiente texto, explique si el diseño de Crystal con duck typing mantiene los
principios de seguridad del tipado estático, qué errores se podrı́an producir y qué
errores no.
The language allows you to program using duck-typing, in a way. For exam-
ple if you have a variable ”duck.and you assign a Duck to it and invoke
”quack.on it, given a Duck quacks, it will work. Now if later you assign a
Horse to it, and the horse doesn’t quack, you will get a compile error. So,
the compiler catches the error at compile time. Now, if you assign another
type, say, a Goose, and it quacks, then the code continue to compile fine
(provided that it returns the correct type, which again is determined by its
methods, and so on). The new type will be Duck | Goose. In short: there’s
no “undefined method” errors at runtime in Crystal.
16
Capı́tulo 5
Estructura en bloques
17
Este fragmento de código spaghetti podrı́a traducirse a un lenguaje con bloques de la
siguiente forma:
1 10 FOR i = 1 TO 10
20 PRINT i ; " squared = " ; i * i
3 30 NEXT i
40 PRINT " Program Completed . "
5 50 END
Para evitar este problema, la mayorı́a de los lenguajes de programación modernos pro-
porcionan alguna forma de bloque. Un bloque es una región del texto del programa con
inicio y fin explı́citos e inambiguos. Esta región del texto permite organizar de forma
explı́cita la lógica del programa. Pero además, posibilita sucesivas abstracciones sobre el
flujo de ejecución.
En primer lugar, los bloques nos permiten hacer declaraciones de variables locales. Por
ejemplo:
1 { int x = 2;
{ int y = 3;
3 x = y +2;
}
5 }
En esta sección del código hay dos bloques. Cada bloque comienza con una llave izquier-
da, {, y termina con una llave derecha, }1 . El bloque exterior comienza con la primera llave
izquierda y termina con la última llave derecha. El bloque interno se anida en el interior
del bloque exterior, comienza con la segunda llave izquierda y termina con la primera llave
derecha.
La variable x se declara en el bloque exterior y la variable y se declara en el bloque
interior. Una variable declarada dentro de un bloque se dice que es una variable local para
ese bloque. Las variables que no están declaradas en un bloque, sino en algún otro bloque
que lo contiene, se dice que es una variable global para el bloque más interno. En este
ejemplo, x es local en el bloque exterior, y es local para el bloque interior, y x es global
para el bloque interior.
Gracias a la estructura de bloques se pueden implementar mecanismos de gestión de la
memoria que permiten, por ejemplo, el llamado a funciones de forma recursiva.
Las versiones de Fortran de los años 1960 y 1970 no eran estructuradas por bloques.
En Fortran histórico, todas las variables, incluyendo todos los parámetros de cada proce-
dimiento (llamado subrutina en Fortran) tenı́an una ubicación fija en memoria. Esto hacı́a
imposible llamar a un procedimiento de forma recursiva, ya fuera directa o indirectamente.
Si el procedimiento Fortran P llama Q, Q llama R, R y luego intenta llamar a P, la segunda
1
Los bloques se pueden delimitar con cualquier sı́mbolo arbitrario, de hecho, los lenguajes de progra-
mación modernos ya no usan llaves sino espacios en blanco, que resultan más naturales para el humano.
Un buen ejercicio es pensar la gramática que describirı́a la sintaxis de delimitadores de bloque de Python.
18
llamada a P no se puede realizar. Si P se llama por segunda vez en esta cadena de llamadas,
la segunda llamada escribirı́a sobre los parámetros y la dirección de la primera llamada,
por lo tanto, no se podrı́a volver correctamente a la primera llamada.
Cada declaración es visible dentro de una determinada región de texto del programa,
llamada bloque. Los bloques pueden ser anidados, pero no pueden superponerse par-
cialmente. En otras palabras, si dos bloques contienen expresiones o declaraciones en
común, entonces un bloque debe estar enteramente contenida dentro del otro.
Pregunta 1:
Den un ejemplo de bloques superpuestos parcialmente, y argumenten qué
problemas podemos encontrar si permitimos este tipo de estructuras en
nuestros programas.
19
Figura 5.1: Modelo simplificado de la computadora que usamos para describir la semántica
operacional.
variables globales que se declaran en algún bloque que contiene al bloque actual y por
lo tanto hay que acceder a ellos desde un activation record que se colocó en la pila
de ejecución antes del bloque actual.
20
encuentra la instrucción de programa que se está ejecuntando actualmente. Normalmente
se incrementa después de ejecutar cada instrucción.
El puntero de entorno nos sirve para saber cuáles son los valores que se asignan a las
variables que se están usando en una parte determinada del código. En los lenguajes no
estructurados por bloques, la memoria de datos es no estructurada, todo el espacio es heap,
y por lo tanto los valores que se asignan a las variables son visibles desde cualquier parte
del código. En cambio, en los lenguajes estructurados por bloques, se guardan en el heap
algunos datos para los que necesitamos persistencia (por ejemplo, variables globales), en
los registros guardamos algunos datos para los que queremos rápido acceso, y en la pila de
ejecución o stack se guardan los datos estructurados en forma de pila, lo cual hace posible
que se instancien variables locales y argumentos distintos para cada llamada de función.
Esto posibilita que las funciones puedan ser más abstractas y que se puedan hacer llamadas
recursivas.
Pregunta 2:
¿Por qué no se pueden hacer llamadas recursivas sin bloques ni activation re-
cords? Explique usando Fortran como ejemplo.
El stack o pila de ejecución funciona de la siguiente forma: cuando el programa entra en
un nuevo bloque, se agrega a la pila una estructura de datos que se llama activation record
o marco de pila (stack frame), que contiene el espacio para las variables locales declaradas
en el bloque, normalmente, por la parte de arriba de la pila. Entonces, el puntero de
entorno apunta al nuevo activation record. Cuando el programa sale del bloque, se retira
el activation record de la pila y el puntero de entorno se restablece a su ubicación anterior,
es decir, al puntero de entorno correspondiente a la función que llamaba a la función que
ha sido desapilada. El activation record que se apila más recientemente es el primero en
ser desapilado, a esto se le llama disciplina de pila.
En la figura 5.2 podemos ver la información que hay en un activation record de arqui-
tectura x86. Vemos que en esta arquitectura hay, además de espacio para variables locales,
también espacio para los argumentos que puede recibir una función, que funcionan como
variables locales con respecto a la función pero que son escritos por la función que la llama.
También encontramos espacio para guardar resultados intermedios, en el caso de que sea
necesario. Podemos observar que hay dos direcciones de memoria donde se guardan datos
importantes: una, el control link, contiene el que será el puntero de entorno cuando se
desapile el activation record actual, es decir, el puntero de entorno del activation record
correspondiente a la función que llamaba a la función del activation record actual. La otra
dirección de memoria distinguida es la llamada dirección de retorno, que es donde se va a
guardar el resultado de la ejecución de la función, si es que lo hay.
Los activation records pueden tener tamaños variables, por lo tanto, las operaciones que
ponen y sacan activation records de la pila guardan también en cada activation record una
variable con la dirección de memoria que corresponde a la parte superior del registro de
activación anterior. Esta variable o puntero (porque apunta a una dirección de memoria)
se llama control link, porque es el enlace que se sigue cuando se devuelve el control a la
instrucción en el bloque precedente.
21
Figura 5.2: Esquema de los contenidos de un activation record tı́pico de la arquitectura
x86.
Pregunta 3:
Aunque la mayorı́a de lenguajes estructurados por bloques se ejecutan con una
pila, las funciones de alto orden pueden hacer que falle la disciplina de pila.
¿Por qué?
Pregunta 4:
Se pueden hacer algunas optimizaciones sobre el funcionamiento de los activa-
tion records, cuáles se le ocurren?
Pregunta 5:
Los programas estructurados por bloques y su traslado a activation records en
una parte de la memoria permiten las llamadas recursivas. Pero hacer muchas
llamadas recursivas puede crear un problema en la memoria. ¿Cómo se llama
este problema y cómo lo solucionan los compiladores de algunos lenguajes?
Pregunta 6:
¿Por qué pueden tener tamaños variables los activation records?
22
En la siguiente sección vamos a ver cómo funcionan los activation records en ejemplos
de ejecución. Para una explicación alternativa, pueden consultar la sección correspondiente
de la wikipedia sobre la estructura de pila de llamadas.
23
Figura 5.3:
Figura 5.4:
24
el formato de cada activation record y almacenar esta información como parte del código
compilado.
En general, un activation record también puede contener espacio para resultados inter-
medios, como hemos visto en la figura 5.2. Estos son valores que no reciben un identificador
de variable explı́cito en el código, pero que se guardan temporalmente para facilitar algún
cálculo.
Por ejemplo, el registro de activación para este bloque,
1 { int z = ( x + y ) * (x - y ) ; }
puede tener espacio para asignar valor a la variable z pero también para los valores x+y
y x-y, porque los valores de estas subexpresiones pueden tener que ser evaluados y alma-
cenados en algún lugar antes de multiplicarse. Si hacemos zoom en el tercer estado de la
pila de la figura 5.3, veremos lo que se muestra en la figura 5.4, con espacio para valores
temporales.
En las computadoras modernas, hay suficientes registros en memoria para guardar
estos resultados intermedios en los registros y no en la pila. No hablamos de ubicación de
registros porque es un tema que compete a los compiladores y no al diseño de lenguajes de
programación.
5.4. Ejercicios
5.1. El siguiente es un ejemplo de “spaghetti code”. Reescrı́balo de forma que NO use
saltos (GOTO), y en cambio use programación estructurada.
1 10 i = 0
20 i = i + 1
3 30 PRINT i ; " squared = " ; i * i
40 IF i >= 10 THEN GOTO 60
5 50 GOTO 20
60 PRINT " Program Completed . "
7 70 END
5.2. Diagrame los estados de la pila de ejecución en los diferentes momentos de la ejecución
del siguiente programa, mostrando cómo se apilan y desapilan los diferentes activation
records a medida que se va ejecutando el programa. Puede representar los activation
records con la información de variables locales y control links.
1 { int x =2 { int y =3; x = y +2;}}
25
else
5 return n + sum (n -1) ;
}
7 sum (4) ;
26
Capı́tulo 6
Control de la ejecución
27
del parámetro (la misma ubicación del bloque que hace la llamada a función o una nueva
ubicación especı́fica de la función).
La mayorı́a de los lenguajes de programación actuales evalúan los parámetros reales de
forma estricta, no perezosa, pero hay algunas excepciones. (Una de las razones para que un
lenguaje o optimización de programa podrı́a querer retrasar la evaluación de un parámetro
real es que la evaluación podrı́a ser costosa y el parámetro real podrı́a no ser utilizado en
algunas llamadas.) Entre los mecanismos que evalúan el parámetro real antes de ejecutar
el cuerpo de la función, los más comunes son el pasaje por referencia y el pasaje por
valor. Estos dos mecanismos se diferencian entre ellos por la ubicación de memoria donde
se almacena el valor del parámetro. El pasaje por referencia pasa el L-valor, es decir, la
ubicación en memoria, del parámetro real, mientras que el pasaje por valor pasa el R-valor,
es decir, el valor que hay en la ubicación de memoria.
La diferencia entre el pasaje por valor y el pasaje por referencia es importante para el
programador por varias razones:
Eficiencia Pasar por valor puede ser ineficaz para grandes estructuras de datos, si hay que
copiar ese valor. Pasar por referencia puede ser menos eficiente que pasar por valor
pequeñas estructuras que se adapten bien en la pila, porque cuando los parámetros
se pasan por referencia, debemos resolver la referencia de un puntero para obtener
su valor.
Hay dos formas de describir la semántica del pasaje de parámetros: con diagramas de
la memoria y la pila que muestren si la pila contiene una copia del parámetro real o una
referencia. Otra forma es traducir el código a un lenguaje que distinga entre R-valores y
L-valores, que es lo que vamos a hacer aquı́.
28
1 f ( x ) = { x : = x + 1; volver x };
... . f ( y ) ... ;
Si el parámetro se pasa por valor e y es una variable entera, entonces este código tiene
el mismo significado que el siguiente código ML:
fun f ( z : int ) = let x = ref z in x :=! x +1;! x end ;
2 ... f (! y ) ... ;
Como se puede ver en el tipo, el valor pasado a la función f es un número entero. El
entero es el R-valor del parámetro real y, como lo indica la expresión !y en la llamada. En
el cuerpo de f , se asigna y se inicializa una nueva ubicación para el R-valor de y.
Si el valor de y es 0 antes de la llamada, entonces el valor de f (!y) es 1 porque la
función f incrementa el parámetro y devuelve su valor. Sin embargo, el valor de y después
de la llamada es aún 0, porque la asignación dentro del cuerpo de f solamente cambia el
contenido de una ubicación temporal.
29
Como se puede ver en el tipo, el valor pasado a la función f es una referencia, un
L-valor. Si el valor de y es 0 antes de la llamada, entonces el valor de f (!y) es 1 porque
la función f incrementa el parámetro y devuelve su valor. Sin embargo, a diferencia de la
situación para el pasaje por valor, el valor de y es 1 también después de la llamada, debido
a que la asignación dentro del cuerpo de f también cambia el valor del parámetro real.
Veamos un ejemplo, escrito en una notación asimilable a Algol, que combina pasaje por
referencia y pasaje por valor:
fun f ( pass - by - ref x : int , pass - by - value y : int )
2 begin
x := 2;
4 y := 1;
if x = 1 then return 1 else return 2;
6 end ;
var z : int ;
8 z := 0;
print f (z , z ) ;
Si traducimos este ejemplo en pseudo-Algol a ML nos da:
1 fun f ( x : int ref , y : int ) =
let val yy = ref y in
3 x := 2;
yy := 1;
5 if (! x = 1) then 1 else 2
end ;
7 val z = ref 0;
f (z ,! z ) ;
Este código, que trata a los R-valores y L-valores explı́citamente, muestra que para el
pasaje por referencia se pasa un L-valor, la referencia z. Para pasaje por valor, se pasa un
R-valor, el contenido !z de z, y se le asigna una nueva ubicación temporal.
Si se pasa y por valor tal como se ve, a z se le asigna el valor 2. En cambio, si y se
hubiera pasado por referencia, entonces x e y habrı́an sido alias de la misma variable y a
z se le habrı́a asignado el valor 1.
En este otro ejemplo tenemos una función que comprueba si sus dos parámetros son
alias:
function (y , z ) {
2 y := 0;
z :=0;
4 y := 1;
if z =1 then y :=0; return 1 else y :=0; return 0
6 }
30
Si y y z son alias, entonces asignar 1 a y asignará 1 a z también, y la función devolverá 1.
De lo contrario, la función devolverá 0. Por lo tanto, una llamada f (x, x) tendrá diferentes
resultados si los parámetros se pasan por valor o por referencia.
31
Hay varias razones por las que se puede pasar una referencia. Una razón puede ser
que el lenguaje no permita el pasaje por valor de estructuras de datos complejas, y se
pase una referencia para preservar esa estructura. También se usa de forma sistemática en
lenguajes con orientación a objetos, lo que se suele llamar call-by-sharing, call-by-object o
call-by-object-sharing.
En call-by-sharing, el pasaje de parámetros es por valor, pero se pasa una referencia a
un objeto. Por lo tanto, se hace una copia local del argumento que se pasa, pero esa copia
es una referencia a un objeto que es visible tanto desde la función llamada como desde la
función que llama.
La semántica de call-by-sharing difiere del pasaje por referencia en que las asignaciones
a los argumentos dentro de la función no son visibles a la función que llama, por lo que, por
ejemplo, si se pasa una variable, no es posible simular una asignación en esa variable en el
alcance de la función que llama. Por lo tanto, no se pueden hacer cambios a la referencia
que se ha pasado por valor.
Sin embargo, a través de esa referencia de la que la función llamada ha hecho una
copia, ambas funciones, la llamada y la que llama, tienen acceso al mismo objeto, no se
hace ninguna copia. Si ese objeto es mutable, los cambios que se hagan al objeto dentro de
la función llamada son visibles para la función que llama, a diferencia del pasaje por valor.
Este es el comportamiento de Java, Python, Ruby, JavaScript o OCaml. Sin embargo,
el término call-by-sharing no se usa mucho, se usan diferentes términos en los diferentes
lenguajes y documentaciones. Por ejemplo, en la comunidad Java dicen que Java tiene
pasaje por valor, mientras que en la comunidad Ruby dicen que Ruby tiene pasaje por
referencia, a pesar de que los dos lenguajes tienen la misma semántica. El call-by-sharing
implica que los valores que se pasan por parámetro se basan en objetos en lugar de tipos
primitivos, es decir que todos los valores están empaquetados.
Resuelva el ejercicio 6.11 para profundizar sobre este tipo de pasaje de parámetros.
32
Tal y como indica el ejemplo de simulación de valor-resultado, el orden de copia depende
del compilador, lo que implica que la misma función pueda dar resultados diferentes según
el compilador usado.
Pregunta 1:
Qué ventajas y qué desventajas tiene pasaje por valor-resultado?
Pregunta 2:
Haga un programa como en la sección anterior, que tenga diferentes resultados
si los parámetros se pasan por valor, por referencia o por valor-resultado.
Pregunta 3:
¿La evaluación de las alternativas de expresiones condicionales debe conside-
rarse estricta o perezosa?
Pregunta 4:
¿Se le ocurre alguna forma de optimización de la evaluación de expresiones
complejas, usando evaluación perezosa y las propiedades del absorbente de un
operador?
33
Pasaje por necesidad call-by-need
El pasaje por necesidad call-by-need es una versión memoizada de pasaje por nombre
donde, si se evalúa el argumento de la función, ese valor se almacena para usos posteriores.
En un entorno de ”puro”(sin efectos secundarios), esto produce los mismos resultados que
el pasaje por nombre; cuando el argumento de la función se utiliza dos o más veces, el
pasaje por necesidad es casi siempre más rápido.
Haskell es el lenguaje más conocido que utiliza call-by-need. R también utiliza una
forma de pasaje por necesidad. Variantes de .NET pueden simular llamada por necesidad
utilizando el tipo Lazy¡T¿.
Pregunta 5:
Haga un programa como en la sección anterior, que tenga diferentes resultados
si los parámetros se pasan por nombre, por necesidad o por algún otro método.
6.2.6. Ejercicios
6.1. Si el Browse del siguiente programa muestra 4, qué tipo de pasaje de parámetros
tiene?
1 declare A =2 B =4 C =1
proc { P X Y Z}
3 C =5
X=Z
5 C =4
Y=Z+A
7 end
in { P A B A+C}
9 { Browse B}
a) por valor
b) por referencia
c) por valor-resultado
d) por nombre
e) por por necesidad
6.2. Si el Browse del siguiente programa muestra 5, qué tipo de pasaje de parámetros
tiene?
1 declare A =2 B =4 C =1
proc { P X Y Z }
3 C =5
X=Z
34
5 C =4
Y=Z+A
7 end
in { P A B A + C }
9 { Browse B }
a) por valor
b) por referencia
c) por valor-resultado
d) por nombre
e) por por necesidad
a) reduce aliasing.
b) ocupa menos lugar en la memoria.
c) no tiene efectos fuera de la función llamada.
d) permite actuar en la función que llama, si el programador lo necesita.
a) reduce aliasing.
b) ocupa menos lugar en la memoria.
c) no tiene efectos fuera de la función llamada.
d) permite actuar en la función que llama, si el programador lo necesita.
a) reduce aliasing.
b) ocupa menos lugar en la memoria.
c) no tiene efectos fuera de la función llamada.
d) permite actuar en la función que llama, si el programador lo necesita.
6.6. Los métodos de pasaje por parámetros que siguen la filosofı́a de evaluación perezosa
(lazy evaluation) son...
35
e) pasaje por necesidad.
begin
integer n;
procedure p(k: integer);
begin
k := k+2;
print(n);
n := n+(2*k);
end;
n := 4;
p(n);
print(n);
end;
a) por valor?
b) por valor-resultado?
c) por referencia?
begin
integer a, b, c;
procedure p(x: integer, y: integer, z: integer);
begin
c := 5;
x := z;
c := 4;
y := z+a
end;
a := 2;
b := 4;
c := 1;
p(a, b, a+c);
print(b);
end;
a) por valor?
36
b) por valor-resultado?
c) por referencia?
6.9. En los siguientes fragmentos de programa, introduzcan los elementos necesarios para
determinar si C y Perl funcionan por call-by-value o call-by-reference:
Fragmento en C:
1 int main ( void ) {
int x =50 , y =70;
3 ...
printf ( " x= %d y= %d " , x , y ) ;
5 return 0;
}
7
Fragmento en Perl:
$x =50;
2 $y =70;
& interchange ( $x , $y ) ;
4 print " x : $x y : $y \ n " ;
6 sub interchange {
( $x1 , $y1 ) = @_ ;
8 ...
...
10 ...
print " x1 : $x1 y1 : $y \ n " ;
12 }
6.10. ¿Qué diferencia hay en Scala entre pasar una variable por valor, pero lazy, a pasar
a una variable por nombre? Cree un programa que muestre estas diferencias.
6.11. Describa qué sucede en el siguiente programa en Java, teniendo en cuenta el meca-
nismo de pasaje de parámetros que utiliza el lenguaje.
public class Point {
2 public int x ;
37
public int y ;
4
38
40 }
Pregunta 6:
¿En qué contextos le parece que serı́a útil tener alcance dinámico?
39
4 int x = y +1;
return g ( y * x )
6 };
f (3) ;
La llamada a f(3) lleva a una llamada a g(12) dentro de la función f. Esto provoca
que se evalúe la expresión x + z en el cuerpo de g. Después de la llamada a g, la pila de
ejecución tendrá registros de activación para la declaración externa de x, la invocación de
f, y la invocación de g, como se muestra en la siguiente ilustración:
Pregunta 7:
Describa el conflicto en resolución de alcance que se resuelve en C prohibiendo
la definición de una función dentro de bloques anidados.
Sin embargo, en la mayorı́a de lenguajes se usan access links para mantener la semántica
de alcance estático.
Un access link es una dirección de memoria que se guarda en el activation record de una
función y que apunta al activation record del bloque más cercano que lo contiene en el texto
40
Figura 6.1: Estado de la pila de ejecución después de la llamada a la función g dentro del
cuerpo de la función f.
del programa. Para los bloques anidados in-line esta dirección de memoria es redundante
con el control link, ya que el bloque más cercano que lo contiene siempre se corresponde
con el activation record que se ha apilado inmediatamente antes.
Para las funciones, sin embargo, el bloque más cercano que la contiene viene determina-
do por el lugar donde se declara la función. Debido a que el punto de declaración a menudo
es diferente del punto en el que se llama a una función, el access link generalmente apunta
a un registro de activación diferente al enlace de control.
Echemos un vistazo a los registros de activación y enlaces de acceso para el código del
ejemplo anterior. La figura 6.1 muestra la pila de ejecución después de la llamada a la
función g dentro del cuerpo de f.
Como ya sabemos, los control links apuntan al activation record previo en la pila, y los
dibujamos a la izquierda para dejar espacio para los access links a la derecha. El access
link para cada bloque apunta al activation record del bloque más cercano que lo contiene
en el texto del programa.
He aquı́ algunos puntos importantes acerca de esta ilustración, que sigue nuestra con-
vención de comenzar un nuevo bloque para cada declaración ML:
41
La llamada f(3) crea un activation record asociado al alcance del cuerpo de f. El
cuerpo de f ocurre dentro del alcance de la declaración de f. Por lo tanto, el access
link para f(3) apunta al activation record para la declaración de f.
6.3.3. Ejercicios
6.1. Si el resultado del siguiente programa es 19, qué tipo de alcance tiene el lenguaje de
programación en el que está escrito?
1 val x = 4;
fun f ( y ) = x * y ;
3 fun g ( x ) = let
f (3) + x ;
5 g (7) ;
a) estático
b) dinámico
6.2. Diagrame los estados de la pila de ejecución en los diferentes momentos de la ejecución
del siguiente programa, mostrando cómo se apilan y desapilan los diferentes activation
records a medida que se va ejecutando el programa. Puede representar los activation
records con la información de variables locales y control links. Añada en el activation
record la información necesaria para poder recuperar la información de la función,
con alcance estático.
1 int sum ( int n ) {
if ( n ==0)
3 return n ;
else
5 return n + sum (n -1) ;
}
7 sum (4) ;
42
1 bool isPalindrome ( char * s , int len )
{
3 if ( len < 2)
return TRUE ;
5 else
return s [0] == s [ len -1] && isPalindrome (& s [1] , len -2) ;
7 }
isPalindrome ( ‘ ‘ ada ’ ’)
6.5. Diagrame como en el ejercicio anterior, pero ahora con alcance dinámico.
1 var x =1;
function g ( z ) { return x + z ;}
3 function f ( y ) {
var x = y +1;
5 return g ( y * x ) ;
}
7 f (3)
6.7. Diagrame la pila de ejecución del siguiente programa en Bash, incluyendo la estruc-
tura de control links y access links, asumiendo que el lenguaje de programación tiene
alcance estático.
43
x =1
2 function g () { echo $x ; x =2 ; }
function f () { local x =3 ; g ; }
4 f # does this print 1 , or 3?
echo $x
En realidad este programa está escrito en Bash, que tiene alcance dinámico. Justifique
por qué puede ser que Bash se haya diseñado con lenguaje dinámico.
Qué imprimirá este programa, ahora que sabemos que tiene alcance dinámico?
6.8. Reescriba el siguiente programa para que sea un programa en C estándar (sin usar
gcc con -fnested-functions) con la misma semántica.
1 void f ( void )
{
3 // Define a function called g
void g ( void )
5 {
}
7 // Call g
g () ;
9 }
6.9. Reescriba el siguiente programa en Python para que sea válido en Perl en modo
estricto, poniendo atención al lugar de declaración de la variable, para que su alcance
sea exactamente el mismo en ambos programas.
1 if c :
a = ’ foo ’
3 else :
a = ’’
6.10. Determine si los siguientes lenguajes tienen alcance estático o alcance dinámico:
Python, Haskell, Java, Scala, C, Javascript y Ruby. Para cada uno de estos len-
guajes, cree un programa (simple) que lo demuestre.
44
evita llegar a problemas por lı́mites de hardware como el llamado stack overflow, en el que
la ejecución de un programa requiere más espacio del que hay disponible en la pila.
El principal concepto de lenguajes de programación que necesitamos entender es el
concepto de la llamada a la cola. Supongamos que la función f llama a la función g. f y g
podrı́an ser diferentes funciones o la misma, haciendo una llamada recursiva. Una llamada
a f en el cuerpo de g es una llamada de la cola si g devuelve el resultado de la llamada f
sin ningún cálculo adicional. Por ejemplo, en la función
fun g ( x ) = if x =0 then f ( x ) else f ( x ) * 2
la primera llamada a f en el cuerpo de g es una llamada de la cola, ya que el valor de
retorno de g es exactamente el valor de retorno de la llamada a f. La segunda llamada a
f en el cuerpo de g no es una llamada de la cola porque g realiza un cálculo que involucra
el valor de retorno de f antes de que g retorne.
Una función f es recursiva de cola si todas las llamadas recursivas en el cuerpo de f
son llamadas a la cola a f.
Veamos como ejemplo la función recursiva a la cola que calcula el factorial:
1 fun factcola (n , a ) = if n <= 1 then a else factcola (n -1 , n *
a);
Para cualquier entero positivo n, factcola (n, a) devuelve n!. Podemos ver que
factcola es una función recursiva de cola porque la única llamada recursiva en el cuerpo
de factcola es una llamada a la cola.
La ventaja de la recursión de cola es que podemos utilizar el mismo activation record
para todas las llamadas recursivas. Considere la llamada factcola(3,1). La figura 6.2
muestra las partes de cada activation record en el cómputo que son relevantes para la
discusión.
6.4.1. Ejercicios
6.1. Diagrame los estados de la pila de ejecución en los diferentes momentos de la ejecución
del siguiente programa, mostrando cómo se apilan y desapilan los diferentes activation
records a medida que se va ejecutando el programa. Diagrame en dos versiones: una
con un compilador que NO optimice las llamadas a la cola y otra con un compilador
que sı́ las optimice, sin una optimización especı́fica para llamadas recursivas a la
cola, es decir, sin overlay, pero sı́ consiguiendo que el tamaño de la pila se mantenga
constante.
1 def fact (n , acc ) :
if n ==0:
3 return acc
return fact (n -1 , acc * n )
5 fact (4 ,1)
45
Figura 6.2: Estado de la pila después de tres llamadas a factcola sin optimización del
compilador.
46
La función map toma una función f y una lista m como argumentos y aplica f a cada
elemento de m en orden. El resultado de map(f, m) es la lista de resultados f(x) para
elementos x de la lista m. Esta función es útil en muchos programas en los que se usan
listas. Por ejemplo, si tenemos una lista de tiempos de vencimiento para una secuencia de
eventos y queremos incrementar cada tiempo de vencimiento, podemos hacerlo pasando
una función de incremento a map.
Veremos por qué se necesitan las clausuras teniendo en cuenta las interacciones entre
el alcance estático y las funciones como argumentos y como valores de retorno. C y C ++
no admiten clausuras porque tratan de mantener el costo computacional del compilador
al mı́nimo, y las clausuras involucran un overhead no despreciable. Sin embargo, la imple-
mentación de objetos en C ++ y otros lenguajes se relaciona con la aplicación de valores
de función tal como se explican en este capı́tulo. La razón es que un clausura y un objeto
ambos combinan los datos y el código de la función.
47
Esta ilustración simplificada muestra sólo los datos contenidos en cada activation record.
En esta ilustración, la expresión x * y del cuerpo de f se muestra en la parte inferior, el
activation record asociado a la invocación de f (a través del parámetro formal h de g).
Como muestra la ilustración, la variable y es local para la función y por lo tanto se puede
encontrar en el activation record actual. Sin embargo, la variable x es global, y se encuentra
varios registros de activación por encima del actual. Para encontrar las variables globales
tenemos que seguir los access links, por lo tanto el access link del activation record inferior
debe permitirnos alcanzar el activation record de la parte superior de la ilustración.
Cuando las funciones se pasan a otras funciones, hay que guardar el access link para el
registro de la activación de cada función para que podamos encontrar las variables globales
de esa función según la semántica del alcance estático. Y para poder cubrir este requisito
necesitamos extender alguna estructura de datos de tiempo de ejecución. Esa estructura
de datos serán las clausuras.
La solución estándar para mantener el alcance estático cuando las funciones se pasan
como argumentos a otras funciones o se devuelven como resultados es utilizar una estruc-
tura de datos llamada clausura. La clausura es un par formado por un puntero a la parte de
la memoria donde se guarda el código de la función y otro puntero a un activation record.
Debido a que cada activation record contiene un access link señalando al activation record
del bloque del texto del programa más cercano que lo contiene, un puntero al alcance en
el que se declara una función también proporciona enlaces al activation record del bloque
que la contiene.
Cuando se pasa como parámetro una función a otra función, el valor real que se pasa es
un puntero a una clausura. A partir de una clausura, una función se llama de la siguiente
forma:
6.1. Asignar un activation record para la función que se llama, como es habitual.
6.2. Establecer el access link en el activation record utilizando el puntero del activation
record de la clausura.
48
Figura 6.3: Cómo se establecen los access links desde la clausura.
Podemos ver cómo esto resuelve el problema de acceso a variables para funciones que
se pasan a otras funciones como argumentos. Para ello vamos a diagramar el estado de la
pila, con los diferentes activation records, cuando se ejecuta el programa anterior, tal como
se muestra en la figura 6.3.
Podemos entender la figura 6.3 atravesando la secuencia de pasos de ejecución que
conducen a la configuración que se muestra en la figura:
6.4. Llamada a g(f): La llamada hace que se apile un activation record para la función g.
El tamaño y estructura de este record los determina el código para g. El access link se
establece hacia el activation record para el alcance donde se declara g; el access link
apunta al mismo activation record que el activation record que hay en la clausura de
g. El activation record tiene espacio para el parámetro h y la variable local x. Debido
a que el parámetro real es la clausura de f, el valor del parámetro h es un puntero a
49
la clausura de f. La variable local x tiene un valor de 7, que viene dado por el código
fuente.
6.5. Llamada a h(3): El mecanismo para la ejecución de esta llamada es el punto principal
de este ejemplo. Debido a que h es un parámetro formal de g, el código de g se
compila sin saber dónde se declara la función h. Como resultado, el compilador no
puede establecer el access link para el activation record para la llamada h(3). Sin
embargo, la clausura provee esa información: el access link para este activation record
se establece mediante el puntero de activation record de la clausura de h. Debido a
que el parámetro real es f, el acces link apunta al activation record para el alcance
en el que se declaró f. Cuando se ejecuta el código de f, el access link se utiliza para
encontrar x. En concreto, el código seguirá el access link hasta el segundo activation
record de la ilustración, seguirá un access link adicional porque el compilador sabı́a,
cuando generó el código para f, que la declaración de x se encuentra un alcance por
encima de la declaración de f, y encontrará el valor 4 para la variable global x en el
cuerpo de f.
Como se describe en el paso 5, la clausura de f hace posible que el código que se ejecuta
encuentre el activation record que contiene la declaración global de x.
Cuando podemos pasar funciones como argumentos, los access links dentro de la pila
forman un árbol. La estructura no es lineal, porque el activation record correspondiente a
la llamada a la función h(3) tiene que saltar el activation record para g(f) para encontrar
el x adecuado. Sin embargo, todos los access links apuntan hacia arriba. Por lo tanto, sigue
siendo posible asignar y desasignar activation records mediante el uso de la disciplina de
pila (último apilado, primero desapilado).
6.6. Excepciones
Mitchell 8.2
Las excepciones proporcionan una forma estructurada de salto que se puede utilizar
para salir de una construcción tal como un bloque o llamada a función. El nombre de
excepción sugiere que las excepciones deben ser utilizados en circunstancias excepcionales.
Sin embargo, es muy habitual usarlas de formas muy poco excepcionales, como por ejemplo:
Además de saltar de un punto a otro del programa, también se asocia con las excepciones
parte del manejo de memoria. Más en concreto, se pueden desapilar activation records que
ya resulten innecesarios como resultado del salto.
Todo mecanismo de excepción incluye dos construcciones lingüı́sticas:
50
levantar, rise o throw, una construcción para ejecutar una excepción, que corta parte
del cómputo actual y provoca un salto (transferencia de control),
Hay varias razones por las que las excepciones se han convertido en construcciones muy
aceptadas en la mayor parte de lenguajes. Muchos lenguajes no tienen ningún otro mecanis-
mo limpio para saltar fuera de una llamada de función, por ejemplo, abortando la llamada.
En lugar de utilizar instrucciones del tipo go to, que permiten crear código totalmente
desestructurado (código spaghetti), muchos programadores prefieren usar excepciones, que
se pueden utilizar para saltar sólo hacia la parte del programa que ya se ha cargado en la
pila, no hacia alguna parte del programa que no se ha ejecutado todavı́a.
Las excepciones también permiten a un programador pasar datos como parte del salto,
lo cual resulta muy útil si el programa trata de recuperarse de algún tipo de condición
de error. Las excepciones proporcionan un método dinámico y útil de determinar hasta
donde llega un salto. Si se declara más de un mecanismo de control, se establece cuál es el
controlador adecuado en cada caso siguiendo una semántica de alcance dinámico, lo cual
no serı́a fácil de lograr con otras formas de saltos de control.
La utilidad del alcance dinámico para estos casos se ilustra en el siguiente ejemplo, el
cálculo de la inversa de una matriz de números reales. Calculamos la multiplicación de
matrices, que se utiliza en el álgebra lineal, multiplicando las entradas de dos matrices.
Si A es una matriz e I es la matriz identidad (con unos a lo largo de la diagonal y ceros
en todas las otras posiciones), entonces la inversa A−1 de A es una matriz tal que el
producto AA−1 = I. Una matriz cuadrada tiene una inversa si y sólo si algo que se llama
el determinante de A no es igual a cero. Encontrar el determinante de una matriz requiere
aproximadamente el mismo coste computacional que invertir una matriz.
Supongamos que escribimos una función para invertir matrices que tienen entradas de
números reales. Como sólo se pueden invertir matrices con determinantes distintos de cero,
no podemos calcular correctamente la inversa de una matriz si el determinante resulta ser
cero. Una forma de manejar esta dificultad podrı́a ser la de comprobar el determinante
antes de llamar a la función de inversión. Pero esto no es una buena idea, porque calcular
el determinante de una matriz es aproximadamente tanto trabajo como invertirla. Como
podemos comprobar fácilmente el determinante cuando estamos tratando de convertir a la
inversa, tiene más sentido tratar de invertir la matriz y lanzar una excepción en el caso de
que detectemos un determinante cero. Esto lleva a un código estructurado de la siguiente
manera:
exception Determinante ; ( * Declarar excepcion Determinante * )
2 fun invertir ( aMatriz ) =
...
4 if ...
51
then raise Determinante ( * si el determinante de la matriz
es cero * )
6 else ...
end ;
8 invertir ( miMatriz ) handle Determinante ... ;
En este ejemplo, la función invertir genera una excepción si el determinante de
aMatrix resulta ser cero. Si la función levanta esta excepción como resultado de la llamada
invertir(myMatrix), entonces, debido a la declaración de handle asociada, la llamada
a invertir se aborta y se transfiere el control al código que sigue inmediatamente al
handle Determinante.
En este ejemplo, el mecanismo de excepción se utiliza para manejar una condición que
hace que sea imposible continuar el cálculo. En concreto, si el determinante es cero, la
matriz no tiene inversa, y es imposible para la función de inversión devolver la inversa de
la matriz.
Pregunta 8:
Si lo piensas bien, este ejemplo ilustra la necesidad de algún tipo de mecanismo
de alcance dinámico. Más especı́ficamente, supongamos que hay varias llamadas
para invertir en un programa que hace cálculos matriciales. Cada una de estas
llamadas podrı́an levantar la excepción Determinante. Cuando una llamada
levanta esta excepción, donde nos gustarı́a manejar la excepción?
6.6.1. Ejercicios
6.1. En las siguientes funciones en ML:
exception Excpt of int ;
2 fun twice (f , x ) = f ( f ( x ) ) handle Excpt ( x ) = > x ;
fun pred ( x ) = if x = 0 then raise Excpt ( x ) else x -1;
4 fun dumb ( x ) = raise Excpt ( x ) ;
fun smart ( x ) = 1 + pred ( x ) handle Excpt ( x ) = > 1;
52
Cuál es el resultado de evaluar cada una de las siguientes expresiones?
a) twice(pred,1)
b) twice(dumb,1)
c) twice(smart,1)
6.2. Java tiene una construcción lingüı́stica llamada “try... catch... finally...” cu-
ya semántica consiste en que el bloque de código bajo el alcance de finally se ejecuta
siempre, tanto si se lanza una excepción bajo el alcance de try como si no se lanzó.
Si se lanza una excepción, la excepción funciona exactamente igual que si no existie-
ra el bloque “finally” (aunque el bloque finally se ejecuta en cualquier caso), si
se puede capturar mediante el catch se captura, y si no sigue buscando hasta que
encuentra un catch que la pueda capturar. Sabiendo esto, explique qué se imprime
si ejecutamos los siguientes tres programas y por qué.
1 class Ejemplo1 {
public static void main ( String args []) {
3 try {
System . out . println ( " primera sentencia del bloque
try " ) ;
5 int num =45/0;
System . out . println ( num ) ;
7 }
catch ( ArrayIndexOutOfBoundsException e ) {
9
class Ejemplo2 {
2 public static void main ( String args []) {
try {
4 System . out . println ( " primera sentencia del bloque
try " ) ;
int num =45/0;
6 System . out . println ( num ) ;
53
}
8 catch ( ArithmeticException e ) {
System . out . println ( " ArithmeticException " ) ;
10 }
finally {
12 System . out . println ( " bloque finally " ) ;
}
14 System . out . println ( " fuera del bloque
try - catch - finally " ) ;
}
16 }
class Ejemplo3 {
2 public static void main ( String args []) {
try {
4 System . out . println ( " primera sentencia del bloque
try " ) ;
int num =45/3;
6 System . out . println ( num ) ;
}
8 catch ( ArrayIndexO utOfBoundsException e ) {
6.3. En este fragmento de código se captura una excepción y se vuelve a lanzar la mis-
ma excepción. Explique qué sucede en este fragmento de código, ayudándose de un
diagrama de una secuencia de estados de la pila de ejecución, mostrando cómo se
apilan y desapilan los diferentes activation records a medida que se va ejecutando
el programa. Para mayor claridad, puede acompañarlo de una descripción verbal.
Describa también verbalmente cuál serı́a el objetivo de este programa. ¿Qué sentido
tiene capturar una excepción para volver a lanzarla? ¿Es eso lo único que se hace?
ITransaccion transaccion = null ;
2 try
{
4 transaccion = sesion . EmpiezaTransaccion () ;
54
// hacer algo
6 transaccion . Commit () ;
}
8 catch
{
10 if ( transaccion != null ) {
transaccion . RestaurarEstadoPrevio () ; }
throw ;
12 }
6.4. En el siguiente código java, inserte en el main los mecanismos de manejo de excep-
ciones necesarios para capturar la excepción de forma adecuada.
public class RepartirComensales
2 {
public static void main ( String [] args )
4 {
Mesa mesa = new Mesa (1) ;
6 System . out . println ( " vamos a llenar la mesa 1 ... " ) ;
mesa . aniadirComensal ( " Ana " ) ;
8 mesa . aniadirComensal ( " Juan " ) ;
mesa . aniadirComensal ( " Maria " ) ;
10 mesa . aniadirComensal ( " Pedro " ) ;
mesa . aniadirComensal ( " Juana " ) ;
12 mesa . aniadirComensal ( " Esteban " ) ;
mesa . aniadirComensal ( " Lola " ) ;
14 }
}
16
55
30 }
else
32 {
numeroDeComensales += 1;
34 }
}
36 }
6.5. Escriba el siguiente programa con excepciones en lugar de tratar manejo de errores
mediante estructuras de control.
Nota: no es necesario que el programa compile, puede usar pseudocódigo siempre que su
semántica sea inambigua.
codigoErrorType readFile {
2 initialize codigoError = 0;
4 // abrir el archivo ;
if ( elArchivoEstaAbierto ) {
6 // determinar la longitud del archivo ;
if ( obtenemosLongitudArchivo ) {
8 // alojar esa cantidad de memoria ;
if ( tenemosSuficienteMemoria ) {
10 // leemos el archivo a memoria ;
if ( falloLectura ) {
12 codigoError = -1;
}
14 } else {
codigoError = -2;
16 }
} else {
18 codigoError = -3;
}
20 // cerrar el archivo ;
if ( elArchivoNoSeCerro && codigoError == 0) {
22 codigoError = -4;
} else {
24 codigoError = codigoError and -4;
}
26 } else {
codigoError = -5;
28 }
return codigoError ;
30 }
56
6.6. En el siguiente código java, inserte en el main los mecanismos de manejo de excep-
ciones necesarios para capturar la excepción de forma adecuada.
public class BankDemo
2 {
public static void main ( String [] args )
4 {
CuentaBancaria c = new CuentaBancaria (101) ;
6 System . out . println ( " Depositando $500 ... " ) ;
c . depositar (500.00) ;
8 System . out . println ( " \ nRetirando $100 ... " ) ;
c . retirar (100.00) ;
10 System . out . println ( " \ nRetirando $600 ... " ) ;
c . retirar (600.00) ;
12 }
}
57
25 public double getBalance ()
{
27 return balance ;
}
29 public int getNumber ()
{
31 return numero ;
}
33 }
6.7. La siguiente función de Python añade un elemento a una lista dentro de un diccionario
de listas. Modifı́quelo usando excepciones para manejar un posible error de llaves
(KeyError) si la lista con el nombre no existe todavı́a en el diccionario, en lugar
de comprobar por adelantado si ya existe. Incluya una cláusula finally (que se
ejecutará independientemente de si se lanza una excepción o no).
1 def aniadir_a_lista_en_diccionario ( diccionario ,
nombrelista , elemento ) :
if nombrelista in diccionario :
3 l = diccionario [ nombrelista ]
print ( " %s ya tiene %d elementos . " % ( nombrelista ,
len ( l ) ) )
5 else :
diccionario [ nombrelista ] = []
7 print ( " Creamos %s . " % nombrelista )
58
7 R=P
in
9 X =2
local A B C R = Q Z = foo ( A f1 : B C ) in
11 {R Z} % (B)
{ Browse Z } % (C)
13 end
{R Z} % (D)
15 Y =3
{ Browse Z } % (E)
17 end
6.2. En los puntos indicandos con <----, indique cuáles son las variables que pueden ser
recolectadas si el recolector de basura (garbage collector) se ejecuta.
1 fun { Mergesort Xs }
case Xs
3 of nil then nil
[] [ X ] then [ X ]
5 else Ys Zs in
{ Split Xs Ys Zs }
7 <----
{ Merge { Mergesort Ys }
9 { Mergesort Zs }}
end
11 end
13 Z = [1 2 3 4 5]
XS = { Mergesort 1 | 2 | 3 | Z }
15 { Browse Z }
<----
17 { Browse XS }
<----
6.3. Escriba en pseudocódigo un ejemplo de programa donde hay variables que se pueden
recoletar semánicamente pero no sintácticamente.
6.4. Escriba en pseudocódigo un ejemplo de programa donde hay variables que, aunque
por la estructura de bloques se podrı́an recolectar, no se pueden recolectar porque
tienen una referencia de otra variable. Luego, modifique el programa anterior inser-
tando una sentencia “dereference” que elimine todas las referencias a una variable.
Ahora, la variable quedará disponible para recolección de basura? Qué problema pue-
de acarrear el uso de una sentencia como dereference? Compare ese problema con
el problema de los memory leaks, y opine sobre la gravedad de cada uno de ellos.
59
6.5. En el siguiente código en Ruby, cuándo se puede recolectar la memoria asociada a
obj?
module Base
2 def click ( element_hash )
begin
4 obj =
@browser . element ( element_hash . keys [0] , element_hash . values [0])
obj . when_present . click
6 report_pass ( " Element successfully clicked " )
rescue = > exception
8 report_fail ( " Failed to click on object . #{ exception })
end
10 end
6.6. En el siguiente programa en Java, qué variables pueden ser recolectadas por el re-
colector de basura, y cuándo? En su explicación, use el concepto de alcanzabilidad.
NOTA: En Java, obj = null elimina exactamente una referencia a un objeto.
class A { int i = 5; }
2 class B { A a = new A () ; }
class C {
4 B b;
public static void main ( String args []) {
6 C c = new C () ;
c.b = new B () ;
8 c.b = null ;
}
6.8. El método finalize() en Java contiene código que se ejecuta cuando el recolector
de basura reclama a un objeto. La acción obj = null; elimina una referencia a un
objeto. Cuál de estas dos acciones contribuye de forma más efectiva a la liberación
de memoria del programa?
6.9. Comente el siguiente texto, explicando primero cuál es la función del garbage co-
llector en un lenguaje de programación, y describiendo los problemas que se pueden
encontrar con el uso de finalize().
60
Many GC systems have a notion of ”finalization..An object may be regis-
tered with the GC system so that when it is about to reclaim the object, it
runs a function on the object that can perform necessary cleanups. Finali-
zation is tricky. Some of the issues are:
When can an object actually be finalized? This is trickier than it first ap-
pears in the presence of some normally-desirable optimizing transforma-
tions.
In what thread, resource, or security context does a finalization function
run?
What happens when registered objects reference each other?
What happens if a finalization function makes an object not be garbage any
more?
1 MyClass myObj ;
3 try {
myObj = new MyClass () ;
5
// ...
7 } finally {
61
Capı́tulo 7
Orientación a objetos
7.1. Ejercicios
7.1. En las siguientes declaraciones de clase, indique qué partes son implementación y qué
partes son interfaz, marcando la interfaz.
public class URLExpSimple {
2
62
1 public class Stopwatch
{
3 private long startTime ;
private long stopTime ;
5
63
{ return 0 <= row && row < myTruth . length && 0 <= col
&& col < myTruth [0]. length ;
19 }
}
64
for ( int i = 2; i < result . length ; i ++)
38 result [ i ] = true ;
final double LIMIT = Math . sqrt ( max ) ;
40 for ( int i = 2; i <= LIMIT ; i ++) {
if ( result [ i ]) {
42 // cross out all multiples ;
int index = 2 * i ;
44 while ( index < result . length ) {
result [ index ] = false ;
46 index += i ;
}
48 }
}
50 return result ;
}
52
65
}
78 return isPrime ;
}
80
private :
14 int m_topSpeed ;
};
16
66
numberOfWheels )
{}
22 int NumberOfWheels () const {
return m_numberOfWheels ;
24 }
28 private :
int m_numberOfWheels ;
30 };
42 private :
int m_numberOfTracks ;
44 };
class DrawableObject
2 {
public :
4 virtual void Draw ( GraphicalDrawingBoard &) const = 0;
// draw to GraphicalDrawingBoard
};
6
67
12
DrawableList drawableList ;
28 GraphicalDrawingBoard drawingBoard ;
7.3. Identifique en el siguiente código en C++ un problema con la herencia del miembro
meow.
1 class Felino {
public :
3 void meow () = 0;
};
5
68
7 public :
void meow () { std :: cout << " miau \ n " ; }
9 };
7.4. A partir del siguiente código Ruby hemos tratado de escribir un código Java con
la misma semántica, pero los resultados no son iguales. Cuál es la diferencia y por
qué? Cómo deberı́amos modificar el programa en Java para que haga lo mismo que
el programa en Ruby?
1 #!/ usr / bin / ruby
3 class Being
5 @@count = 0
7 def initialize
@@count += 1
9 puts " creamos un ser "
end
11
def show_count
13 " Hay #{ @@count } seres "
end
15
end
17
def initialize
21 super
puts " creamos un humano "
23 end
end
69
25
def initialize
29 super
puts " creamos un animal "
31 end
end
33
def initialize
37 super
puts " creamos un perro "
39 end
end
41
Human . new
43 d = Dog . new
puts d . show_count
class Being {
2
public Being () {
6 count ++;
System . out . println ( " creamos un ser " ) ;
8 }
16 public Human () {
System . out . println ( " creamos un humano " ) ;
18 }
}
20 class Animal extends Being {
22 public Animal () {
70
System . out . println ( " creamos un animal " ) ;
24 }
}
26 class Dog extends Animal {
28 public Dog () {
System . out . println ( " creamos un perro " ) ;
30 }
}
32
7.5. A partir de la siguiente template en C++, escriba una clase de C++ con la misma
semántica pero especı́fica para int.
1 template < class A_Type > class calc
{
3 public :
A_Type multiply ( A_Type x , A_Type y ) ;
5 A_Type add ( A_Type x , A_Type y ) ;
};
7 template < class A_Type > A_Type
calc < A_Type >:: multiply ( A_Type x , A_Type y )
{
9 return x * y ;
}
11 template < class A_Type > A_Type calc < A_Type >:: add ( A_Type
x , A_Type y )
{
13 return x + y ;
}
7.6. Los mixins son una construcción de Ruby que permite incorporar algunas de las
funcionalidades de la herencia múltiple, ya que Ruby es un lenguaje con herencia
simple. Con un mixin se pueden incluir en una clase miembros de otra clase, con
71
la palabra clave include. Los name clashes, si los hay, se resuelven por el orden
de los include, de forma que la última clase añadida prevalece, y sus definiciones
son las que se imponen en el caso de conflicto. Teniendo esto en cuenta, describa el
comportamiento del siguiente pedazo de código.
module EmailReporter
2 def send_report
# Send an email
4 end
end
6
module PDFReporter
8 def send_report
# Write a PDF file
10 end
end
12
class Person
14 end
class Vehicle
22 end
7.7. Reescriba el siguiente código en Java en Haskell, de forma que su semántica denota-
cional sea idéntica.
1 import core . List ;
import static core . List . * ;
3
class P01 {
7
72
9 if ( list . isEmpty () ) throw new
NoSuchElementException ( " List is empty " ) ;
List <T > elements = list . tail () ;
11 List <T > result = list ;
while ( elements . nonEmpty () ) {
13 result = elements ;
elements = elements . tail () ;
15 }
return result . head () ;
17 }
7.8. El siguiente ejemplo en C++ causa un error de compilación. Por qué? cómo puede
solucionarse?
1 class trabajador
{
3 public :
void hora_de_levantarse ( )
5 { ... . ... ... . }
};
7 class estudiante
{
9 void hora_de_levantarse ( )
{ ... . ... ... . }
11 };
class ayudante_alumno : public trabajador , public
estudiante
13 {
15 };
17 int main ()
{
19 ayudante_alumno obj ;
21 obj . hora_de_levantarse ( )
}
7.9. Explique qué tests realizarı́a para saber cuál es la visibilidad de un método protected
en Ruby.
7.10. Explique por qué Smalltalk es menos eficiente que C++ utilizando el concepto de
overhead. Puede ayudarse de los siguientes gráficos, en los que se muestra el proceso
de lookup de información asociada a los objetos en uno y otro lenguaje.
73
7.11. En el siguiente código el compilador está creando una instancia de una subclase anóni-
ma de la clase abstracta, y luego se está invocando al método de la clase abstracta.
Explique por qué es necesario que el compilador haga este proceso.
abstract class my {
2 public void mymethod () {
System . out . print ( " Abstract " ) ;
4 }
}
6
class poly {
8 public static void main ( String a []) {
my m = new my () {};
10 m . mymethod () ;
}
12 }
7.12. En el siguiente programa en C++ indique si hay name clashes. Si los hay, iden-
tifı́quelos y mencione alguna polı́tica para solucionarlos. Si no los hay, introdúzcalos
y añada los mecanismos para solucionarlos (no es necesario que sea el mecanismo que
efectivamente implementa C++, puede ser otra polı́tica). ¿En qué contexto se dan
los name clashes?
class Persona
2 {
private :
74
4 std :: string m_strNombre ;
int m_nEdad ;
6 bool m_bEsVaron ;
8 public :
Persona ( std :: string strNombre , int nEdad , bool
bEsVaron )
10 : m_strNombre ( strNombre ) , m_nEdad ( nEdad ) ,
m_bEsVaron ( bEsVaron )
{
12 }
class Empleado
20 {
private :
22 std :: string m_strEmpleador ;
double m_dSalario ;
24
public :
26 Empleado ( std :: string strEmpleador , double dSalario )
: m_strEmpleador ( strEmpleador ) ,
m_dSalario ( dSalario )
28 {
}
30
40 public :
Profesor ( std :: string strNombre , int nEdad , bool
bEsVaron ,
42 std :: string strEmpleador , double dSalario , int
75
nDictaGrado )
: Persona ( strNombre , nEdad , bEsVaron ) ,
44 Empleado ( strEmpleador , dSalario ) ,
m_nDictaGrado ( nDictaGrado )
46 {
}
48 };
7.13. Indique si el siguiente programa en C++ se podrı́a escribir en Java con una semántica
equivalente. Si no se puede, indique por qué. Si se puede, indique con qué recursos
del lenguaje se puede.
class Persona
2 {
private :
4 std :: string m_strNombre ;
int m_nEdad ;
6 bool m_bEsVaron ;
8 public :
Persona ( std :: string strNombre , int nEdad , bool
bEsVaron )
10 : m_strNombre ( strNombre ) , m_nEdad ( nEdad ) ,
m_bEsVaron ( bEsVaron )
{
12 }
class Empleado
20 {
private :
22 std :: string m_strEmpleador ;
double m_dSalario ;
24
public :
26 Empleado ( std :: string strEmpleador , double dSalario )
: m_strEmpleador ( strEmpleador ) ,
m_dSalario ( dSalario )
28 {
76
}
30
40 public :
Profesor ( std :: string strNombre , int nEdad , bool
bEsVaron ,
42 std :: string strEmpleador , double dSalario , int
nDictaGrado )
: Persona ( strNombre , nEdad , bEsVaron ) ,
44 Empleado ( strEmpleador , dSalario ) ,
m_nDictaGrado ( nDictaGrado )
46 {
}
48 };
7.14. En Java existen dos formas de crear un nuevo thread explı́citamente: creando una
subclase de la clase Thread o bien implementando la interfaz Runnable. En ambos
casos el nuevo thread debe implementar el método run, que contendrá el código
ejecutado por el thread. Cuál de estas dos opciones crea un objeto más versátil y por
qué?
7.15. Estos dos programas, en dos lenguajes distintos, tienen la misma semántica. ¿Cuáles
son las diferencias entre estos dos lenguajes? Refiérase a los elementos de los progra-
mas que ejemplifican las diferencias entre estos dos lenguajes. Cuando sea relevante,
argumente las ventajas y desventajas de la forma de expresión de cada lenguaje.
interface Iterable <T >
2 def each (& block : T - >)
end
4
def sum ( values : Iterable <T >) where T : Addable <T >
10 count = 0
77
values . each do | value |
12 count += value
end
14 count
end
7.16. El lenguaje de programación Scala implementa como parte del lenguaje una heurı́sti-
ca llamada “linearización”, que permite determinar qué implementación se usará en
un objeto que hereda una componente con el mismo nombre de diferentes clases. Ex-
plique por qué es necesario que el lenguaje implemente esta heurı́stica (qué problema
resuelve) y dé un ejemplo donde se muestre una configuración donde entrarı́a en juego
esta heurı́stica para evitar el problema, por ejemplo, la configuración conocida como
el problema diamante.
7.17. Java implementa un tipo de pasaje de parámetros que consiste en pasar por valor
la referencia a un objeto. Mediante este tipo de pasaje de parámetros, ¿Se puede
implementar la función swap para que intercambie el objeto A por el objeto B? ¿Se
puede implementar la función swap para intercambiar algún elemento, algún pedazo
de software? ¿Cuál?
class Persona {
7 def explicar { println ( " humano " ) }
// mas metodos
9 }
78
class Actor extends Cantante with Performer
dog . remember () ;
12 dog . protectOwner () ;
Learn dl = dog ;
14 dl . learn () ;
16 cat . remember () ;
cat . protectOwner () ;
18
Climb c = cat ;
20 c . climb () ;
Climb cm = man ;
26 cm . climb () ;
79
Think t = man ;
28 t . think () ;
Learn l = man ;
30 l . learn () ;
Apply a = man ;
32 a . apply () ;
34 }
}
36
80
will protect owner " ) ;
}
66 public void learn () {
interface Learn {
92 public void learn () ;
}
94 interface Apply {
public void apply () ;
96 }
81
String name ;
100 int age ;
7.21. En Go existen interfaces con caracterı́sticas semejantes a las de Java, pero con una
diferencia:
With how Go interfaces work, you don’t need to declare an interface im-
plementation. If you implement the proper methods, you implement the
interface.
Relacione esta propiedad con las polı́ticas de subtipado de diferentes lenguajes orien-
tados a objetos, que pueden estar implementadas en herencia o en inclusión entre
interfaces.
7.22. omente el siguiente texto en el que se compara subtipado e interfaces, y explique por
qué un la herencia y el subtipado son distintos y qué ventajas o desventajas puede
tener separarlos o usarlos juntos.
82
Inheritance is about gaining attributes (and/or functionality) of super ty-
pes. For example:
class Base {
2 // interface with included definitions
4 }
83
Capı́tulo 8
Frameworks de programación
84
con este diseño invierte el control en comparación a la programación procedural tradicional.
En la programación tradicional, el código que escribe el programador llama a librerı́as que
se encargan de tareas genéricas. En cambio, con la inversión de control es el framework el
que llama al código escrito por el programador.
La inversión de control se usa para aumentar la modularidad del programa y hacerlo
extensible. Este término está relacionado al principo de inversión de dependencia, que se
encarga de desacoplar dependencias entre niveles altos y niveles bajos del código a través
de abstracciones compartidas.
El concepto general también se relaciona con la programación dirigida por eventos
(event-driven programming), ya que ésta se implementa normalmente usando inversión
de control. Efectivamente, en la programación dirigida por eventos el código escrito por el
programador sólo se encarga de tratar con los eventos, mientras que el framework se encarga
de manejar la ocurrencia de los eventos, el envı́o de eventos o mensajes o el entorno de
ejecución en general.
En pocas palabras, la inversión de control convierte un código escrito secuencialmente en
una estructura de delegación. En lugar de que tu programa controle todo explı́citamente, el
programa configura una clase o librerı́a con algunas funciones especı́ficas para tu aplicación,
que se llamarán cuando sucedan algunos eventos especı́ficos de tu aplicación.
Esta aproximación reduce la duplicación de código. Por ejemplo, con programación
tradicional un programador tenı́a que escribir el código para manejar la ocurrencia de
eventos, o buscar las librerı́as de sistema para nuevos eventos. Hoy en dı́a, la mayor parte de
APIs modernas permiten que el programador simplemente declare qué eventos le interesan
y qué librerı́as necesita, y la API te notifica cuándo suceden los eventos.
La inversión de control se ha simplificado en muchos lenguajes a través del concepto de
delegados, interfaces o incluso punteros de función crudos.
Como todos los paradigmas, la inversión de control no es adecuada para todos los
problemas. Puede resultar difı́cil seguir el flujo de un programa escrito en inversión de
control. Sin embargo, es una forma útil de diseñar métodos cuando se escribe una librerı́a
que se va a reusar.
8.1.1. Desacoplamiento
Sin desacoplamiento:
public class ServerFacade {
2 public <K , V > V respondToRequest ( K request ) {
if ( businessLayer . validateRequest ( request ) ) {
4 DAO . getData ( request ) ;
return Aspect . convertData ( request ) ;
6 }
return null ;
8 }
}
85
Este esquema básico en Java da un ejemplo de código con inversión de control. Pero
vemos que en ServerFacade se hacen muchas asunciones sobre cómo son los datos que
devuelve el objeto de acceso a los datos (DAO). Estas asunciones son probablmente ciertas
en el momento de creación del código, pero en la evolución del código ServerFacade y DAO
pueden cambiar de forma independiente, y perder ese acoplamiento.
Si se usa inversión de control de forma total, el control lo tiene totalmente el objeto
DAO, y el código se convierte en:
1 public class ServerFacade {
public <K , V > V respondToRequest ( K request , DAO dao ) {
3 return dao . getData ( request ) ;
}
5 }
8.1.2. Boilerplate
Boilerplate es un término que se usa para nombrar porciones del código que se usan en
muchos lugares con poca o ninguna alteración. Se usa más frecuentemente para referirse a
lenguajes que se consideran verbosos.
En programas orientados a objetos, en general hay clases que ya vienen con métodos
para obtener y configurar variables de instancia. Las definiciones de estos métodos en
generalse consideran boilerplate. Aunque el código puede variar de una clase a otra, es
lo suficientemente predecible en su estructura como para ser generado automáticamente,
mejor que escrito a mano. Por ejemplo, en la siguiente clase Java que representa a una
mascota, casi todo el código es boilerplate excepto por las declaraciones de Mascota, nombre
y propietario.
1 public class Pet {
private PetName name ;
3 private Person owner ;
86
}
17
8.2. Ejercicios
8.1. Argumente cuál de los dos fragmentos de código que siguen es un ejemplo de inversión
de control.
1 class PaymentsController < ApplicationController
def accept_payment
3 if Rails . env . development ? || Rails . env . test ?
@credit_card_validator = BogusCardValidator . new
5 else
@credit_card_validator = RealCardValidator . new
7 end
if Rails . env . production ?
9 @gateway = RealPaymentGateway . new
elsif Rails . env . staging ?
11 @gateway = RealPaymentGateway . new ( use_testing_url :
true )
else
13 @gateway = BogusPaymentGateway . new
end
15 card = @credit_card_validator . validate ( params [: card ])
@gateway . process ( card )
17 end
end
87
8 # config / dependencies / production . rb :
RailsENV :: Dependencies . define do
10 prototype : payment_gateway , RealPaymentGateway
prototype : credit_card_validator , RealCardValidator
12 controller PaymentsController , {
gateway : ref (: payment_gateway )
14 credit_card_validator : ref (: credit_card_validator )
}
16 end
8.2. En el código que sigue, explique por qué este es un ejemplo de boilerplate y desarrolle
(implemente) cómo el programador podrı́a usarlo para instanciar un caso particular.
1 <! DOCTYPE html >
< html >
3 < head >
< title > </ title >
5 </ head >
< body > </ body >
7 </ html >
88
Capı́tulo 9
Paradigma funcional
Mitchell 4.4.
Van Roy y Haridi. 2004. Concepts, Techniques, and Models of Computer Programming.
MIT Press. Capı́tulo 3: introducción
89
sólo la segunda lı́nea es una operación imperativa, las otras lı́neas contienen declaraciones
de nuevas variables.
Un punto sutil es que la última lı́nea en el código anterior declara una nueva variable con
el mismo nombre que el de una variable declarada anteriormente. La forma más sencilla de
entender la diferencia entre declarar una nueva variable y cambiar el valor de una variable
ya existente es cambiando el nombre de la variable. Las variables ligadas, que no son libres
en una expresión (que están definidas dentro del alcance de la expresión) pueden cambiar
de nombre sin cambiar el significado de la expresión. En particular, podemos cambiar
el nombre de las variables ligadas en el fragmento de programa anterior de la siguiente
manera:
1 { int x = 1;
x = x +1;
3 { int y = x +1;
{ int z = y +1;
5 }}}
(Si hubiera más ocurrencias de x dentro del bloque interior, también les cambiarı́amos el
nombre a z.) Después de volver a escribir el programa a esta forma equivalente, podemos
ver fácilmente que la declaración de una nueva variable z no cambia el valor de cualquier
variable ya existente.
La asignación imperativa puede introducir efectos secundarios porque puede destruir
el valor anterior de una variable, sustituyéndolo por uno nuevo, de forma que éste no esté
disponible más adelante. En programación funcional la asignación imperativa se conoce
como asignación o actualización destructiva.
Decimos que una operación computacional es declarativa si, cada vez que se invoca con
los mismos argumentos, devuelve los mismos resultados independientemente de cualquier
otro estado de computación. Una operación declarativa es:
independiente no depende de ningún estado de la ejecución por fuera de sı́ misma,
sin estado no tiene estados de ejecución internos que sean recordados entre invocaciones,
y
determinı́stica siempre produce los mismos resultados para los mismos argumentos y,
por extensión, ejecutar la operación no tendrá efectos secundarios.
Una consecuencia muy valiosa de estas propiedades es la conocida como transparencia
referencial. Una expresión es transparente referencialmente si se puede sustituir por su valor
sin cambiar la semántica del programa. Esto hace que todas las componentes declarativas,
incluso las más complejas, se puedan usar como valores en un programa, por ejemplo, como
argumentos de función, como resultados de función o como partes de estructuras de datos.
Esto aporta una gran flexibilidad a los lenguajes funcionales, ya que se pueden tratar de
forma homogénea expresiones de estructura muy variable, como por ejemplo en el siguiente
programa:
90
1 HayAlgunExceso xs n = foldr ( filter ( > n ) ( map
convertirSistemaMetrico xs ) ) False xs
Veamos un ejemplo de dos funciones, una referencialmente transparente y otra referen-
cialmente opaca:
1 globalValue = 0;
91
En algunos casos se usa el término “lenguaje funcional” para referirse a lenguajes
que no tienen expresiones con efectos secundarios o cualquier otra forma de construcción
imperativa. Para evitar ambigüedades entre estos y lenguajes como Lisp o ML, llamaremos
a estos últimos “lenguajes funcionales puros”. Los lenguajes funcionales puros pueden pasar
el siguiente test:
Dentro del alcance de las declaraciones x1 , ..., xn , todas las ocurrencias de una
expresión e que contenga sólo las variables x1 , ..., xn tendrán el mismo valor.
Como consecuencia de esta propiedad, los lenguajes funcionales puros tienen una propiedad
muy útil: si la expresión e ocurre en varios lugares dentro de un alcance especı́fico, entonces
la expresión sólo necesita evaluarse una vez. Esto permite que el compilador pueda hacer
optimizaciones de cálculo y de espacio en memoria (aunque no necesariamente todos los
compiladores de todos los lenguajes funcionales lo van a hacer).
Otra propiedad interesante de los programas declarativos es que son composicionales.
Un programa declarativo consiste de componentes que pueden ser escritos, comprobados,
y probados correctos independientemente de otros componentes y de su propia historia
pasada (invocaciones previas).
Otra propiedad interesante es que razonar sobre programas declarativos es sencillo. Es
más fácil razonar sobre programas escritos en el modelo declarativo que sobre programas
escritos en modelos más expresivos. Como los programas declarativos sólo pueden calcular
valores, se pueden usar técnicas sencillas de razonamiento algebraico y lógico.
En un programa declarativo, la interacción entre componentes se determina únicamente
por las entradas y salidas de cada componente. Considere un programa con un componente
declarativo. Este componente puede ser mirado en sı́ mismo, sin tener que entender el resto
del programa. El esfuerzo que se necesita para entender el programa completo es la suma
de los esfuerzos que se necesitan para entender el componente declarativo y para entender
el resto.
Si existiera una interacción más estrecha entre el componente y el resto del programa
no se podrı́an entender independientemente. Tendrı́an que entenderse juntos, y el esfuerzo
requerido serı́a mucho más grande. Por ejemplo, podrı́a ser (aproximadamente) proporcio-
nal al producto de los esfuerzos requeridos para entender cada parte. En un programa con
muchos componentes que interactuán estrechamente, esto explota muy rápido, difcultan-
do o haciendo imposible entenderlo. Un ejemplo de interacción estrecha es un programa
concurrente con estado compartido.
Pero para algunos problemas, es necesario trabajar con interacciones estrechas. Sin
embargo, podemos adoptar por principio la directiva de tratar de hacer código lo más
modular posible, con interacciones estrechas sólo cuando sea necesario y no en cualquier
caso. Para soportar este principio, el total de componentes declarativos deberı́a ser el mayor
número posible.
Estas dos propiedades son importantes tanto para programar en grande como en pe-
queño. Serı́a muy agradable si todos los programas pudieran escribirse en el modelo de-
clarativo. Desafortunadamente, este no es el caso. El modelo declarativo encaja bien con
ciertas clases de programas y mal con otras.
92
9.3. Problemas naturalmente no declarativos
La forma más elegante y natural (compacta e intuitiva) de representar algunos proble-
mas es mediante estado explı́cito. Es el caso, por ejemplo, de las aplicaciones cliente-servidor
y de las aplicaciones que muestran video. En general, todo programa que realice algún tipo
de Input-Output lo hará de forma más natural mediante estado explı́cito. También es el
caso en el que estamos tratando de modelar algún tipo de comportamiento (por ejemplo,
agentes inteligentes, juegos) en el que representamos a una componente de software como
una entidad que tiene memoria, porque inherentemente la memoria cambia a lo largo del
tiempo.
Pregunta 1:
Piense algunos ejemplos de problemas en los que el estado deberı́a estar repre-
sentado en la solución de software de forma explı́cita.
También hay tipos de problemas que se pueden programar de forma mucho más eficiente
si se hace de forma imperativa. En esos casos podemos llegar a convertir un problema
que es intratable con programación declarativa en un problema tratable. Este es el
caso de un programa que realiza modificaciones incrementales de estructuras de datos
grandes, e.g., una simulación que modifica grafos grandes, que en general no puede ser
compilado eficientemente. Sin embargo, si el estado está guardado en un acumulador y el
programa nunca requiere acceder a un estado ya pasado, entonces el acumulador se puede
implementar con asignación destructiva (ver Van Roy y Haridi 6.8.4.).
Por esta razón muchos lenguajes funcionales proveen algún tipo de construcción lingüı́sti-
ca para poder expresar instrucciones imperativas. Incluso en lenguajes funcionales puros
existen estas construcciones, que en general se conocen como mónadas. Las mónadas son
una construcción de un lenguaje que permite crear un alcance aislado del resto del pro-
grama. Dentro de ese alcance, se permiten ciertas operaciones con efectos secundarios, por
ejemplo, el uso de variables globales (globales al alcance) o asignación destructiva. Está ga-
rantizado que estos efectos secundarios no van a afectar a la parte del programa que queda
fuera del alcance de la mónada. Las mónadas han sido descritas como un “punto y coma
programable”, que transportan datos entre unidades funcionales que los van transformando
un paso a la vez.
Sabiendo todo esto, ¿podemos considerar que la programación declarativa es eficien-
te? Existe una distancia muy grande entre el modelo declarativo y la arquitectura de una
computadora. La computadora está optimizada para modificar datos en su lugar, mientras
que el modelo declarativo nunca modifica los datos sino que siempre crea datos nuevos.
Pero sin embargo esto no es un problema tan grave como podrı́a parecer, porque el com-
pilador puede convertir partes importantes de los programas funcionales en instrucciones
imperativas.
Pregunta 2:
Piense dos ejemplos de operación declarativa y dos ejemplos de operación no
declarativa
93
Pregunta 3:
Piense por lo menos una operación que no se pueda llevar a cabo sin estado
9.5. Ejercicios
9.1. Cuál de estas dos funciones, rq o rt, es transparente referencialmente?
1 globalValue = 0;
94
9.2. El siguiente pedazo de código es declarativo? Por qué?
{ int x = 1;
2 x = x +1;
{ int y = x +1;
4 { int x = y + z ;
}}}
9.3. Si una componente de software usa una variable global en una guarda (como parte
de una condición con “if”), entonces no es independiente de contexto. Si, en cambio,
una componente de software modifica una variable global pero ésta no afecta a su
ejecución, conserva la transparencia referencial pero puede tener efectos secundarios.
Escriba dos programas en pseudocódigo, uno en el que se de el primer fenómeno
(una variable global que hace que un programa no sea independiente de contexto, es
decir, que los resultados de su ejecución no dependan solamente de sus parámetros de
entrada), y otro en el que se de el segundo fenómeno (un programa que tiene efectos
secundarios a través de una variable global).
9.4. De la misma forma que las variables globales, el pasaje de variables por referencia
puede producir efectos secundarios. Sin embargo, en componentes de software decla-
rativas no hay efectos secundarios, y no hay diferencia entre pasaje de parámetros
por valor o por referencia. Explique cómo se puede dar este fenómeno, y cuál es la
restricción lingüı́stica que imponen los lenguajes que se inscriben en el paradigma
funcional para forzar esta propiedad en los programas que se escriben en esos len-
guajes, es decir, para forzar que el pasaje de parámetros no pueda ser una vı́a para
propagar efectos secundarios.
9.5. En los siguientes programas imperativos, identifique porciones de código que no sean
declarativas.
1 int A ;
int B ;
3
int Add ()
5 {
return A + B ;
7 }
9 int main ()
{
11 int answer ;
A = 5;
95
13 B = 7;
answer = Add () ;
15 printf ( " %d \ n " , answer ) ;
return 0;
17 }
1 int glob = 0;
9 int main ()
{
11 int res ;
glob = 0;
13
17 return res ;
}
9.6. Reescriba el siguiente código en Java en un lenguaje declarativo, por ejemplo Haskell
o pseudocódigo declarativo, de forma que su semántica denotacional sea idéntica.
class P01 {
2
96
9.7. En el siguiente programa, identifique las porciones del programa que no son funcio-
nales, y mencione por qué no lo son. Escriba en pseudocódigo un programa funcional
con la misma semántica que este programa.
Module Modulo1
2 Dim texto As String = " textoUno "
8 Sub Main ()
ConcatenaConGuion ( " textoDos " )
10 End Sub
End Module
9.8. Estos dos programas están escritos en Perl. ¿Cuál de los dos es funcional? Identifique
en el programa no funcional las partes del código que no son funcionales y explique
cómo se expresa la misma semántica en la versión funcional para que sea funcional.
1 my @numbers = qw (1 3 5 6 7 8) ;
3 my $sum = 0;
for my $num ( @numbers ) { $sum += $num ;}
5
say $sum ;
my @numbers = qw (1 3 5 6 7 8) ;
2
sub sum {
14 return reduce { $_ [0] + $_ [1] } @_ ;
}
16
97
say sum @numbers ; $
9.9. Escriba un programa (puede ser en pseudocódigo) en el que una variable sea el
medio por el cual se propagan efectos secundarios más allá del alcance de una función.
Relacione este fenómeno con los paradigmas funcional e imperativo, incluyendo en su
explicación los conceptos de determinismo en las componentes de software. Explique
como este comportamiento que puede originar tantos comportamientos inesperados
puede llegar a utilizarse de forma ventajosa en un esquema productor-consumidor.
9.11. En un programa imperativo cualquiera, identifique posibles canales por los que even-
tualmente propagarse efectos secundarios.
98
Capı́tulo 10
Programación lógica
Mitchell 15.
10.1. Ejercicios
10.1. Por qué el cut (!) no pertenece al paradigma declarativo?
10.2. Cuáles de los siguientes pares de términos unifican? En el caso de que unifiquen,
indique cómo se instancian las variables.
a) pan = pan
b) ’Pan’ = pan
c) ’pan’ = pan
d) Pan = pan
e) pan = salchicha
f) comida(pan) = pan
g) comida(pan) = X
h) comida(X) = comida(pan)
i) comida(pan,X) = comida(Y,salchicha)
j) comida(pan,X,cerveza) = comida(Y,salchicha,X)
k) comida(pan,X,cerveza) = comida(Y,hamburguesa)
l) comida(X) = X
m) meal(comida(pan),bebida(cerveza)) = meal(X,Y)
n) meal(comida(pan),X) = meal(X,bebida(cerveza))
99
10.3. A partir de la siguiente base de datos:
elfo_domestico(dobby).
bruja(hermione).
bruja(’McGonagall’).
bruja(rita_skeeter).
puede_hacer_magia(X):- elfo_domestico(X).
puede_hacer_magia(X):- hechicero(X).
puede_hacer_magia(X):- bruja(X).
?- puede_hacer_magia(elfo_domestico).
?- hechicero(harry).
?- puede_hacer_magia(hechicero).
?- puede_hacer_magia(’McGonagall’).
?- puede_hacer_magia(Hermione).
Escriba un pseudocódigo en imperativo (usando if ... then ... else ...) o fun-
cional (usando guardas) con la misma semántica.
10.5. Defina en Prolog el problema de recomendar “amigos” en una red social, especificado
como sigue: para un usuario P con un conjunto de amigos CA = [A1 , A2 , ...An ],
los cuales a su vez tienen cada uno asociado un conjunto de amigos C1 , C2 ... Cn
le recomendaremos a P todos los miembros de la unión de todos los conjuntos de
amigos de sus propios amigos CCA = C1 ∪ C2 ... ∪ Cn que NO están en la lista de sus
propios amigos.
Pueden usar la siguiente base de conocimiento.
amigo(pedro,marı́a). amigo(pedro,juan).
amigo(marı́a,pedro). amigo(marı́a,clara).
amigo(marı́a,romina). amigo(juan,pedro).
amigo(juan,clara). amigo(clara,marı́a).
amigo(clara,juan). amigo(romina,marı́a).
100
10.6. Defina en Prolog un sistema de ayuda a una central de turnos. Este sistema dirá
si se le puede asignar un turno a un paciente con un determinado médico para un
determinado dı́a, siguiendo las siguientes premisas:
el paciente no tiene ningún turno asignado para el mismo dı́a a la misma hora.
el paciente no tiene ningún turno asignado con ningún especialista de la misma
especialidad para la que pide turno.
el médico para el que pide turno no tiene turno asignado con ningún otro paciente
para el mismo dı́a a la misma hora.
turno(celia,rivas,(6,30,8)).
turno(celia,zilvetti,(7,14,11)).
turno(tomás,rivas,(7,11,10)).
turno(tomás,pérez,(8,11,10)).
turno(tomás,schuster,(9,11,10)).
turno(lidia,zilvetti,(7,14,10)).
turno(lidia,schuster,(9,11,11)).
turno(esteban,rivas,(7,1,9)).
especialidad(rivas,oftalmologia).
especialidad(smith,oftalmologia).
especialidad(zilvetti,ginecologı́a).
especialidad(román,ginecologı́a).
especialidad(pérez,endocrinologı́a).
especialidad(schuster,clı́nico).
101
Capı́tulo 11
Programación concurrente
Mitchell 14.
11.2. Ejercicios
11.1. En el siguiente ejemplo se está usando la variable común lock como lock. Describa
una ejecución del código que lleve a un estado inconsistente, describiendo en orden
las asignaciones y evaluaciones que ocurren al ejecutar el código. Explique cómo se
podrı́a evitar este funcionamiento no deseado con implementaciones atómicas de wait
y signal.
1 lock := 0;
cobegin
3 begin
while lock =1 do end ;
5 lock :=1;
sign up ( fred ) ;
7 lock :=0;
end ;
9 begin
102
while lock =1 do end ; // loop que no hace nada hasta
que el lock sea 0
11 lock :=1; // setear el lock para entrar
a la seccion critica
sign up ( bill ) ; // seccion critica
13 lock := 0; // soltar el lock
end ;
15 end ;
11.2. En java existen dos formas de crear un nuevo thread explı́citamente: creando una
subclase de la clase Thread o bien implementando la interfaz Runnable. En ambos
casos el nuevo thread debe implementar el método run, que contendrá el código
ejecutado por el thread. Cuál de estas dos opciones crea un objeto más versátil y por
qué?
11.4. Explique por qué el esquema productor - consumidor no tiene condiciones de carrera.
103
1 lista_dobles = map (\ x -> 2 * x ) lista_todos_los_naturales
11.6. Hay ocho combinaciones posibles en el pasaje de mensajes entre procesos concu-
rrentes, combinando sincronicidad / asincronicidad, ordenado / no ordenado y con
buffer / sin buffer. Explique cuáles tienen ventajas y cuáles no tienen sentido o tienen
desventajas.
11.7. Los actores se comunican mediante mensajes desordenados. Explique qué información
y métodos deberı́a añadir a un modelo basado en actores para poder procesar los
mensajes recibidos en el orden en el que fueron enviados.
11.8. Escriba en pseudocódigo dos versiones secuenciales (no concurrentes) del siguiente
programa, una en paradigma funcional y otra en paradigma imperativo. Argumente
las ventajas y desventajas de las tres versiones del programa usando los conceptos de
velocidad, overhead y determinismo.
1 class Ejemplo extends RecursiveTask < Integer > {
final int n ;
3 Ejemplo ( int n ) { this . n = n ; }
Integer compute () {
5 if ( n <= 1)
return n ;
7 Ejemplo f1 = new Ejemplo ( n - 1) ;
f1 . fork () ;
9 Ejemplo f2 = new Ejemplo ( n - 2) ;
return f2 . compute () + f1 . join () ;
11 }
}
11.9. En el código del ejercicio anterior, identifique las expresiones que expresan semántica
exclusiva de un programa concurrente y describa su semántica informalmente.
11.10. En las siguientes interfaces y clases de Java, subraye las partes de la descripción que
describen semántica concurrente.
104
A Queue that additionally supports
operations that wait for the queue to
become non-empty when retrieving an
BlockingQueue<E>
element, and wait for space to become
available in the queue when storing an
element.
A Future represents the result of an
Future<E>
asynchronous computation.
An unbounded blocking queue that uses
the same ordering rules as class
PriorityBlockingQueue<E>
PriorityQueue and supplies blocking
retrieval operations.
A synchronization aid that allows a set
CyclicBarrier of threads to all wait for each other to
reach a common barrier point.
A blocking queue in which each insert
operation must wait for a corresponding
SynchronousQueue<E>
remove operation by another thread, and
vice versa.
11.11. En el siguiente código Java, subraye las partes del programa que hacen referencia a
semántica concurrente.
protected class ReadLock {
2 WriteLock lock ;
105
20
28 }
11.12. En Java se puede escribir sincronización de métodos, pero esta funcionalidad es una
forma más cómoda de escribir (el llamado azúcar sintáctico), porque en realidad el al-
cance de una sincronización es siempre el objeto entero. Sabiendo esto, en el siguiente
código, ¿pueden dos threads acceder los métodos addA() y addB() simultáneamente?
¿Por qué? ¿Podemos decir que las variables a y b son regiones crı́ticas o canales de
comunicación entre dos threads?
class X {
2
private int a ;
4 private int b ;
14 }
11.13. En Quake-C se usa un modelo de pasaje de mensajes. Acá mostramos algunos ti-
pos de mensaje, su especificación y algunos ejemplos. Para los mensajes de tipo
MSG BROADCAST, MSG ONE, MSG ALL, diga cuáles de las tres caracterı́sticas
del pasaje de mensajes del modelo de actores tiene sentido usar: sincronización, buf-
fer, orden.
MSG_BROADCAST = 0; unreliable message , sent to all
2 MSG_ONE = 1; reliable message , sent to msg_entity
MSG_ALL = 2; reliable message , sent to all
4 MSG_INIT = 3; write to the init string
106
// Use unreliable ( but fast ) messages , when it ’s of no
importance that a client misses the message .
6 // examples : sound , explosions , monster deaths , taunts ... .
// Use reliable messages when it ’s very important that
every client sees the message , or a game incoherency
might happen .
8 // examples : shots , player deaths , door moves , game ends
... and CD track changes !.
msg_entity = player
18 WriteByte ( MSG_ONE , SVC_SETVIEWANGLES ) ;
WriteAngle ( MSG_ONE , camera . angles_x ) ;
20 WriteAngle ( MSG_ONE , camera . angles_y ) ;
WriteAngle ( MSG_ONE , camera . angles_z ) ;
22
107
</ li >
12 )
14 Todo . propTypes = {
onClick : PropTypes . func . isRequired ,
16 completed : PropTypes . bool . isRequired ,
text : PropTypes . string . isRequired
18 }
11.15. El siguiente código está dentro del modelo de actores. Señale en el código los mecanis-
mos propios de actores y sı́rvase de ellos para explicar cómo los actores implementan
concurrencia declarativa.
import akka . actor . _
2
108
case PingMessage = >
30 println ( " pong " )
sender ! PongMessage
32 case StopMessage = >
println ( " pong stopped " )
34 context . stop ( self )
}
36 }
11.16. [11 pt.] En el siguiente lenguaje de programación, la palabra “local” se usa para
declarar variables en un alcance, y el operador “→” se usa para ligar la variable de
la derecha a la de la izquierda, de forma que el valor de la variable de la izquierda
será una referencia a la de la derecha. Todas las variables se representan en la pila
como punteros a una estructura de datos en el heap.
Diagrame los diferentes estados por los que pasa la pila de ejecución y muestre en qué
momento se puede recolectar cada variable. Señale también si hay casos de variables
que podrı́an ser recolectadas antes de que queden sintácticamente disponibles para
que el recolector de basura las recolecte.
{ local bli
2 local bla
{ local ble
4 local blu
local bla
6 bla <- ble
{ local blo
8 ble <- blo
}
10 bli = 3
}
12 }
109
Capı́tulo 12
Paradigma de scripting
capı́tulo 13. Michael Scott. Programming Language Pragmatics (2nd ed.). Morgan
Kaufmann. 2016.
Los lenguajes de programación tradicionales están diseñados principalmente para crear
aplicaciones autónomas: programas que aceptan algún tipo de entrada, la manipulan de al-
guna manera que se entiende bien, y generan la salida esperada. Pero la mayorı́a de los usos
reales de las computadoras requieren la coordinación de múltiples programas. Un sistema
de nóminas institucional, por ejemplo, debe procesar los datos de informes de los lectores
de tarjetas, formularios en papel escaneados, y la entrada manual (teclado); ejecutar miles
de consultas de bases de datos; cumplir cientos de normas jurı́dicas e institucionales; crear
un extenso rastro de papel”para el mantenimiento de registros, auditorı́a, y los propósitos
de preparación de impuestos; cheques de impresión; y comunicarse con los servidores de
todo el mundo para el depósito directo on-line, la retención de impuestos, la acumulación
de jubilación, seguro médico, y ası́ sucesivamente. Estas tareas suelen involucrar docenas
o cientos de programas ejecutables por separado. La coordinación entre estos programas
requiere pruebas y condicionales, bucles, variables y tipos, subrutinas y abstracciones: el
mismo tipo de herramientas lógicas que un lenguaje convencional proporciona dentro de
una aplicación.
En principio, cualquier lenguaje puede ser utilizado como un lenguaje de scripting,
siempre que cuente con las librerı́as o bindings para un entorno especı́fico. Sin embargo,
los lenguajes especı́ficamente de scripting están pensados para ser muy rápidos de apren-
der y escribir, ya sea como archivos ejecutables o de forma interactiva en un bucle de
lectura–evaluación–impresión (REPL). En general, esto implica una sintaxis y semánti-
ca relativamente simples; tı́picamente un ”script”se ejecuta de principio a fin, como un
”guión”, sin un punto de entrada explı́cito.
Por ejemplo, no es común utilizar Java como lenguaje de scripting debido a su sintaxis
verbosa y reglas sobre las clases que se encuentran en diferentes archivos, y directamente
es imposible ejecutar Java de forma interactiva, ya que los archivos fuente sólo pueden
tener las definiciones que deben ser invocados externamente por una aplicación lanzador
de aplicaciones. Por el contrario, en Python se pueden definir algunas funciones en un
solo archivo, evitar por completo las funciones y escribir en un estilo de programación
110
imperativo, o incluso utilizarlo de forma interactiva.
Un lenguaje de scripting suele ser interpretado a partir del código fuente o el bytecode.
Los lenguajes de scripting pueden estar diseñados para ser usados por usuarios finales
de una aplicación o sólo para uso interno de los desarrolladores. Los lenguajes de scripting
suelen utilizar abstracción para hacer transparente a los usuarios los detalles de los tipos
internos variables, almacenamiento de datos y la gestión de memoria.
Los scripts son a menudo creados o modificados por la persona que los ejecuta, pero a
menudo también se distribuyen, por ejemplo, buena parte de los juegos están escritos en
un lenguaje de scripting.
111
programas más pequeños que los que están mejor implementados en un lenguaje
compilado;
112
Estos lenguajes podrı́an, en principio, utilizarse para controlar cualquier aplicación de
interfaz gráfica de usuario; pero, en la práctica, su uso es limitado porque están muy ligados
a una aplicación especı́fica o un sistema operativo particular. Hay algunas excepciones a
esta limitación. Algunos lenguajes de scripting de GUI se basan en el reconocimiento de
objetos gráficos a través de sus pı́xeles de la pantalla de visualización. Estos lenguajes de
scripting de GUI no dependen del apoyo del sistema operativo o aplicación.
Muchos programas de aplicación grandes incluyen un pequeño lenguaje de programa-
ción adaptado a las necesidades del usuario de la aplicación. Del mismo modo, muchos
juegos de computadora utilizan un lenguaje de programación especı́fico para expresar las
acciones de personajes no jugadores y del entorno del juego. Los lenguajes de este tipo están
diseñados para una sola aplicación; y, si bien pueden parecerse superficialmente un lenguaje
especı́fico de propósito general (por ejemplo QuakeC se parece a C), tienen caracterı́sticas
especı́ficas que los distinguen, principalmente abstracciones que acortan la expresión de
los conceptos propios del juego. El Emacs Lisp, aunque un dialecto totalmente formada
y capaz de Lisp, contiene muchas caracterı́sticas especiales que hacen que sea más útil
para ampliar las funciones de edición de Emacs. Un lenguaje de programación especı́fico
de la aplicación puede ser visto como un lenguaje de programación especı́fico del dominio
especializado para una sola aplicación.
Varios lenguajes han sido diseñados para reemplazar lenguajes de scripting especı́ficos
una aplicación, como estándares para tareas de scripting. De esta forma, los programadores
de aplicaciones (que trabajan en C o en otro lenguaje de programación no de scripting)
incluye ”ganchosçon los que el lenguaje de scripting puede controlar la aplicación. Estos
lenguajes pueden ser técnicamente equivalentes a un lenguaje de extensión especı́fico de
la aplicación, pero cuando una aplicación incorpora un lenguaje çomún”, el usuario puede
programar con el mismo lenguaje para diferentes aplicaciones. Una alternativa más genéri-
ca es simplemente proporcionar una librerı́a que un lenguaje de propósito general puede
utilizar para controlar la aplicación, sin modificar el lenguaje para el dominio especı́fico.
JavaScript comenzó como un lenguaje de secuencias de comandos en el interior de los
navegadores web, y ésa sigue siendo su función principal. Sin embargo, la estandarización
del lenguaje como ECMAScript lo ha popularizado como lenguaje integrable de propósi-
to general. En particular, la aplicación SpiderMonkey Mozilla está incrustada en varios
ambientes.
TCL fue creado como un lenguaje de extensión, pero ahora se usa principalmente como
un lenguaje de propósito general, parecido a Python, Perl o Ruby. Por otro lado, Rexx
fue creado originalmente como un lenguaje de control de trabajos, pero se usa como un
lenguaje de extensión, ası́ como un lenguaje de propósito general. Perl es un lenguaje de
propósito general, pero tenı́a en 1990 el dialecto Oraperl que consiste en un binario de Perl
4 con Oracle Call Interface compilado. Sin embargo, desde entonces se ha sustituido por
una biblioteca (Perl Module), DBD :: Oracle.
Otras aplicaciones complejas y orientadas a tareas pueden incorporar y un lenguaje de
programación para permitir a sus usuarios un mayor control y más funcionalidades. Por
ejemplo, las herramientas de autorı́a Autodesk Maya 3D incrustan el lenguaje de script
MEL, o Blender, que utiliza Python para desempeñar este papel.
113
12.2. Ejercicios
12.1. Si quiero un control fuerte de la seguridad de una aplicación, debo renunciar a usar
un lenguaje de scripting como por ejemplo JavaScript? Dé por lo menos un argumen-
to que muestre las vulnerabilidades de un lenguaje de scripting y por lo menos dos
argumentos o estrategias con las que podrı́a tratar de reducir los riesgos de un len-
guaje de scripting. Argumente qué ventajas aportan las caracterı́sticas del lenguaje
que implican vulnerabilidades.
// Quake only has three clipping hulls for moving entities, point-size,
// man-size, and shambler-size. Making the hull man-sized gives the
// fiend a BIG edge in tracking you down, (it’s a definite hack :)
DEMON.QC
(Change demon1_run6 and demon1_jump4)
void() demon1_jump1;
void() demon1_run6 =[ $run6, demon1_run1 ]
{
ai_run(36);
// if underwater, give a chance to switch to jump AI anyway
if (self.waterlevel == 1)
{
if ( random() > 0.8 )
self.think = demon1_jump1;
}
114
};
ai_face();
self.touch = Demon_JumpTouch;
makevectors (self.angles);
self.origin_z = self.origin_z + 1;
if (self.enemy.origin_z > self.origin_z + 40)
{
if ( infront(self.enemy) || self.waterlevel == 1 )
{
dir = normalize(self.enemy.origin - self.origin);
dist = vlen(self.enemy.origin - self.origin);
self.velocity = dir * dist;
}
else
self.velocity = v_forward * 150;
115
// attempt to jump wall or normal vel.
if (trace_fraction < 1)
{
self.velocity = v_forward * 110;
self.velocity_z = 250 * (random() + 1);
}
else
self.velocity_z = 250;
}
float() CheckDemonJump =
{
local vector dist;
local float d;
12.3. Ordene los siguientes fragmentos de código de más de scripting a menos de scripting,
y explique cuáles son los principios que han contribuı́do a su ordenamiento.
class HELLO_WORLD
2
creation
4 make
feature
6 make is
local
8 io : BASIC_IO
do
10 !! io
io . put_string ( " %N Hello World !!!! " )
12 end -- make
116
16 program hello_prog
18 root
HELLO_WORLD : " make "
20
22 cluster
24 " ./ "
26 end
30
end -- hello_prog
1 while 1 ,
disp ( ’ hello world ’)
3 end
12.4. Ordene los siguientes fragmentos de código de más de scripting a menos de scripting,
siempre justificando por qué.
1 # !/ bin / bash
for jpg ; do
3 png = " $ { jpg %.jpg }. png "
echo converting " $jpg " ...
5 if convert " $jpg " jpg . to . png ; then
mv jpg . to . png " $png "
7 else
echo ’ jpg2png : error : failed output saved in
" jpg . to . png " . ’ >&2
9 exit 1
117
fi
11 done
echo all conversions successful
13 exit 0
6 my ( $remaining , $total ) ;
12 while ( $remaining ) {
printf ( " Remaining %s/ %s \ r " , $remaining - - , $total ) ;
14 sleep 1;
}
16
5 public :
// Class ( static ) functions
7 static void * classMethod1 () ;
static return_type classMethod2 () ;
9 static return_type classMethod3 ( param1_type
param1_varName ) ;
118
11 // Instance ( member ) functions
return_type instanceMethod1With1Parameter ( param1_type
param1_varName ) ;
13 return_type instanceMethod2With2Parameters ( param1_type
param1_varName , param2_type param2_varName = default ) ;
};
119
Capı́tulo 13
Mitchell 13.5
13.1. Ejercicios
13.1. En Java, hay varios mecanismos de seguridad a través del lenguaje, y muchos de
ellos interactúan. Los class loaders son parte del Java Runtime Environment (JRE)
y sirven para cargar dinámicamente, por demanda, clases en tiempo de ejecución.
No se puede cargar ninguna clase sin un class loader. Sabiendo el rol del Security
Manager en Java, explique cómo imagina que debe ser la relación entre el Security
Manager y un class loader para preservar seguridad.
13.4. La no interferencia es uno de los principios básicos de seguridad. Explique cómo los
niveles de visibilidad de un lenguaje orientado a objetos pueden contribuir a la no
interferencia. Ahora, explique cómo la visibilidad ortogonal (por ejemplo, el package
en Java) pueden contribuir a la implementación de polı́ticas de acceso a información
más complejas que las basadas únicamente en herencia.
120
defensiva. Explique qué tipo de vulnerabiliad encontramos en estos códigos, si vulne-
rabilidad en el sistema de tipos o vulnerabilidad de memoria, y en qué consiste esta
vulnerabilidad de forma genérica.
const char * trafficlight_colorname ( enum
trafficlight_color c ) {
2 switch ( c ) {
case TRAFFICLIGHT_RED : return " red " ;
4 case TRAFFICLIGHT_YELLOW : return " yellow " ;
case TRAFFICLIGHT_GREEN : return " green " ;
6 }
return " black " ;
8 }
121
Un ejemplo de uso:
1 // returns null if ( and only if ) foo is null , or bar ()
returns null ,
// or baz () returns null
3 foo ?. bar () ?. baz ()
122
Apéndice A
123
Paradigmas de la Programación – Examen Final
5 de Julio de 2019
Apellido y Nombre:
1. [10 pt.] Diagrame los sucesivos estados por los que pasa la pila de ejecución del siguiente programa
cando se ejecuta N(3), asumiendo que el lenguaje tiene alcance estático. ¿Qué se imprimirá si el
lenguaje tiene alcance estático? ¿Y si tiene alcance dinámico?
p r o c e d u r e N( y : i n t )
begin
var z : i n t = 2 ;
p r o c e d u r e Q( v : i n t )
begin
print v ;
end
procedure P( )
begin
print z ;
end
i f true begin
var z : i n t = 4 2 ;
call P();
end
print y ;
print z ;
c a l l Q( y ) ;
end
2. [10 pt.] En el siguiente fragmento de código, qué se imprimirá si el lenguaje tiene pasaje de parámetros
por referencia, pasaje de parámetros por valor, y pasaje de parámetros por valor-resultado?
z : integer ;
procedure p (x : i n t e g e r )
x := x+1 ;
z := z +2;
z := 1 ;
p( z ) ;
write ( z )
1
3. [10 pt.] Diagrame los estados por los que pasa la pila de ejecución en el siguiente programa en java,
enfocándose únicamente en el manejo de excepciones, sin representar las variables locales, control links,
access links, retorno de función ni ninguna otra información que no sea relevante al proceso de manejo
de excepciones. Explique qué pasa con los dos catch, y por qué.
c l a s s Test
{
public s t a t i c void main ( S t r i n g [ ] a r g s )
{
try
{
int a [ ] = { 1 , 2 , 3 , 4 } ;
f or ( int i = 1 ; i <= 4 ; i ++)
{
System . out . p r i n t l n ( ”a [ ” + i + ”]=” + a [ i ] + ”n” ) ;
}
}
catch ( E x c e p t i o n e )
{
System . out . p r i n t l n ( ” e r r o r = ” + e ) ;
}
catch ( ArrayIndexOutOfBoundsException e )
{
System . out . p r i n t l n ( ” ArrayIndexOutOfBoundsException ” ) ;
}
}
}
4. [10 pt.] Dada la siguiente base de conocimiento en Prolog, liste los subobjetivos que hay que satisfacer
para verificar si el predicado tio(Pedro,Juan). es cierto o no.
hermano ( Pedro , J u l i a ) .
hermana ( Ana , T e re s a ) .
hermano ( Pedro , Esteban ) .
madre ( Ana , Juan ) .
padre ( Esteban , Juan ) .
5. [10 pt.] En el siguiente programa en AWK, identifique por lo menos dos caracterı́sticas propias de los
lenguajes de scripting y justifı́quelas con porciones del código.
i f ( ( x=i n d e x ( $1 , ”@” ) ) > 0 ) {
username = s u b s t r ( $1 , 1 , x −1);
hostname = s u b s t r ( $1 , x+1, l e n g t h ( $1 ) ) ;
p r i n t f ( ” username = %s , hostname = %s \n ” , username , hostname ) ;
}
2
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
7. [5 pt.] De estos dos fragmentos de códigos, cuál es un ejemplo de polimorfismo y cuál es un ejemplo
de sobrecarga? Justifique su respuesta.
public c l a s s Animal {
public void sound ( ) {
System . out . p r i n t l n ( ” Animal i s making a sound ” ) ;
}
}
c l a s s Examp
{
void demo ( int a )
{
System . out . p r i n t l n ( ” a : ” + a ) ;
}
void demo ( int a , int b )
{
System . out . p r i n t l n ( ” a and b : ” + a + ” , ” + b ) ;
}
double demo ( double a ) {
System . out . p r i n t l n ( ” d o u b l e a : ” + a ) ;
return a∗ a ;
}
}
c l a s s MethodExamping
{
public s t a t i c void main ( S t r i n g a r g s [ ] )
{
Examp Obj = new Examp ( ) ;
double r e s u l t ;
Obj . demo ( 1 0 ) ;
Obj . demo ( 1 0 , 2 0 ) ;
r e s u l t = Obj . demo ( 5 . 5 ) ;
System . out . p r i n t l n ( ”O/P : ” + r e s u l t ) ;
}
}
3
8. [15 pt.] Explique por qué el siguiente programa es un ejemplo de programación defensiva, identifique
las lı́neas de código que implementan programación defensiva (5 pt.). Explique qué vulnerabilidad
del lenguaje se está protegiendo mediante estas acciones, y cómo otros lenguajes evitan este tipo de
vulnerabilidad (5 pt.). Por último, escriba en pseudocódigo cómo se podrı́a tratar este problema con
excepciones (5 pt.).
s t a t i c v o i d CreateRandomPermutation ( i n t numbers [ ] , i n t nNumbers )
{
a s s e r t ( numbers != NULL ) ;
a s s e r t ( nNumbers >= 0 ) ;
9. [5 pt.] Lea el siguiente texto y explique con sus propias palabras el nivel de visibilidad package en
Java, y relaciónelo con los niveles de visibilidad private y protected.
Access level modifiers determine whether other classes can use a particular field or invoke a
particular method. There are two levels of access control:
At the top level—public, or package-private (no explicit modifier).
At the member level—public, private, protected, or package-private (no explicit modifier).
A class may be declared with the modifier public, in which case that class is visible to all
classes everywhere. If a class has no modifier (the default, also known as package-private),
it is visible only within its own package (packages are named groups of related classes).
At the member level, you can also use the public modifier or no modifier (package-private)
just as with top-level classes, and with the same meaning. For members, there are two addi-
tional access modifiers: private and protected. The private modifier specifies that the member
can only be accessed in its own class. The protected modifier specifies that the member can
only be accessed within its own package (as with package-private) and, in addition, by a
subclass of its class in another package.
10. [10 pt.] De los siguientes fragmentos de código, ¿cuál es declarativo? En el que no es declarativo ¿Qué
fenómenos podemos encontrar que no son declarativos?
procedure FACTORIAL i s
N: i n t e g e r ;
T : i n t e g e r := 1 ;
begin
put ( ” Input ” ) ; g e t (N ) ;
f o r K in 2 . . N loop
T:= T∗K;
end loop ;
put ( ” F a c t o r i a l ” ) ; put (N ) ;
put ( ” i s ” ) ; put (T ) ; n e w l i n e ;
end FACTORIAL;
4
f a c t o r i a l (0 ,1): −
!.
f a c t o r i a l (N1 , T2): −
N2 i s N1−1,
f a c t o r i a l (N2 , T1 ) ,
T2 i s N1∗T1 .
c l a s s Gato : p u b l i c F e l i n o {
public :
v o i d meow ( ) { s t d : : c o u t << ”miau\n ” ; }
};
2. [10 pt.] En el siguiente programa en Erlang, identifique los fragmentos de programa con semántica
concurrente y explique cuál es su semántica y por qué es concurrente, es decir, qué semántica expresan
que no está incluı́da en la expresividad de las máquinas de Turing. Identifique, en particular, cómo se
expresa sincronización, exclusión mútua, generación de procesos y monitoreo de procesos.
Note que alguna de las palabras del programa se puede usar para más de una de estas funciones.
max(N) −>
Max = e r l a n g : s y s t e m i n f o ( p r o c e s s l i m i t ) ,
i o : format ( ”Maximum a l l o w e d p r o c e s s e s : ˜ p˜n” , [ Max ] ) ,
s t a t i s t i c s ( runtime ) ,
statistics ( wall clock ) ,
U1 = Time1 ∗ 1000 / N,
5
U2 = Time2 ∗ 1000 / N,
i o : format ( ” P r o c e s s spawn time=˜p ( ˜ p ) m i c r o s e c o n d s ˜n” , [ U1 , U2 ] ) .
w a i t ( ) −>
receive
d i e −> v o i d
end .
f o r (N, N, F) −> [ F ( ) ] ;
f o r ( I , N, F) −> [ F ( ) | f o r ( I +1, N, F ) ] .
s t a r t ()−>
max ( 1 0 0 0 ) ,
max ( 1 0 0 0 0 0 ) .
3. [10 pt.] El siguiente código (resumido) de un driver SCSI para Linux es spaghetti. ¿Por qué decimos
que es spaghetti, cuál es la principal diferencia con el código estructurado? ¿Si el lenguaje obliga a
usar código estructurado, qué estructura de datos puede usar el compilador para manejar la memoria
de forma más eficiente? Reescriba el driver en pseudocódigo para que sea estructurado y no spaghetti.
wait nomsg :
i f ( ( i n b ( tmport ) & 0 x04 ) != 0 ) {
g o t o wait nomsg ;
}
...
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x80 ) != 0 ) {
goto w a i t i o ;
}
}
g o t o TCM SYNC;
wait io :
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x81 ) == 0 x0081 ) {
goto w a i t i o 1 ;
}
}
g o t o TCM SYNC;
wait io1 :
...
TCM SYNC:
...
6
Paradigmas de la Programación – Recuperatorio del Segundo Parcial
18 de Junio de 2019
Apellido y Nombre:
1. [10 pt.] Ordene los siguientes fragmentos de código de más de scripting a menos de scripting, justifi-
cando su respuesta con al menos 3 caracterı́sticas de los lenguajes de scripting.
main ( ) {
extrn a , b , c ;
putchar ( a ) ; putchar (b ) ; putchar ( c ) ; putchar ( ’ ! ∗ n ’ ) ;
}
a ’ hell ’ ;
b ’o , w’ ;
c ’ orld ’ ;
p u t s ” H e l l o , world ! ”
p u b l i c c l a s s HelloWorld {
%i n c l u d e { s t r i n g . tom }
p u b l i c f i n a l s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S t r i n g who = ” world ” ;
%match ( S t r i n g who ) {
”World” −> { System . out . p r i n t l n ( ” H e l l o , ” + who + ” ! ” ) ; }
−> { System . out . p r i n t l n ( ”Don ’ t p a n i c ” ) ; }
}
}
2. [10 pt.] En el siguiente texto se describe el uso de streams en programación reactiva. Explique en
sus propias palabras qué es un stream, y cómo una componente declarativa puede tratar un stream,
usando el concepto de “mensaje”.
A stream is a sequence of ongoing events ordered in time. It can emit three different things:
a value (of some type), an error, or a çompleted”signal. Consider that the çompleted”takes
place, for instance, when the current window or view containing that button is closed.
We capture these emitted events only asynchronously, by defining a function that will execute
when a value is emitted, another function when an error is emitted, and another function
when ’completed’ is emitted. Sometimes these last two can be omitted and you can just focus
on defining the function for values. The ”listening”to the stream is called subscribing. The
functions we are defining are observers. The stream is the subject (or .observable”) being
observed. This is precisely the Observer Design Pattern.
1
3. [15 pt.] El siguiente código no compila. Explique por qué, y qué habrı́a que hacer para que compile.
c l a s s USBDevice
{
private :
long m id ;
public :
USBDevice ( long i d )
: m id ( i d )
{
}
c l a s s NetworkDevice
{
private :
long m id ;
public :
NetworkDevice ( long i d )
: m id ( i d )
{
}
int main ( )
{
W i r e l e s s A d a p t e r c54G ( 5 4 4 2 , 1 8 1 7 4 2 ) ;
s t d : : c o u t << c54G . getID ( ) ; // Which g e t I D ( ) do we c a l l ?
return 0 ;
}
2
4. En el siguiente texto se explica qué son los tipos nulificables, las excepciones de puntero nulo en Java
y cómo Kotlin implementa estrategias para evitar este tipo de excepciones. Comente en sus propias
palabras cuándo se da una excepción de puntero nulo en Java [5 pt.].
Explique cómo se relacionan las estrategias de Kotlin para evitar excepciones de puntero nulo con
estrategias genéricas de programación defensiva [10 pt.].
Java types are divided into primitive types (boolean, int, etc.) and reference types. Reference
types in Java allow you to use the special value null which is the Java way of saying ”no
object”.
A NullPointerException is thrown at runtime whenever your program attempts to use a
null as if it was a real reference. For example, if you write this:
public c l a s s Test {
public s t a t i c void main ( S t r i n g [ ] a r g s ) {
S t r i n g f o o = null ;
int l e n g t h = f o o . l e n g t h ( ) ; // HERE
}
}
the statement labelled “HERE” is going to attempt to run the length() method on a null
reference, and this will throw a NullPointerException.
There’re several strategies that can help us avoid this exception, making our codes more
robust.
The most obvious way is to use if (obj == null) to check every variable you need to use,
either from function argument, return value, or instance field. When you receive a null ob-
ject, you can throw a different, more informative exception like IllegalArgumentException.
There are some library functions that can make this process easier, like Objects#requireNonNull.
Kotlin was designed to eliminate the danger of null pointer references. It does this by making
a null illegal for standard types, adding nullable types, and implementing shortcut notations
to handle tests for null. For example, a regular variable of type String cannot hold null:
var a : S t r i n g =” abc ”
a = null // c o m p i l a t i o n e r r o r
If you need to allow nulls, for example to hold SQL query results, you can declare a nullable
type by appending a question mark to the type, e.g. String?.
var b : S t r i n g ? =” abc ”
b = null // ok
The protections go a little further. You can use a non-nullable type with impunity, but you
have to test a nullable type for null values before using it.
To avoid the verbose grammar normally needed for null testing, Kotlin introduces a safe call,
written ?.. For example, b?.length returns b.length if b is not null, and null otherwise.
In other words, b?.length is a shortcut for if (b != null) b.length else null.
3
numbers = [ 1 . 5 , 2 . 3 , 0 . 7 , −0.001 , 4 . 4 ]
total = 0.0
f o r n i n numbers :
a s s e r t n >= 0 . 0 , ’ A s s e r t 1 ’
t o t a l += n
print ’ total is : ’ , total
6. [10 pt.] En el siguiente fragmento de código, identifique construcciones lingüı́sticas con semántica de:
a) sincronización de procesos
b) manejo explı́cito de procesos
public c l a s s Counting {
public s t a t i c void main ( S t r i n g [ ] a r g s ) throws I n t e r r u p t e d E x c e p t i o n {
c l a s s Counter {
int c o u n t e r = 0 ;
public void i n c r e m e n t ( ) { c o u n t e r ++; }
public int g e t ( ) { return c o u n t e r ; }
}
7. [10 pt.] A partir de la siguiente base de conocimiento, liste los subobjetivos que se tienen que satisfacer
para comprobar que sabe de(valen,julen).
s a b e d e (X,Y) :− f u e p r e s e n t a d o (X,Y ) .
s a b e d e (X,Y) :−
f u e p r e s e n t a d o (X, Z ) ,
s a b e d e ( Z ,Y ) .
f u e p r e s e n t a d o ( maxi , j u l e n ) .
f u e p r e s e n t a d o ( sam , maxi ) .
f u e p r e s e n t a d o ( va len , sam ) .
4
8. [15 pt.] El segundo fragmento de código es equivalente al primero pero con inversión de control.
Describa qué caracterı́sticas del código convierten al segundo en un ejemplo de inversión de control,
haciendo referencia a partes especı́ficas del código.
c l a s s S u p e rh e ro
{
p u b l i c $name ;
c l a s s ComicBook
{
p u b l i c $mainCharacter ;
public function c o n s t r u c t ( Su p e r h e r o $ s u p e r h e r o )
{
$ t h i s −>mainCharacter = $ s u p e r h e r o ;
}
}
$ s u p e r h e r o = new S up e r he ro ( ’ C a f f e i n e Man ’ ) ;
$comic = new ComicBook ( $ s u p e r h e r o ) ;
interface CharacterInterface
{
p u b l i c f u n c t i o n getName ( ) : s t r i n g ;
}
a b s t r a c t c l a s s C h a r a c t e r implements C h a r a c t e r I n t e r f a c e
{
p u b l i c $name ;
p u b l i c f u n c t i o n getName ( ) : s t r i n g
{
r e t u r n $ t h i s −>name ;
}
}
c l a s s S u p e rh e ro e x t e n d s C h a r a c t e r
{
p u b l i c $name ;
5
$ t h i s −>name = $name ;
}
}
c l a s s VampiricDog e x t e n d s C h a r a c t e r
{
p u b l i c $name ;
c l a s s ComicBook
{
p u b l i c $mainCharacter ;
$ s u p e r h e r o = new S up e r he ro ( ’ C a f f e i n e Man ’ ) ;
$superheroComic = new ComicBook ( $ s u p e r h e r o ) ;
6
Paradigmas de la Programación – Recuperatorio del Primer Parcial
18 de junio de 2019
Apellido y Nombre:
[10 pt.] Diagrame los estados por los que va pasando la pila de ejecución al ejecutarse el programa,
asumiendo que el lenguaje tiene alcance estático.
[10 pt.] ¿Qué se imprimirı́a si el lenguaje tiene alcance estático? ¿Y si tiene alcance dinámico?
[10 pt.] ¿Qué se imprimirı́a si el lenguaje tiene pasaje de parámetros por valor? ¿Y si tiene pasaje
de parámetros por referencia?
var x : i n t = 0 ;
var y : i n t = 1 ;
p r o c e d u r e M( x : i n t )
begin
var y : i n t = 4 2 ;
x := 1 ;
c a l l N( x ) ;
end
p r o c e d u r e N( x : i n t )
begin
var z : i n t = 2 ;
i f true begin
var z : i n t = 4 2 ;
end
print x ;
print y ;
print z ;
end
p r o c e d u r e Main ( )
begin
c a l l M( x ) ;
end
1
2. [10 pt.] La siguiente expresión está mal tipada:
f (a , b) = ( b + a > 3 ) | | b
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
3. [10 pt.] Explique por qué la instrucción go to puede producir código no estructurado. Explique a
partir de eso cuál es la diferencia esencial entre código estructurado y no estructurado, y qué impacto
tiene esa diferencia en la gestión de la memoria. La instrucción go to, ¿puede usarse siguiendo las
reglas de código estructurado?
4. [10 pt.] Teniendo en cuenta que la siguiente función en Javascript tiene transparencia referencial ¿qué
diferencia hay entre pasar los parámetros por valor y pasarlos por referencia?
c o n s t sum = a => b => a + b ;
c o n s o l e . l o g ( sum ( 5 ) ( 3 ) ) ;
5. [10 pt.] Estas dos funciones tienen la misma semántica, pero una de ellas no es declarativa. ¿Cuál no
es declarativa y por qué no lo es?
c o n s t c o n t a i n e r = document . getElementById ( ’ c o n t a i n e r ’ ) ;
c o n s t btn = document . c r e a t e E l e m e n t ( ’ button ’ ) ;
btn . className = ’ btn red ’ ;
btn . o n c l i c k = f u n c t i o n ( e v e n t ) {
i f ( t h i s . c l a s s L i s t . c o n t a i n s ( ’ red ’ ) ) {
t h i s . c l a s s L i s t . remove ( ’ red ’ ) ;
t h i s . c l a s s L i s t . add ( ’ blue ’ ) ;
} else {
t h i s . c l a s s L i s t . remove ( ’ blue ’ ) ;
t h i s . c l a s s L i s t . add ( ’ red ’ ) ;
}
};
c o n t a i n e r . appendChild ( btn ) ;
2
6. [10 pt.] En el siguiente código en Java, cuándo se puede recolectar el objeto Bar creado en la lı́nea 6?
Justifique su respuesta usando el concepto de alcanzabilidad.
c l a s s Bar { }
c l a s s Test
{
Bar doBar ( )
{
Bar b = new Bar ( ) ; /∗ Linea 6 ∗/
return b ; /∗ Linea 7 ∗/
}
public s t a t i c void main ( S t r i n g a r g s [ ] )
{
Test t = new Test ( ) ; /∗ Linea 11 ∗/
Bar newBar = t . doBar ( ) ; /∗ Linea 12 ∗/
System . out . p r i n t l n ( ”newBar” ) ;
newBar = new Bar ( ) ; /∗ Linea 14 ∗/
System . out . p r i n t l n ( ” f i n i s h i n g ” ) ; /∗ Linea 15 ∗/
}
}
7. [10 pt.] Diagrame los estados por los que pasa la pila de ejecución en el siguiente programa en java,
enfocándose únicamente en el manejo de excepciones, sin representar las variables locales, control links,
access links, retorno de función ni ninguna otra información que no sea relevante al proceso de manejo
de excepciones. Explique qué pasa con los dos catch, y por qué.
c l a s s Test
{
public s t a t i c void main ( S t r i n g [ ] a r g s )
{
try
{
int a [ ] = { 1 , 2 , 3 , 4 } ;
f or ( int i = 1 ; i <= 4 ; i ++)
{
System . out . p r i n t l n ( ”a [ ” + i + ”]=” + a [ i ] + ”n” ) ;
}
}
catch ( E x c e p t i o n e )
{
System . out . p r i n t l n ( ” e r r o r = ” + e ) ;
}
catch ( ArrayIndexOutOfBoundsException e )
{
System . out . p r i n t l n ( ” ArrayIndexOutOfBoundsException ” ) ;
}
}
}
3
Paradigmas de la Programación – Segundo Parcial
11 de Junio de 2019
Apellido y Nombre:
1. [10 pt.] El algoritmo de linearización C3 se usa para obtener el orden en que se tienen que heredar
los métodos en presencia de herencia múltiple, y se suele llamar Orden de Resolución de Métodos
(Method Resolution Order (MRO)). Varios lenguajes de scripting, entre ellos Python y Perl (a partir
de 6) lo implementan, tal como se muestra en el siguiente ejemplo:
c l a s s A {}
c l a s s B {}
c l a s s C {}
c l a s s D {}
c l a s s E {}
c l a s s K1 i s A i s B i s C {}
c l a s s K2 i s D i s B i s E {}
c l a s s K3 i s D i s A {}
c l a s s Z i s K1 i s K2 i s K3 {}
say Z . ˆ mro ; # OUTPUT: ( ( Z ) (K1) (K2) (K3) (D) (A) (B) (C) (E) (Any) (Mu) )
Explique por qué es necesario un algoritmo de linearización, qué problema resuelve y describa una
estrategia más para resolver el mismo problema.
2. [10 pt.] En el siguiente fragmento de código, identifique caracterı́sticas propias de los lenguajes de
scripting, señálelas en el código y descrı́balas.
f l o a t v l e n ( v e c t o r v ) = #12;
e n t i t y n e x t e n t ( e n t i t y e ) = #47;
1
3. [15 pt.] En el siguiente fragmento de código vemos un actor que tiene semántica de acumulador sin
usar asignación destructiva. El resultado de la acumulación se asigna por única vez al recolectar los
resultados de todas las sumas que componen los cómputos parciales que realizaron otros actores.
Identifique en el código construcciones lingüı́sticas con la siguiente semántica propia de actores:
a) pasaje de mensajes,
b) tipado de variables para excluir asignación destructiva,
c) sincronización con otros procesos especificando variables con resultados parciales que serán satis-
fechos cuando se complete otro proceso.
v a l t o t a l = system . a c t o r O f ( Props [ T o t a l ] , ” t o t a l ” )
v a l measurementsWebSocket =
Flow [ Message ]
. collect {
case TextMessage . S t r i c t ( t e x t ) =>
Future . s u c c e s s f u l ( t e x t )
case TextMessage . Streamed ( t e x t S t r e a m ) =>
t e x t S t r e a m . runFold ( ” ” ) ( + )
. flatMap ( Future . s u c c e s s f u l )
}
. mapAsync ( 1 ) ( i d e n t i t y )
. groupedWithin ( 1 0 0 0 , 1 s e c o n d )
. map( me ssa ge s => ( me ssage s . l a s t , Messages . p a r s e ( mes sage s ) ) )
. map {
case ( l a s t M e s s a g e , measurements ) =>
t o t a l ! Increment ( measurements . sum )
lastMessage
}
. map( Messages . ack )
val route =
path ( ” measurements ” ) {
get {
handleWebSocketMessages ( measurementsWebSocket )
}
}
v a l b i n d i n g F u t u r e = Http ( ) . bindAndHandle ( r o u t e , ” l o c a l h o s t ” , 8 0 8 0 )
4. En el siguiente texto se explica qué son los tipos nulificables, las excepciones de puntero nulo en Java
y cómo Kotlin implementa estrategias para evitar este tipo de excepciones. Comente en sus propias
palabras cuándo se da una excepción de puntero nulo en Java [10 pt.].
Explique cómo se relacionan las estrategias de Kotlin para evitar excepciones de puntero nulo con
estrategias genéricas de programación defensiva [10 pt.].
2
Java types are divided into primitive types (boolean, int, etc.) and reference types. Reference
types in Java allow you to use the special value null which is the Java way of saying ”no
object”.
A NullPointerException is thrown at runtime whenever your program attempts to use a
null as if it was a real reference. For example, if you write this:
public c l a s s Test {
public s t a t i c void main ( S t r i n g [ ] a r g s ) {
S t r i n g f o o = null ;
int l e n g t h = f o o . l e n g t h ( ) ; // HERE
}
}
the statement labelled “HERE” is going to attempt to run the length() method on a null
reference, and this will throw a NullPointerException.
There’re several strategies that can help us avoid this exception, making our codes more
robust.
The most obvious way is to use if (obj == null) to check every variable you need to use,
either from function argument, return value, or instance field. When you receive a null ob-
ject, you can throw a different, more informative exception like IllegalArgumentException.
There are some library functions that can make this process easier, like Objects#requireNonNull.
Kotlin was designed to eliminate the danger of null pointer references. It does this by making
a null illegal for standard types, adding nullable types, and implementing shortcut notations
to handle tests for null. For example, a regular variable of type String cannot hold null:
var a : S t r i n g =” abc ”
a = null // c o m p i l a t i o n e r r o r
If you need to allow nulls, for example to hold SQL query results, you can declare a nullable
type by appending a question mark to the type, e.g. String?.
var b : S t r i n g ? =” abc ”
b = null // ok
The protections go a little further. You can use a non-nullable type with impunity, but you
have to test a nullable type for null values before using it.
To avoid the verbose grammar normally needed for null testing, Kotlin introduces a safe call,
written ?.. For example, b?.length returns b.length if b is not null, and null otherwise.
In other words, b?.length is a shortcut for if (b != null) b.length else null.
5. [10 pt.] En el siguiente fragmento de código, identifique construcciones lingüı́sticas con semántica de:
a) sincronización de procesos
b) manejo explı́cito de procesos
3
6. [10 pt.] De estos dos fragmentos de código con funcionalidades comparables, uno tiene inversión de
control y el otro no. Explique cuál es el que tiene inversión de control y justifique su respuesta.
interface C or re c to rO rt o gr af ic o {
A r r a y l i s t <e r r o r e s > C o r r e c c i o n O r t o g r a f i c a ( s t r i n g Text ) ;
}
Class TextEditor {
CorrectorOrtografico objCorrectorOrtografico ;
s t r i n g Text ;
public void T e x t E d i t o r ( C o r r e c t o r O r t o g r a f i c o objSC ) {
o b j C o r r e c t o r O r t o g r a f i c o = objSC ;
}
Class TextEditor {
//un monton de c o d i g o para e l e d i t o r
CorrectorOrtograficoIngles objCorrectorOrtografico ;
String text ;
public void T e x t E d i t o r ( ) {
o b j C o r r e c t o r O r t o g r a f i c o = new C o r r e c t o r O r t o g r a f i c o I n g l e s ( ) ;
}
public A r r a y L i s t <e r r o r e s > C o r r e c c i o n O r t o g r a f i c a ( ) {
// d e v u e l v e e r r o r e s de o r t o g r a f i a ;
}
}
4
7. [15 pt.] El siguiente fragmento de Elm explica cómo se tratan los errores en Elm. Describa con sus
propias palabras esta polı́tica de tratamiento de errores y de su opinión sobre si esta polı́tica contribuye
de alguna forma a la seguridad del lenguaje con respecto a vulnerabilidades por el sistema de tipos.
One of the guarantees of Elm is that you will not see runtime errors in practice. This is
partly because Elm treats errors as data. Rather than crashing, we model the possibility of
failure explicitly with custom types. For example, say you want to turn user input into an
age. You might create a custom type like this:
type MaybeAge
= Age I n t
| InvalidInput
Instead of crashing on bad input, we say explicitly that the result may be an Age 24 or an
InvalidInput. No matter what input we get, we always produce one of these two variants.
From there, we use pattern matching which will ensure that both possibilities are accounted
for. No crashing!
8. [10 pt.] A partir de la siguiente base de conocimiento, liste los subobjetivos que se tienen que satisfacer
para comprobar que digiriendo(gaviota,mosquito).
d i g i r i e n d o (X,Y) :− comio (X,Y ) .
d i g i r i e n d o (X,Y) :−
comio (X, Z ) ,
d i g i r i e n d o ( Z ,Y ) .
5
Paradigmas de la Programación – Primer Parcial
25 de Abril de 2019
Apellido y Nombre:
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
2. [10 pt.] El siguiente código (resumido) de un driver SCSI para Linux es spaghetti. ¿Por qué decimos
que es spaghetti, cuál es la principal diferencia con el código estructurado? ¿Si el lenguaje obliga a
usar código estructurado, qué estructura de datos puede usar el compilador para manejar la memoria
de forma más eficiente? Reescriba el driver en pseudocódigo para que sea estructurado y no spaghetti.
wait nomsg :
i f ( ( i n b ( tmport ) & 0 x04 ) != 0 ) {
g o t o wait nomsg ;
}
...
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x80 ) != 0 ) {
goto w a i t i o ;
}
}
g o t o TCM SYNC;
wait io :
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x81 ) == 0 x0081 ) {
goto w a i t i o 1 ;
}
}
g o t o TCM SYNC;
wait io1 :
...
TCM SYNC:
...
3. [10 pt.] De estos dos fragmentos de códigos, el primero en Pascal y el segundo en C++, cuál es un
ejemplo de sobrecarga y cuál es un ejemplo de polimorfismo? Justifique su respuesta.
1
f u n c t i o n Add( x , y : I n t e g e r ) : I n t e g e r ;
begin
Add := x + y
end ;
f u n c t i o n Add( s , t : S t r i n g ) : S t r i n g ;
begin
Add := Concat ( s , t )
end ;
c l a s s L i s t <T> {
c l a s s Node<T> {
T elem ;
Node<T> next ;
}
Node<T> head ;
int length () { . . . }
}
4. [15 pt.] En el siguiente programa en Lua, diagrame el estado en el que se encuentra la pila de ejecución
al terminar de ejecutar las lı́neas señaladas con A, B, C y D en el texto del programa1 .
a = 1
b = 2
c = 3
function test ()
local function g ()
local a = 2
c = a + 4 −−−> D
end
local function f ()
local c = 5
b = 4 −−−> C
g ()
end
−−−> B
f ()
end
−−−> A
test ()
5. [10 pt.] En el siguiente programa, ¿encontraremos una diferencia si el alcance del lenguaje es estático
o si el alcance es dinámico? ¿Qué se imprimirı́a en cada caso?
1
Para ser más precisos, al terminar de ejecutar el equivalente en código máquina a esas lı́neas del código en Lua.
2
procedure p ;
x : integer ;
procedure q ;
b e g i n x := x+1 end ;
procedure r ;
x : integer ;
b e g i n x := 1 ; q ; w r i t e ( x ) end ;
begin
x:= 2 ;
r
end ;
6. [10 pt.] Qué tres valores imprimirı́a este programa (con la expresión write) si el lenguaje en el que
está programado tuviera pasaje de parámetros a) por valor, b) por referencia, c) por valor resultado y
d) por nombre?
in t i , A[ 2 ]
i <− 1
Proc edure f o o ( i n t x , i n t y )
i n t temp
temp <− x
x <− y
i <− 0
y <− temp
end
A [ 0 ] <− 0
A [ 1 ] <− 2
f o o ( i , A[ i ] )
write i , A[ 0 ] , A[ 1 ]
7. [10 pt.] Estas dos funciones tienen la misma semántica, pero una de ellas no es declarativa. ¿Cuál no
es declarativa y por qué no lo es?
d e f summation ( n , term ) :
total , k = 0 , 1
w h i l e k <= n :
t o t a l , k = t o t a l + term ( k ) , k + 1
return total
d e f summation ( n , term ) :
i f n == 0 :
r e t u r n term ( n )
else :
r e t u r n term ( n ) + summation ( n − 1 , term )
8. [10 pt.] Lea los siguientes textos y explique con sus propias palabras la diferencia entre abstracción y
encapsulación. Incluya en su explicación los conceptos de interfaz e implementación.
3
Abstraction is a process where you show only “relevant” data and “hide” unnecessary details
of an object from the user. Consider your mobile phone, you just need to know what buttons
are to be pressed to send a message or make a call, What happens when you press a button,
how your messages are sent, how your calls are connected is all abstracted away from the
user.
Encapsulation is the process of combining data and functions into a single unit called class.
In Encapsulation, the data is not accessed directly; it is accessed through the functions present
inside the class. In simpler words, attributes of the class are kept private and public getter
and setter methods are provided to manipulate these attributes. Thus, encapsulation makes
the concept of data hiding possible.
Stackoverflow
In object oriented programming languages, encapsulation is used to refer to one of two related
but distinct notions, and sometimes to the combination thereof:
A language mechanism for restricting direct access to some of the object’s components.
A language construct that facilitates the bundling of data with the methods (or other
functions) operating on that data.
Wikipedia
9. [15 pt.] Diagrame los estados por los que pasa la pila de ejecución en el siguiente programa en java,
enfocándose únicamente en el manejo de excepciones, sin representar las variables locales, control links,
access links, retorno de función ni ninguna otra información que no sea relevante al proceso de manejo
de excepciones. Explique si hay alguna relación entre los throws y el catch, y cómo se puede llegar a
generar la excepción que captura el catch si no se encuentra explı́citamente en el código.
import j a v a . i o . ∗ ;
public c l a s s t e s t {
int c h a n c e s = 3 ;
boolean s u c c e s s = f a l s e ;
B u f f e r e d R e a d e r br = null ;
B u f f e r e d W r i t e r bw = null ;
while ( ! s u c c e s s ) {
try {
F i l e R e a d e r f r = new F i l e R e a d e r ( from ) ;
F i l e W r i t e r fw = new F i l e W r i t e r ( t o ) ;
br = new B u f f e r e d R e a d e r ( f r ) ;
bw = new B u f f e r e d W r i t e r ( fw ) ;
4
String line ;
l i n e = br . r e a d L i n e ( ) ;
while ( l i n e != null ) {
System . out . p r i n t l n ( l i n e ) ;
bw . w r i t e ( l i n e ) ;
l i n e = br . r e a d L i n e ( ) ;
}
s u c c e s s = true ;
}
catch ( S e c u r i t y E x c e p t i o n e ) {
i f ( c h a n c e s == 0 )
break ;
chances −−;
System . out . p r i n t l n ( ” S e c u r i t y E x c e p t i o n ” ) ;
System . out . p r i n t l n ( ” Update f i l e p e r m i s s s i o n s . ” ) ;
byte [ ] b y t e s = new byte [ 3 ] ;
System . i n . r e a d ( b y t e s ) ;
}
finally {
try {
i f ( br != null )
br . c l o s e ( ) ;
i f (bw != null )
bw . c l o s e ( ) ;
}
catch ( IOException e ) {}
}
}
}
}
5
Paradigmas de la Programación – Examen Final
5 de Julio de 2018
Apellido y Nombre:
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
2. [10 pt.] En el siguiente programa, diagrame los diferentes estados por los que va pasando la pila de
ejecución, con atención a las relaciones entre las variables.
#i n c l u d e < s t d i o . h >
i n t x=0;
void p( int , i n t ) ;
v o i d main ( ) {
int x = 1;
p(x , x ) ;
}
void p( i n t y , i n t z ){
x = x+1;
y = y+1;
z = z +1;
p r i n t f (” %d\n ” , x+y+z ) ;
}
v o i d p ( i n t &y , i n t &z ) {
x = x+1;
y = y+1;
z = z +1;
p r i n t f (” %d\n ” , x+y+z ) ;
}
3. [10 pt.] Diagrame los sucesivos estados por los que pasa la pila de ejecución al ejecutar el siguiente
programa en ML:
e x c e p t i o n Excpt o f i n t ;
fun t w i c e ( f , x ) = f ( f ( x ) ) h a n d l e Excpt ( x ) => x ;
fun pred ( x ) = i f x = 0 then r a i s e Excpt ( x ) e l s e x −1;
fun dumb( x ) = r a i s e Excpt ( x ) ;
fun smart ( x ) = 1 + pred ( x ) h a n d l e Excpt ( x ) => 1 ;
t w i c e ( pred , 1 ) ;
1
4. [15 pt.] El siguiente fragmento de código funciona porque se aplica un tipo de polimorfismo que
recurre al subtipado que se articula a través de la herencia. En este lenguaje, el subtipado permite que
una función se defina para tomar objetos de tipo T pero la función también funciona correctamente si
se le pasan objetos de tipo S, siempre que S sea subtipo de T . Esto es ası́ porque se aplica el principio
de Liskov, enunciado por Bárbara Liskov y Jeannette Wing de la siguiente forma:
Let φ(x) be a property provable about objects x of type T . Then φ(y) should be true for objects
y of type S where S is a subtype of T .
Explique con sus propias palabras por qué funciona este código, recurriendo a los conceptos de poli-
morfismo, subtipado y herencia.
a b s t r a c t c l a s s Animal {
abstract String talk ( ) ;
}
c l a s s Cat e x t e n d s Animal {
String talk () {
r e t u r n ”Meow ! ” ;
}
}
c l a s s Dog e x t e n d s Animal {
String talk () {
r e t u r n ”Woof ! ” ;
}
}
s t a t i c v o i d l e t s H e a r ( f i n a l Animal a ) {
println (a . talk ( ) ) ;
}
s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
l e t s H e a r ( new Cat ( ) ) ;
l e t s H e a r ( new Dog ( ) ) ;
}
5. [10 pt.] Si una componente de software usa una variable global en una guarda (como parte de una
condición con “if”), entonces no es independiente de contexto. Si, en cambio, una componente de
software modifica una variable global pero ésta no afecta a su ejecución, conserva la transparencia
referencial pero puede tener efectos secundarios.
Escriba dos programas en pseudocódigo, uno en el que se de el primer fenómeno (una variable global
que hace que un programa no sea independiente de contexto, es decir, que los resultados de su ejecución
no dependan solamente de sus parámetros de entrada), y otro en el que se de el segundo fenómeno
(un programa que tiene efectos secundarios a través de una variable global).
6. [10 pt.] De la misma forma que las variables globales, el pasaje de variables por referencia puede
producir efectos secundarios. Sin embargo, en componentes de software declarativas no hay efectos
secundarios, y no hay diferencia entre pasaje de parámetros por valor o por referencia. Explique cómo
se puede dar este fenómeno, y cuál es la restricción lingüı́stica que imponen los lenguajes que se
inscriben en el paradigma funcional para forzar esta propiedad en los programas que se escriben en
esos lenguajes, es decir, para forzar que el pasaje de parámetros no pueda ser una vı́a para propagar
efectos secundarios.
2
7. [10 pt.] Estos dos fragmentos de código tienen la misma semántica, pero en uno hay inversión de
control e inyección de dependencia, y en el otro no. Explique cuál es cuál. En el que tiene inversión de
control, identifique el hot spot donde se puede parametrizar la dependencia que se inyecta en lugar de
que esté incrustada en el código.
c l a s s EventLogWriter // E s c r i b i r a l l o g de e v e n t o s
{
p u b l i c v o i d Write ( s t r i n g message ) { }
}
c l a s s AppPoolWatcher
{
// Handle para e l e s c r i t o r de e v e n t o s
EventLogWriter w r i t e r = n u l l ;
c l a s s AppPoolWatcher
{
// Handle para e l e s c r i t o r de e v e n t o s
INofificationAction action = null ;
c l a s s EventLogWriter : I N o f i f i c a t i o n A c t i o n // E s c r i b i r a l l o g de e v e n t o s
{
p u b l i c v o i d A c t O n N o t i f i c a t i o n ( s t r i n g message ) { }
}
3
8. [10 pt.] Dada la siguiente base de conocimiento en Prolog, liste los subobjetivos que hay que satisfacer
para verificar si el predicado tio(Pedro,Juan). es cierto o no.
hermano ( Pedro , J u l i a ) .
hermana ( Ana , T e re s a ) .
hermano ( Pedro , Esteban ) .
madre ( Ana , Juan ) .
padre ( Esteban , Juan ) .
9. [10 pt.] En el siguiente programa en Erlang, identifique los fragmentos de programa con semántica
concurrente y explique cuál es su semántica y por qué es concurrente, es decir, qué semántica expresan
que no está incluı́da en la expresividad de las máquinas de Turing.
max(N) −>
Max = e r l a n g : s y s t e m i n f o ( p r o c e s s l i m i t ) ,
i o : format ( ”Maximum a l l o w e d p r o c e s s e s : ˜ p˜n” , [ Max ] ) ,
s t a t i s t i c s ( runtime ) ,
statistics ( wall clock ) ,
U1 = Time1 ∗ 1000 / N,
U2 = Time2 ∗ 1000 / N,
i o : format ( ” P r o c e s s spawn time=˜p ( ˜ p ) m i c r o s e c o n d s ˜n” , [ U1 , U2 ] ) .
w a i t ( ) −>
receive
d i e −> v o i d
end .
f o r (N, N, F) −> [ F ( ) ] ;
f o r ( I , N, F) −> [ F ( ) | f o r ( I +1, N, F ) ] .
s t a r t ()−>
max ( 1 0 0 0 ) ,
max ( 1 0 0 0 0 0 ) .
10. [10 pt.] En el siguiente programa en AWK, identifique por lo menos dos caracterı́sticas propias de los
lenguajes de scripting y justifı́quelas con porciones del código.
i f ( ( x=i n d e x ( $1 , ”@” ) ) > 0 ) {
username = s u b s t r ( $1 , 1 , x −1);
hostname = s u b s t r ( $1 , x+1, l e n g t h ( $1 ) ) ;
p r i n t f ( ” username = %s , hostname = %s \n ” , username , hostname ) ;
}
4
Ejercicios para libres
1. [-10 pt.] El siguiente código (resumido) de un driver SCSI para Linux es spaghetti. ¿Por qué decimos
que es spaghetti, cuál es la principal diferencia con el código estructurado? ¿Si el lenguaje obliga a
usar código estructurado, qué estructura de datos puede usar el compilador para manejar la memoria
de forma más eficiente? Reescriba el driver en pseudocódigo para que sea estructurado y no spaghetti.
wait nomsg :
i f ( ( i n b ( tmport ) & 0 x04 ) != 0 ) {
g o t o wait nomsg ;
}
...
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x80 ) != 0 ) {
goto w a i t i o ;
}
}
g o t o TCM SYNC;
wait io :
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x81 ) == 0 x0081 ) {
goto w a i t i o 1 ;
}
}
g o t o TCM SYNC;
wait io1 :
...
TCM SYNC:
...
2. [-20 pt.] En el siguiente código en C++, hay un problema con la herencia del miembro “meow”, que
viene dada por el tipo de herencia por defecto que tiene C++. Explique por qué C++ tiene herencia
no virtual, sin reescritura, por defecto, y cómo habrı́a que hacer para que este programa funcionara
sin problemas.
class Felino {
public :
v o i d meow ( ) = 0 ;
};
c l a s s Gato : p u b l i c F e l i n o {
public :
v o i d meow ( ) { s t d : : c o u t << ”miau\n ” ; }
};
5
6
Paradigmas de la Programación – Recuperatorio del Segundo Parcial
19 de Junio de 2018
Apellido y Nombre:
1. [20 pt.] En Perl tenemos las palabras clave my y local para definir diferentes alcances de las variables,
tal como se explica en el siguiente texto. Explique por qué el programa de ejemplo final imprime 1,
después 2 y después 1 de vuelta.
my marks a variable as private in a lexical scope, and local marks a variable as private in
a dynamic scope.
It’s easier to understand my, since that creates a local variable in the usual sense. There is
a new variable created and it’s accessible only within the enclosing lexical block, which is
usually marked by curly braces.
sub Foo {
my $x = s h i f t ;
print ” $x \n” ;
}
In that case, $x is private to the subroutine and its scope is enclosed by the curly braces. The
thing to note, and this is the contrast to local, is that the scope of a my variable is defined
with respect to your code as it is written in the file. It’s a compile-time phenomenon.
To understand local, you need to think in terms of the calling stack of your program as
it is running. When a variable is local, it is redefined from the point at which the local
statement executes for everything below that on the stack, until you return back up the stack
to the caller of the block containing the local.
Sabiendo esto, explique por qué el siguiente programa imprime 1, después 2 y después 1. Use diagramas
de los diferentes estados por los que pasa la pila de ejecución.
sub f o o { print ” $x \n” ; }
sub bar { l o c a l $x ; $x = 2 ; f o o ( ) ; }
$x = 1 ;
foo ( ) ; # p r i n t s ’1 ’
bar ( ) ; # p r i n t s ’ 2 ’
foo ( ) ; # p r i n t s ’1 ’
2. [10 pt.] Dada la siguiente base de conocimiento en Prolog, liste los subobjetivos que hay que satisfacer
para verificar si el predicado not(obsesionado(mario)). es cierto.
ama( mario , maria ) .
ama( juan , maria ) .
ama( maria , juan ) .
c o n s e r v a d o r ( mario ) .
o b s e s i o n a d o (X) :− ama(X,Y) , not (ama(Y,X) ) , p o s e s i v o (X ) .
p o s e s i v o (X) :− c o n s e r v a d o r (X ) .
1
3. [20 pt.] En el siguiente fragmento de C++, diga qué se imprime si se pasan los parámetros tal como
están escritos y qué se imprime si modificamos la declaración con p(int &y, int &z) {...}. Explique
por qué en componentes declarativas no hay diferencia entre pasaje de parámetros por valor y pasaje
de parámetros por referencia.
#i n c l u d e < s t d i o . h >
i n t x=0;
void p( int , i n t ) ;
v o i d main ( ) {
int x = 1;
p(x , x ) ;
}
void p( i n t y , i n t z ){
x = x+1;
y = y+1;
z = z +1;
p r i n t f (” %d\n ” , x+y+z ) ;
}
4. [30 pt.] Explique por qué el siguiente programa es un ejemplo de programación defensiva, identifique
las lı́neas de código que implementan programación defensiva (10 pt.). Explique qué vulnerabilidad del
lenguaje se está protegiendo mediante estas acciones, y cómo otros lenguajes evitan este tipo de vul-
nerabilidad. Por último, escriba en pseudocódigo cómo se podrı́a tratar este problema con excepciones
(20 pt.).
s t a t i c v o i d CreateRandomPermutation ( i n t numbers [ ] , i n t nNumbers )
{
a s s e r t ( numbers != NULL ) ;
a s s e r t ( nNumbers >= 0 ) ;
5. [10 pt.] De los siguientes fragmentos de código, ¿cuál es declarativo? En el que no es declarativo ¿Qué
fenómenos podemos encontrar que no son declarativos?
procedure FACTORIAL i s
N: i n t e g e r ;
T : i n t e g e r := 1 ;
begin
put ( ” Input ” ) ; g e t (N ) ;
f o r K in 2 . . N loop
T:= T∗K;
end loop ;
put ( ” F a c t o r i a l ” ) ; put (N ) ;
put ( ” i s ” ) ; put (T ) ; n e w l i n e ;
end FACTORIAL;
2
f a c t o r i a l (0 ,1): −
!.
f a c t o r i a l (N1 , T2): −
N2 i s N1−1,
f a c t o r i a l (N2 , T1 ) ,
T2 i s N1∗T1 .
6. [10 pt.] En el siguiente fragmento, identifique en el código las porciones con semántica concurrente.
c l a s s Line
{
public void g e t L i n e ( )
{
f o r ( int i = 0 ; i < 3 ; i ++)
{
System . out . p r i n t l n ( i ) ;
try
{
Thread . s l e e p ( 4 0 0 ) ;
}
catch ( E x c e p t i o n e )
{
System . out . p r i n t l n ( e ) ;
}
}
}
}
c l a s s Train extends Thread
{
Line l i n e ;
Train ( L ine l i n e )
{
this . l i n e = l i n e ;
}
@Override
public void run ( )
{
l i n e . getLine ( ) ;
}
}
c l a s s GFG
{
public s t a t i c void main ( S t r i n g [ ] a r g s )
{
Line o b j = new Li ne ( ) ;
train1 . start ( ) ;
train2 . start ( ) ;
}
}
3
Paradigmas de la Programación – Recuperatorio del Primer Parcial
19 de Junio de 2018
Apellido y Nombre:
1. [20 pt.] Explique por qué se usa “X::f()” en el siguiente fragmento de código en C++.
class X {
public : v i r t u a l void f ( ) {
}
};
c l a s s Y : public X {
public : v i r t u a l void f ( ) {
}
};
c l a s s Z : public Y {
public : v i r t u a l void f ( ) {
X: : f ( ) ;
}
};
2. [20 pt.] Estos dos fragmentos de código tienen la misma semántica, pero en uno hay inversión de
control e inyección de dependencia, y en el otro no. Explique cuál es cuál. En el que tiene inversión de
control, identifique el hot spot donde se puede parametrizar la dependencia que se inyecta en lugar de
que esté incrustada en el código.
C l a s s Engine { . . . }
C l a s s Car{
p r i v a t e Engine e ;
Car ( ) {
e= new Engine ( ) ;
}
}
C l a s s Car{
p r i v a t e Engine e ;
Car ( Engine e ) {
this . e = e ;
}
}
1
3. [10 pt.] La siguiente expresión está mal tipada:
f (a , b) = a > b | | a
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
4. [20 pt.] En el siguiente programa, diagrame el estado en el que se encuentra la pila de ejecución al
terminar de ejecutar las lı́neas señaladas en el texto del programa.
a = 1
b = 2
c = 3
function test ()
local function g ()
local a = 2
c = a + 4 −−−−−−
end
local function f ()
local c = 5
b = 4 −−−−−−
g ()
end
−−−−−−
f ()
end
−−−−−−
test ()
5. [10 pt.] Lea el siguiente texto y explique con sus propias palabras el nivel de visibilidad package en
Java, y relaciónelo con los niveles de visibilidad private y protected.
Access level modifiers determine whether other classes can use a particular field or invoke a
particular method. There are two levels of access control:
At the top level—public, or package-private (no explicit modifier).
At the member level—public, private, protected, or package-private (no explicit modifier).
A class may be declared with the modifier public, in which case that class is visible to all
classes everywhere. If a class has no modifier (the default, also known as package-private),
it is visible only within its own package (packages are named groups of related classes).
At the member level, you can also use the public modifier or no modifier (package-private)
just as with top-level classes, and with the same meaning. For members, there are two addi-
tional access modifiers: private and protected. The private modifier specifies that the member
can only be accessed in its own class. The protected modifier specifies that the member can
only be accessed within its own package (as with package-private) and, in addition, by a
subclass of its class in another package.
2
6. [20 pt.] De estos dos fragmentos de códigos, cuál es un ejemplo de polimorfismo y cuál es un ejemplo
de sobrecarga? Justifique su respuesta.
public c l a s s Animal {
public void sound ( ) {
System . out . p r i n t l n ( ” Animal i s making a sound ” ) ;
}
}
c l a s s Examp
{
void demo ( int a )
{
System . out . p r i n t l n ( ” a : ” + a ) ;
}
void demo ( int a , int b )
{
System . out . p r i n t l n ( ” a and b : ” + a + ” , ” + b ) ;
}
double demo ( double a ) {
System . out . p r i n t l n ( ” d o u b l e a : ” + a ) ;
return a∗ a ;
}
}
c l a s s MethodExamping
{
public s t a t i c void main ( S t r i n g a r g s [ ] )
{
Examp Obj = new Examp ( ) ;
double r e s u l t ;
Obj . demo ( 1 0 ) ;
Obj . demo ( 1 0 , 2 0 ) ;
r e s u l t = Obj . demo ( 5 . 5 ) ;
System . out . p r i n t l n ( ”O/P : ” + r e s u l t ) ;
}
}
3
Paradigmas de la Programación – Segundo Parcial
14 de Junio de 2018
Apellido y Nombre:
1. [10 pt.] En el siguiente fragmento de código, diga qué se imprime si el pasaje de parámetros es por
valor, por referencia o por valor-resultado.
z : integer ;
procedure p (x : i n t e g e r )
begin
x := x+1 ;
z := z +2;
end
z := 1 ;
p( z ) ;
write ( z )
2. [10 pt.] Dada la siguiente base de conocimiento en Prolog, liste los subobjetivos que hay que satisfacer
para verificar que el predicado ama(amanda,amalia). es cierto.
amable ( a m a l i a ) .
amable ( amadeo ) .
amable ( amanda ) .
c o n o c e ( amalia , amanda ) .
c o n o c e ( amalia , amadeo ) .
3. [10 pt.] En el siguiente fragmento de código, indique qué escribirı́a la función p (en la instrucción
write(x)) en un lenguaje con alcance estático y un lenguaje con alcance dinámico.
procedure p ;
x : integer ;
procedure q ;
b e g i n x := x+1 end ;
procedure r ;
x : integer ;
b e g i n x := 1 ; q ; w r i t e ( x ) end ;
begin
x:= 2 ;
r
end ;
1
4. [10 pt.] El siguiente es un ejemplo de programación defensiva. Identifique la función en la que se
podrı́an insertar funcionalidades de canonicalización. Explique cómo se modificarı́a el manejo de ex-
cepciones si en lugar de usar programación defensiva usaramos programación ofensiva.
function calc () {
var a = form ( ) . a ;
var b = form ( ) . b ;
try {
r e t u r n add ( a , b ) ;
} catch ( err ) {
showErrorMessage ( e r r ) ;
}
}
5. [10 pt.] Para el siguiente código en Java, diagrame los sucesivos momentos por los que pasa la pila de
ejecución, y explique en qué momento hay algún objeto disponible para recolectar y cuál.
c l a s s Test
{
S t r i n g obj name ;
s t a t i c void show ( )
{
Test t 1 = new Test ( ” t 1 ” ) ;
display ( ) ;
}
s t a t i c void d i s p l a y ( )
{
Test t 2 = new Test ( ” t 2 ” ) ;
}
6. [10 pt.] En el siguiente código en un dialecto de Lisp, identifique propiedades de los lenguajes de
scripting de dominio especı́fico.
( defun my−split−window−func ( )
( interactive )
( split−window−vertically )
( set−window−buffer ( next−window ) ( o t h e r − b u f f e r ) ) )
( gl o b a l − s e t − ke y ” \C−x2” ’ my−split−window−func )
2
7. [10 pt.] De los siguientes fragmentos de React, ¿cuál es declarativo? En el que no es declarativo
¿Qué fenómenos podemos encontrar que no son declarativos? Vemos que uno de los fragmentos tiene
la palabra clave “map”. Explique, en función de su razonamiento anterior, qué tipo de programación
concurrente se desarrolla con map-reduce.
f u n c t i o n double ( arr ) {
r e t u r n a r r . map ( ( item ) => item ∗ 2 )
}
f u n c t i o n double ( arr ) {
let results = []
f o r ( l e t i = 0 ; i < a r r . l e n g t h ; i ++){
r e s u l t s . push ( a r r [ i ] ∗ 2 )
}
return r e s u l t s
}
8. [10 pt.] El siguiente código en Perl tiene manejo de excepciones. Identifique el operador que lanza las
excepciones (equivalente a raise o throw), el operador que delimita el alcance donde se puede lanzar
una excepción (equivalente a try) y explique cómo se manejan las excepciones en este programa, si
con un manejador o bien con alguna otra construcción lingüı́stica.
my ( $ e r r o r , $ f a l l o ) ;
{
l o c a l $@ ;
$ f a l l o = not eval {
open ( FILE , $ f i l e ) | | die ”No s e pudo a b r i r e l a r c h i v o : $ ! ” ;
while (<FILE>) {
process line ( $ );
}
c l o s e ( FILE ) | | die ”No s e pudo c e r r a r e l a r c h i v o : $ ! ” ;
return 1 ;
};
$ e r r o r = $@ ;
}
if ( $fallo ) {
warn ” encontramos e l e r r o r : $ e r r o r ” ;
}
9. [10 pt.] Lea esta descripción sobre el lenguaje de programación Elixir y argumente para qué tipo
de proyecto serı́a adecuado y para qué tipo de proyecto no serı́a necesario. Argumente su posición
basándose en las caracterı́sticas del lenguaje que se mencionan en este texto.
Elixir is a general-purpose, functional language designed for building scalable and maintai-
nable applications.
The language complies with the bytecode seen on the Erlang VM (also known as BEAM).
Its syntax is often compared to the ever popular Ruby on Rails development framework.
Elixir code runs inside lightweight, isolated processes, which allows for thousands of processes
to run concurrently in the same machine. This in turn allows for vertical scaling and uses
all of a machine’s resources as efficiently as possible.
These processes are also able to communicate with other processes running on different
machines in the same network, providing a solid foundation for distribution and allowing
for horizontal scaling.
3
Running into issues with running software is inevitable, but Elixir’s fault-tolerant system
can make the process a little less painful. It provides ‘supervisors’ that you can program with
descriptions of how to restart certain parts of a system when things fail.
These parts will then revert to a ‘known, initial state’, which is guaranteed to work.
10. [10 pt.] El siguiente fragmento de código en Scala, identifique en el código por lo menos dos carac-
terı́sticas propias de la programación orientada a actores y explı́quelas. Explique, además, para qué
servirı́a la lı́nea “import context” y su relación con la programación declarativa o imperativa, según
lo crea adecuado.
c l a s s C h o p s t i c k e x t e n d s Actor {
v a l l o g = Logging ( c o n t e x t . system , t h i s )
import c o n t e x t .
//A C h o p s t i c k b e g i n s i t s e x i s t e n c e a s a v a i l a b l e
def receive = available
}
4
Paradigmas de la Programación – Primer Parcial
19 de Abril de 2018
Apellido y Nombre:
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
2. [15 pt.] El siguiente código (resumido) de un driver SCSI para Linux es spaghetti. ¿Por qué decimos
que es spaghetti, cuál es la principal diferencia con el código estructurado? ¿Si el lenguaje obliga a
usar código estructurado, qué estructura de datos puede usar el compilador para manejar la memoria
de forma más eficiente? Reescriba el driver en pseudocódigo para que sea estructurado y no spaghetti.
wait nomsg :
i f ( ( i n b ( tmport ) & 0 x04 ) != 0 ) {
g o t o wait nomsg ;
}
...
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x80 ) != 0 ) {
goto w a i t i o ;
}
}
g o t o TCM SYNC;
wait io :
f o r ( n = 0 ; n < 0 x30000 ; n++) {
i f ( ( i n b ( tmport ) & 0 x81 ) == 0 x0081 ) {
goto w a i t i o 1 ;
}
}
g o t o TCM SYNC;
wait io1 :
...
TCM SYNC:
...
1
3. [15 pt.] En el siguiente programa en Lua, diagrame el estado en el que se encuentra la pila de ejecución
al terminar de ejecutar las lı́neas señaladas con A, B, C y D en el texto del programa1 .
a = 1
b = 2
c = 3
function test ()
local function g ()
local a = 2
c = a + 4 −−−> D
end
local function f ()
local c = 5
b = 4 −−−> C
g ()
end
−−−> B
f ()
end
−−−> A
test ()
4. [10 pt.] De estos dos fragmentos de códigos, el primero en Pascal y el segundo en C++, cuál es un
ejemplo de sobrecarga y cuál es un ejemplo de polimorfismo? Justifique su respuesta.
f u n c t i o n Add( x , y : I n t e g e r ) : I n t e g e r ;
begin
Add := x + y
end ;
f u n c t i o n Add( s , t : S t r i n g ) : S t r i n g ;
begin
Add := Concat ( s , t )
end ;
c l a s s L i s t <T> {
c l a s s Node<T> {
T elem ;
Node<T> next ;
}
Node<T> head ;
int length () { . . . }
}
5. [15 pt.] El siguiente fragmento de código funciona porque se aplica un tipo de polimorfismo que
recurre al subtipado que se articula a través de la herencia. En este lenguaje, el subtipado permite que
una función se defina para tomar objetos de tipo T pero la función también funciona correctamente si
se le pasan objetos de tipo S, siempre que S sea subtipo de T . Esto es ası́ porque se aplica el principio
de Liskov, enunciado por Bárbara Liskov y Jeannette Wing de la siguiente forma:
1
Para ser más precisos, al terminar de ejecutar el equivalente en código máquina a esas lı́neas del código en Lua.
2
Let φ(x) be a property provable about objects x of type T . Then φ(y) should be true for objects
y of type S where S is a subtype of T .
Explique con sus propias palabras por qué funciona este código, recurriendo a los conceptos de poli-
morfismo, subtipado y herencia.
a b s t r a c t c l a s s Animal {
abstract String talk ( ) ;
}
c l a s s Cat e x t e n d s Animal {
String talk () {
r e t u r n ”Meow ! ” ;
}
}
c l a s s Dog e x t e n d s Animal {
String talk () {
r e t u r n ”Woof ! ” ;
}
}
s t a t i c v o i d l e t s H e a r ( f i n a l Animal a ) {
println (a . talk ( ) ) ;
}
s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
l e t s H e a r ( new Cat ( ) ) ;
l e t s H e a r ( new Dog ( ) ) ;
}
6. [15 pt.] En C++, la vtable guarda las funciones definidas como virtuales, tal como se ve en el
siguiente gráfico:
Por defecto, las funciones de una clase no se definen como virtuales sino como estáticas. Teniendo
en cuenta que una de las principales decisiones de diseño de C++ es la eficiencia, explique por qué
se evita tanto como sea posible que las funciones en C++ se definan como virtuales, explicando qué
involucra procesar una función virtual, y recurriendo a los conceptos de overhead y flexibilidad.
7. [10 pt.] Estos dos fragmentos de código tienen la misma semántica, pero en uno hay inversión de
control e inyección de dependencia, y en el otro no. Explique cuál es cuál. En el que tiene inversión de
control, identifique el hot spot donde se puede parametrizar la dependencia que se inyecta en lugar de
que esté incrustada en el código.
3
p u b l i c c l a s s EditorDeTexto {
p u b l i c EditorDeTexto ( C o r r e c t o r O r t o g r a f i c o c o r r e c t o r ) {
this . corrector = corrector ;
}
}
C o r r e c t o r O r t o g r a f i c o co = new C o r r e c t o r O r t o g r a f i c o ;
EditorDeTexto t e x t E d i t o r = new EditorDeTexto ( co ) ;
p u b l i c c l a s s EditorDeTexto {
p u b l i c EditorDeTexto ( ) {
t h i s . c o r r e c t o r = new C o r r e c t o r O r t o g r a f i c o ( ) ;
}
}
8. [10 pt.] Lea el siguiente texto y explique con sus propias palabras el nivel de visibilidad friend en
C++, y relaciónelo con los niveles de visibilidad private y protected. Explique cómo los niveles de
visibilidad se relacionan con el encapsulamiento y con la separación interfaz – implementación, y cuál
es su utilidad en el proceso de desarrollo del software.
In principle, private and protected members of a class cannot be accessed from outside the
same class in which they are declared. However, this rule does not apply to ”friends”.
Friends are functions or classes declared with the friend keyword.
A non-member function can access the private and protected members of a class if it is
declared a friend of that class. That is done by including a declaration of this external function
within the class, and preceding it with the keyword friend.
4
Paradigmas de la Programación – Examen Final
7 de Julio de 2017
Apellido y Nombre:
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a resolver este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
2. [20 pt.] Diagrame los sucesivos estados por los que pasa la pila de ejecución al ejecutar los siguientes
tres programas en ML:
e x c e p t i o n Excpt o f i n t ;
fun t w i c e ( f , x ) = f ( f ( x ) ) h a n d l e Excpt ( x ) => x ;
fun pred ( x ) = i f x = 0 then r a i s e Excpt ( x ) e l s e x −1;
fun dumb( x ) = r a i s e Excpt ( x ) ;
fun smart ( x ) = 1 + pred ( x ) h a n d l e Excpt ( x ) => 1 ;
t w i c e ( pred , 1 ) ;
e x c e p t i o n Excpt o f i n t ;
fun t w i c e ( f , x ) = f ( f ( x ) ) h a n d l e Excpt ( x ) => x ;
fun pred ( x ) = i f x = 0 then r a i s e Excpt ( x ) e l s e x −1;
fun dumb( x ) = r a i s e Excpt ( x ) ;
fun smart ( x ) = 1 + pred ( x ) h a n d l e Excpt ( x ) => 1 ;
t w i c e (dumb , 1 ) ;
e x c e p t i o n Excpt o f i n t ;
fun t w i c e ( f , x ) = f ( f ( x ) ) h a n d l e Excpt ( x ) => x ;
fun pred ( x ) = i f x = 0 then r a i s e Excpt ( x ) e l s e x −1;
fun dumb( x ) = r a i s e Excpt ( x ) ;
fun smart ( x ) = 1 + pred ( x ) h a n d l e Excpt ( x ) => 1 ;
t w i c e ( smart , 1 ) ;
3. [10 pt.] Si el resultado del siguiente programa es 19, qué tipo de alcance tiene el lenguaje de progra-
mación en el que está escrito, estático o dinámico?
val x = 4;
fun f ( y ) = x∗y ;
fun g ( x ) = l e t
f (3) + x ;
g(7);
4. [10 pt.] Para solucionar los name clashes, algunos lenguajes implementan heurı́sticas para decidir
qué implementación seleccionar en el caso de un name clash. Explique qué estrategia utiliza Java para
evitar este tipo de problema y describa por lo menos otra estrategia posible.
1
5. [10 pt.] El siguiente es un ejemplo de un operador que busca aumentar la seguridad de un lenguaje.
Explique qué aporta este operador en términos de seguridad, y qué otros mecanismos se pueden
implementar en lenguajes inseguros para aumentar su seguridad.
Kotlin makes a distinction between nullable and non-nullable datatypes. All nullable objects
must be declared with a ¿”postfix after the type name. Operations on nullable objects need
special care from developers: null-check must be performed before using the value. Kotlin
provides null-safe operators to help developers:
?. (safe navigation operator) can be used to safely access a method or property of a possibly null
object. If the object is null, the method will not be called and the expression evaluates to null.
?: (null coalescing operator) often referred to as the Elvis operator:
fun s a y H e l l o ( maybe : S t r i n g ? , n e v e r N u l l : I n t ) {
// u s e o f e l v i s o p e r a t o r
v a l name : S t r i n g = maybe ? : ” s t r a n g e r ”
p r i n t l n ( ” H e l l o $name ” )
}
Un ejemplo de uso:
// r e t u r n s n u l l i f ( and o n l y i f ) f o o i s n u l l , o r bar ( ) r e t u r n s n u l l ,
// o r baz ( ) r e t u r n s n u l l
f o o ? . bar ( ) ? . baz ( )
6. [10 pt.] En el siguiente código en React, tenemos un ejemplo de programación declarativa? Relacione
este código y el framework React en general con el paradigma de actores y con otros frameworks.
import React , { PropTypes } from ’ r e a c t ’
Todo . propTypes = {
o n C l i c k : PropTypes . f u n c . i s R e q u i r e d ,
completed : PropTypes . b o o l . i s R e q u i r e d ,
t e x t : PropTypes . s t r i n g . i s R e q u i r e d
}
e x p o r t d e f a u l t Todo
7. [10 pt.] Cuando usamos frameworks se da una inversión de control. Explique brevemente en qué con-
siste la inversión de control y argumente cuál de los dos fragmentos de código que siguen es un ejemplo
de inversión de control.
class PaymentsController < A p p l i c a t i o n C o n t r o l l e r
def accept payment
i f R a i l s . env . development ? | | R a i l s . env . t e s t ?
2
@ c r e d i t c a r d v a l i d a t o r = BogusCardValidator . new
else
@ c r e d i t c a r d v a l i d a t o r = R e a l C a r d V a l i d a t o r . new
end
i f R a i l s . env . p r o d u c t i o n ?
@gateway = RealPaymentGateway . new
e l s i f R a i l s . env . s t a g i n g ?
@gateway = RealPaymentGateway . new ( u s e t e s t i n g u r l : true )
else
@gateway = BogusPaymentGateway . new
end
c a r d = @ c r e d i t c a r d v a l i d a t o r . v a l i d a t e ( params [ : c a r d ] )
@gateway . p r o c e s s ( c a r d )
end
end
# c o n f i g / dependencies / production . rb :
RailsENV : : D e p e n d e n c i e s . d e f i n e do
p r o t o t y p e : payment gateway , RealPaymentGateway
prototype : c r e d i t c a r d v a l i d a t o r , RealCardValidator
c o n t r o l l e r PaymentsController , {
gateway : r e f ( : payment gateway )
credit card validator : ref (: credit card validator )
}
end
# c o n f i g / dependencies / s t a g i n g . rb :
RailsENV : : D e p e n d e n c i e s . d e f i n e do
inherit environment ( : production )
p r o t o t y p e : payment gateway , RealPaymentGateway , { u s e t e s t i n g u r l : true }
end
# c o n f i g / dependencies / development . rb :
RailsENV : : D e p e n d e n c i e s . d e f i n e do
inherit environment ( : production )
s i n g l e t o n : payment gateway , BogusPaymentGateway
s i n g l e t o n : c r e d i t c a r d v a l i d a t o r , BogusCardValidator
end
8. [10 pt.] Elm es un lenguaje de programación que pretende sustituir Javascript. Es un lenguaje de
programación declarativo, sin embargo pretende usarse en los mismos contextos que un lenguaje de
programación de scripting tipo glue. La propuesta es circunscribir las partes no declarativas de los
programas fuera de las componentes puramente declarativas, construyendo programas dentro de la
llamada The Elm Architecture (TEA).
Comente el siguiente texto, comparando la funcionalidad de la TEA con la funcionalidad de las móna-
das en otros lenguajes funcionales (como Haskell), explique las propiedades de las componentes decla-
3
rativas y cómo estas propiedades producen limitaciones en los lenguajes de programación tipo glue, y
cómo estas limitaciones se pueden solventar mediante la TEA o las mónadas.
The simplest program consists of a model record storing all data that might be updated, a
union type Msg that defines ways your program updates that data, a function update which
takes the model and a Msg and returns a new model, and a function view which takes a
model and returns the HTML your page will display. Anytime a function returns a Msg,
The Elm Architecture uses it to update the page.
9. [10 pt.] Diga cuál serı́a el resultado de la siguiente función con alcance estático y con alcance dinámico,
y describa brevemente cómo se obtiene el resultado en cada uno de los casos.
var x=1;
f u n c t i o n g ( z ) { r e t u r n x+z ; }
function f (y) {
x = y+1;
r e t u r n g ( y∗x ) ;
}
f (3)
2. [-10 pt.] El siguiente código está dentro del modelo de actores. Señale en el código los mecanis-
mos propios de actores y sı́rvase de ellos para explicar cómo los actores implementan concurrencia
declarativa.
import akka . a c t o r .
4
context . stop ( s e l f )
} else {
s e n d e r ! PingMessage
}
}
}
c l a s s Pong e x t e n d s Actor {
def receive = {
c a s e PingMessage =>
p r i n t l n ( ” pong ” )
s e n d e r ! PongMessage
c a s e StopMessage =>
p r i n t l n ( ” pong s to p pe d ” )
context . stop ( s e l f )
}
}
o b j e c t PingPongTest e x t e n d s App {
v a l system = ActorSystem ( ” PingPongSystem ” )
v a l pong = system . a c t o r O f ( Props [ Pong ] , name = ” pong ” )
v a l p i n g = system . a c t o r O f ( Props ( new Ping ( pong ) ) , name = ” p i n g ” )
// s t a r t them g o i n g
ping ! StartMessage
}
3. [-10 pt.] Dada la siguiente base de conocimiento en Prolog, grafique o explique verbalmente cuál
serı́a la secuencia de predicados por los que se realizarı́a la búsqueda para verificar si es cierto
recomendar(pedro,clara).
amigo ( pedro , maria ) . amigo ( pedro , juan ) .
amigo ( maria , pedro ) . amigo ( maria , c l a r a ) .
amigo ( maria , romina ) . amigo ( juan , pedro ) .
amigo ( juan , c l a r a ) . amigo ( c l a r a , maria ) .
amigo ( c l a r a , juan ) . amigo ( romina , maria ) .
5
Paradigmas de la Programación – Recuperatorio del Segundo Parcial
15 de Junio de 2017
Apellido y Nombre:
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a abordar este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
2. [13 pt.] Ante lenguajes de programación inseguros podemos tomar estrategias de programación de-
fensiva o de programación ofensiva. De los dos códigos que encontramos abajo, explique cuál de los
dos estarı́a aplicando una estrategia ofensiva, cuál una estrategia defensiva. Explique qué tipo de vul-
nerabiliad encontramos en estos códigos, si vulnerabilidad en el sistema de tipos o vulnerabilidad de
memoria, y en qué consiste esta vulnerabilidad de forma genérica.
c o n s t c h a r ∗ t r a f f i c l i g h t c o l o r n a m e ( enum trafficlight color c) {
switch ( c ) {
c a s e TRAFFICLIGHT RED : return ” red ” ;
c a s e TRAFFICLIGHT YELLOW: r e t u r n ” yellow ”;
c a s e TRAFFICLIGHT GREEN : r e t u r n ” green ”;
}
return ” black ”;
}
3. [13 pt.] Los frameworks proveen al programador con un boilerplate. Explique qué es un boilerplate,
qué ventajas provee, qué desventajas en términos de overhead. En el código que sigue, explique por
qué este es un ejemplo de boilerplate y desarrolle (implemente) cómo el programador podrı́a usarlo
para instanciar un caso particular.
< !DOCTYPE html>
<html>
<head>
<t i t l e></ t i t l e>
</head>
<body> </body>
</html>
1
4. [13 pt.] El lenguaje D tiene diseño por contrato.
Contracts are assertions that must be true at specified points in a program. Contracts range
from simple asserts to class invariants, function entry preconditions, function exit postcon-
ditions, and how they are affected by polymorphism. Typical documentation for code is either
wrong, out of date, misleading, or absent. Contracts in many ways substitute for documen-
tation, and since they cannot be ignored and are verified automatically, they have to be kept
right and up to date.
Reescriba el siguiente ejemplo en pseudocódigo imperativo, sustituyendo el contrato por código sin
este tipo de abstracciones, de forma que se logre el mismo efecto que con el contrato en D.
byte ∗memcpy( byte ∗ to , byte ∗ from , u n s i g n e d n b y t e s )
in
{
a s s e r t ( t o + n b y t e s < from | | from + n b y t e s < t o ) ;
}
out ( r e s u l t )
{
a s s e r t ( r e s u l t == t o ) ;
f o r ( u n s i g n e d u = 0 ; u < n b y t e s ; u++)
a s s e r t ( t o [ u ] == from [ u ] ) ;
}
body
{
w h i l e ( nbytes −−)
t o [ n b y t e s ] = from [ n b y t e s ] ;
return to ;
}
5. [5 pt.] El siguiente código está dentro del modelo de actores. Señale en el código los mecanismos propios
de actores y sı́rvase de ellos para explicar cómo los actores implementan concurrencia declarativa.
import akka . a c t o r .
2
}
}
}
c l a s s Pong e x t e n d s Actor {
def receive = {
c a s e PingMessage =>
p r i n t l n ( ” pong ” )
s e n d e r ! PongMessage
c a s e StopMessage =>
p r i n t l n ( ” pong s to p pe d ” )
context . stop ( s e l f )
}
}
o b j e c t PingPongTest e x t e n d s App {
v a l system = ActorSystem ( ” PingPongSystem ” )
v a l pong = system . a c t o r O f ( Props [ Pong ] , name = ” pong ” )
v a l p i n g = system . a c t o r O f ( Props ( new Ping ( pong ) ) , name = ” p i n g ” )
// s t a r t them g o i n g
ping ! StartMessage
}
6. [13 pt.] En el siguiente lenguaje de programación, la palabra “local” se usa para declarar variables
en un alcance, y el operador “←” se usa para ligar la variable de la derecha a la de la izquierda,
de forma que el valor de la variable de la izquierda será una referencia a la de la derecha. Todas las
variables se representan en la pila como punteros a una estructura de datos en el heap.
Diagrame los diferentes estados por los que pasa la pila de ejecución y muestre en qué momento se
puede recolectar cada variable. Señale también si hay casos de variables que podrı́an ser recolectadas
antes de que queden sintácticamente disponibles para que el recolector de basura las recolecte.
{ local bli
l o c a l bla
{ local ble
l o c a l blu
l o c a l bla
b l a <− b l e
{ l o c a l blo
b l e <− b l o
}
bli = 3
}
}
7. [13 pt.] Dada la siguiente base de conocimiento en Prolog, grafique o explique verbalmente cuál
serı́a la secuencia de predicados por los que se realizarı́a la búsqueda para verificar si es cierto
(valido(lidia,smith,(3,10,10)))..
turno ( c e l i a , rivas , ( 6 , 3 0 , 8 ) ) .
turno ( c e l i a , z i l v e t t i , ( 7 , 1 4 , 1 1 ) ) .
t u r n o ( tomas , r i v a s , ( 7 , 1 1 , 1 0 ) ) .
t u r n o ( tomas , p e r e z , ( 8 , 1 1 , 1 0 ) ) .
t u r n o ( tomas , s c h u s t e r , ( 9 , 1 1 , 1 0 ) ) .
turno ( l i d i a , z i l v e t t i , ( 7 , 1 4 , 1 0 ) ) .
3
turno ( l i d i a , schuster , ( 9 , 1 1 , 1 1 ) ) .
turno ( esteban , rivas , ( 7 , 1 , 9 ) ) .
8. [13 pt.] Comente el siguiente texto en el que se compara subtipado e interfaces, y explique por qué un
la herencia y el subtipado son distintos y qué ventajas o desventajas puede tener separarlos o usarlos
juntos.
Inheritance is about gaining attributes (and/or functionality) of super types. For example:
c l a s s Base {
// i n t e r f a c e w i t h i n c l u d e d d e f i n i t i o n s
c l a s s D e r i v e d i n h e r i t s Base {
//Add some a d d i t i o n a l f u n c t i o n a l i t y .
// Reuse Base w i t h o u t h a v i n g t o e x p l i c i t l y f o r w a r d
// t h e f u n c t i o n s i n Base
}
Here, a Derived cannot be used where a Base is expected, but is able to act similarly to a
Base, while adding behaviour or changing some aspect of Bases behaviour. Typically, Base
would be a small helper class that provides both an interface and an implementation for some
commonly desired functionality.
Subtype-polymorphism is about implementing an interface, and so being able to substitute
different implementations of that interface at run-time:
class Interface {
// some a b s t r a c t i n t e r f a c e , no d e f i n i t i o n s i n c l u d e d
}
c l a s s Implementation implements I n t e r f a c e {
// p r o v i d e a l l t h e o p e r a t i o n s
// r e q u i r e d by t h e i n t e r f a c e
}
Here, an Implementation can be used wherever an Interface is required, and different imple-
mentations can be substituted at run-time. The purpose is to allow code that uses Interface
to be more widely useful.
Your confusion is justified. Java, C#, and C++ all conflate these two ideas into a single
class hierarchy. However, the two concepts are not identical, and there do exist languages
which separate the two.
4
9. [5 pt.] Ordene los siguientes fragmentos de código de más de scripting a menos de scripting, siempre
justificando qué decisiones de diseño se evidencian en cada uno de estos códigos que los caracterizan
como instancias de lenguajes del paradigma de scripting.
proc fo r { initCmd t e s t E x p r advanceCmd b o d y S c r i p t } {
uplevel 1 $initCmd
set testCmd [ l i s t expr $ t e s t E x p r ]
while { [ uplevel 1 $testCmd ] } {
uplevel 1 $ b o d y S c r i p t
uplevel 1 $advanceCmd
}
}
use s t r i c t ;
use w a r nin g s ;
use IO : : Handle ;
my ( $remaining , $ t o t a l ) ;
$ r e m a i n i n g = $ t o t a l = s h i f t (@ARGV) ;
STDOUT−>a u t o f l u s h ( 1 ) ;
while ( $ r e m a i n i n g ) {
p r i n t f ( ” Remaining %s/ %s \ r ” , $remaining −−, $ t o t a l ) ;
sleep 1 ;
}
print ”\n” ;
public :
// C l a s s ( s t a t i c ) f u n c t i o n s
s t a t i c void ∗ c l a s s M e t h o d 1 ( ) ;
static r e t u r n t y p e classMethod2 ( ) ;
s t a t i c r e t u r n t y p e c l a s s M e t h o d 3 ( param1 type param1 varName ) ;
// I n s t a n c e ( member ) f u n c t i o n s
r e t u r n t y p e instanceMethod1With1Parameter ( param1 type param1 varName ) ;
r e t u r n t y p e instanceMethod2With2Parameters ( param1 type param1 varName , param2 typ
};
f or j p g ; do
png=” ${ j p g %.j p g } . png”
echo c o n v e r t i n g ” $ j p g ” . . .
i f c o n v e r t ” $ j p g ” j p g . t o . png ; then
mv j p g . t o . png ” $png ”
else
echo ’ jpg2png : e r r o r : f a i l e d output saved i n ” j p g . t o . png” . ’ >&2
ex i t 1
fi
done
echo a l l c o n v e r s i o n s s u c c e s s f u l
ex it 0
5
Paradigmas de la Programación – Recuperatorio Primer Parcial
22 de Junio de 2017
Apellido y Nombre:
1. [15 pt.] Diagrame los sucesivos estados por los que pasa la pila de ejecución al ejecutar el siguiente
programa, teniendo en cuenta que el lenguaje tiene alcance estático.
Program A( )
{ x , y , z : integer ;
p r o c e d u r e B( )
{ y : integer ;
y=0;
x=z +1;
z=y+2;
}
p r o c e d u r e C( )
{ z : integer ;
p r o c e d u r e D( )
{ x : integer ;
x = z + 1;
y = x + 1;
c a l l B( ) ;
}
z = 5;
c a l l D( ) ;
}
x = 10;
y = 11;
z = 12;
c a l l C( ) ;
print x , y , z ;
}
2. [15 pt.] En el siguiente código en Java, explique qué sucede (cuáles son los cambios en los sucesivos
estados de la máquina, a nivel de pila de ejecución) cuando se produce una excepción aritmética o una
excepción de cualquier otro tipo.
class Clase {
public s t a t i c void main ( S t r i n g a r g s [ ] ) {
try {
System . out . p r i n t l n ( ” p r i m e r a s e n t e n c i a d e l b l o q u e t r y ” ) ;
∗∗∗CODIGO QUE LANZA UNA EXCEPCION ARITMETICA O DE OTRO TIPO∗ ∗ ∗ ;
System . out . p r i n t l n (num ) ;
}
1
catch ( A r i t h m e t i c E x c e p t i o n e ) {
System . out . p r i n t l n ( ” A r i t h m e t i c E x c e p t i o n ” ) ;
}
finally {
System . out . p r i n t l n ( ” b l o q u e f i n a l l y ” ) ;
}
System . out . p r i n t l n ( ” f u e r a d e l b l o q u e try −catch −f i n a l l y ” ) ;
}
}
3. [15 pt.] En Javascript no hay clases pero sı́ objetos. Vea cómo se instancian objetos en el siguiente
código de Javascript y explique cuál serı́a la función de la palabra clave constructor estableciendo
un paralelismo con lenguajes como C++ o Java.
Nota: Esta pregunta salió en el parcial correspondiente, ası́ que requiero mucha más precisión en las respuestas
para ser evaluadas positivamente.
f u n c t i o n User ( theName , theEmail ) {
t h i s . name = theName ;
t h i s . e m a i l = theEmail ;
this . currentScore = 0;
}
User . p r o t o t y p e = {
c o n s t r u c t o r : User ,
s a v e S c o r e : f u n c t i o n ( theScoreToAdd ) {
t h i s . q u i z S c o r e s . push ( theScoreToAdd )
},
changeEmail : f u n c t i o n ( newEmail ) {
t h i s . e m a i l = newEmail ;
r e t u r n ”New Email Saved : ” + t h i s . e m a i l ;
}
}
4. [15 pt.] Elm es un lenguaje de programación que pretende sustituir Javascript. Es un lenguaje de
programación declarativo, sin embargo pretende usarse en los mismos contextos que un lenguaje de
programación de scripting tipo glue. La propuesta es circunscribir las partes no declarativas de los
programas fuera de las componentes puramente declarativas, construyendo programas dentro de la
llamada The Elm Architecture (TEA). Compare la funcionalidad de la TEA con la funcionalidad de
las mónadas en otros lenguajes funcionales (como Haskell), explique las propiedades de las componentes
declarativas y cómo estas propiedades producen limitaciones en los lenguajes de programación, y cómo
estas limitaciones se pueden solventar mediante la TEA o las mónadas.
5. [10 pt.] En el siguiente código, ¿puedo asegurar si este lenguaje tiene pasaje de parámetros por call-
by-value, call-by-reference, call-by-value-resultx, call-by-need o call-by-name? ¿Qué tipos de pasaje por
parámetros voy a poder distinguir según el output?
begin
array a [ 1 . . 1 0 ] of i n t e g e r ;
integer n;
procedure p(b : i n t e g e r ) ;
begin
print (b ) ;
n := n+1;
print (b ) ;
end ;
2
a [ 1 ] := 1 0 ;
a [ 2 ] := 2 0 ;
a [ 3 ] := 3 0 ;
a [ 4 ] := 4 0 ;
n := 1 ;
p(a [ n+2]);
print (a );
end ;
6. [15 pt.] Explique el overhead que producen las abstracciones lingüı́sticas propias de los lenguajes
orientados a objetos. Explique qué diferencias hay entre lenguajes que por defecto resuelven estas
abstracciones en tiempo de compilación (como C++) y los que las resuelven en tiempo de ejecución
(como Smalltalk), en términos de flexibilidad, reemplazo en caliente, overhead en tiempo de ejecución
y complejidad del compilador.
7. [15 pt.] Explique por qué es importante tener un compilador con optimizaciones, y cómo eso hace una
diferencia entre lenguajes de programación. Explique en qué decisión de diseño impacta esa diferencia.
Describa la optimización de recursión a la cola y explique en qué puede ayudar esa optimización.
3
Paradigmas de la Programación – Segundo Parcial
15 de Junio de 2017
Apellido y Nombre:
Muestre con el árbol para inferencia de tipos dónde se encuentra el conflicto y en qué consiste. Cómo
podrı́a abordar este conflicto un lenguaje de tipado fuerte? y uno de tipado débil?
2. [11 pt.] Una de las áreas de vulnerabilidad principales de los lenguajes de scripting es justamente su
sistema de tipos. Explique por qué los sistemas de tipos de los lenguajes de scripting son una causa de
vulnerabilidades. Explique a qué decisión de diseño obedecen las caracterı́sticas propias de los sistemas
de tipos. Explique algunas estrategias de programación defensiva para proveer de seguridad algunos
fragmentos de scripts.
Contracts are assertions that must be true at specified points in a program. Contracts range
from simple asserts to class invariants, function entry preconditions, function exit postcon-
ditions, and how they are affected by polymorphism. Typical documentation for code is either
wrong, out of date, misleading, or absent. Contracts in many ways substitute for documen-
tation, and since they cannot be ignored and are verified automatically, they have to be kept
right and up to date.
Reescriba el siguiente ejemplo en pseudocódigo imperativo, sustituyendo el contrato por código sin
este tipo de abstracciones, de forma que se logre el mismo efecto que con el contrato en D.
byte ∗memcpy( byte ∗ to , byte ∗ from , u n s i g n e d n b y t e s )
in
{
a s s e r t ( t o + n b y t e s < from | | from + n b y t e s < t o ) ;
}
out ( r e s u l t )
{
a s s e r t ( r e s u l t == t o ) ;
f o r ( u n s i g n e d u = 0 ; u < n b y t e s ; u++)
a s s e r t ( t o [ u ] == from [ u ] ) ;
}
body
{
w h i l e ( nbytes −−)
t o [ n b y t e s ] = from [ n b y t e s ] ;
return to ;
}
1
4. [11 pt.] Cuando usamos frameworks se da una inversión de control. Explique brevemente en qué con-
siste la inversión de control y argumente cuál de los dos fragmentos de código que siguen es un ejemplo
de inversión de control.
class PaymentsController < A p p l i c a t i o n C o n t r o l l e r
def accept payment
i f R a i l s . env . development ? | | R a i l s . env . t e s t ?
@ c r e d i t c a r d v a l i d a t o r = BogusCardValidator . new
else
@ c r e d i t c a r d v a l i d a t o r = R e a l C a r d V a l i d a t o r . new
end
i f R a i l s . env . p r o d u c t i o n ?
@gateway = RealPaymentGateway . new
e l s i f R a i l s . env . s t a g i n g ?
@gateway = RealPaymentGateway . new ( u s e t e s t i n g u r l : true )
else
@gateway = BogusPaymentGateway . new
end
c a r d = @ c r e d i t c a r d v a l i d a t o r . v a l i d a t e ( params [ : c a r d ] )
@gateway . p r o c e s s ( c a r d )
end
end
# c o n f i g / dependencies / production . rb :
RailsENV : : D e p e n d e n c i e s . d e f i n e do
p r o t o t y p e : payment gateway , RealPaymentGateway
prototype : c r e d i t c a r d v a l i d a t o r , RealCardValidator
c o n t r o l l e r PaymentsController , {
gateway : r e f ( : payment gateway )
credit card validator : ref (: credit card validator )
}
end
# c o n f i g / dependencies / s t a g i n g . rb :
RailsENV : : D e p e n d e n c i e s . d e f i n e do
inherit environment ( : production )
p r o t o t y p e : payment gateway , RealPaymentGateway , { u s e t e s t i n g u r l : true }
end
# c o n f i g / dependencies / development . rb :
RailsENV : : D e p e n d e n c i e s . d e f i n e do
inherit environment ( : production )
s i n g l e t o n : payment gateway , BogusPaymentGateway
s i n g l e t o n : c r e d i t c a r d v a l i d a t o r , BogusCardValidator
end
2
5. [11 pt.] El siguiente código está dentro del modelo de actores. Señale en el código los mecanismos
propios de actores y sı́rvase de ellos para explicar cómo los actores implementan concurrencia decla-
rativa.
import akka . a c t o r .
c l a s s Pong e x t e n d s Actor {
def receive = {
c a s e PingMessage =>
p r i n t l n ( ” pong ” )
s e n d e r ! PongMessage
c a s e StopMessage =>
p r i n t l n ( ” pong s to p pe d ” )
context . stop ( s e l f )
}
}
o b j e c t PingPongTest e x t e n d s App {
v a l system = ActorSystem ( ” PingPongSystem ” )
v a l pong = system . a c t o r O f ( Props [ Pong ] , name = ” pong ” )
v a l p i n g = system . a c t o r O f ( Props ( new Ping ( pong ) ) , name = ” p i n g ” )
// s t a r t them g o i n g
ping ! StartMessage
}
6. [11 pt.] En el siguiente lenguaje de programación, la palabra “local” se usa para declarar variables
en un alcance, y el operador “→” se usa para ligar la variable de la derecha a la de la izquierda,
de forma que el valor de la variable de la izquierda será una referencia a la de la derecha. Todas las
variables se representan en la pila como punteros a una estructura de datos en el heap.
Diagrame los diferentes estados por los que pasa la pila de ejecución y muestre en qué momento se
3
puede recolectar cada variable. Señale también si hay casos de variables que podrı́an ser recolectadas
antes de que queden sintácticamente disponibles para que el recolector de basura las recolecte.
{ local bli
l o c a l bla
{ local ble
l o c a l blu
l o c a l bla
b l a <− b l e
{ l o c a l blo
b l e <− b l o
}
bli = 3
}
}
7. [11 pt.] Dada la siguiente base de conocimiento en Prolog, grafique o explique verbalmente cuál
serı́a la secuencia de predicados por los que se realizarı́a la búsqueda para verificar si es cierto
recomendar(pedro,clara).
amigo ( pedro , maria ) . amigo ( pedro , juan ) .
amigo ( maria , pedro ) . amigo ( maria , c l a r a ) .
amigo ( maria , romina ) . amigo ( juan , pedro ) .
amigo ( juan , c l a r a ) . amigo ( c l a r a , maria ) .
amigo ( c l a r a , juan ) . amigo ( romina , maria ) .
8. [11 pt.] En Go existen interfaces con caracterı́sticas semejantes a las de Java, pero con una diferencia:
With how Go interfaces work, you don’t need to declare an interface implementation. If you
implement the proper methods, you implement the interface.
Relacione esta propiedad con las polı́ticas de subtipado de diferentes lenguajes orientados a objetos,
que pueden estar implementadas en herencia o en inclusión entre interfaces.
9. [11 pt.] Ordene los siguientes fragmentos de código de más de scripting a menos de scripting, siempre
justificando por qué.
#! / b i n / b a s h
f or j p g ; do
png=” ${ j p g %.j p g } . png”
echo c o n v e r t i n g ” $ j p g ” . . .
i f c o n v e r t ” $ j p g ” j p g . t o . png ; then
mv j p g . t o . png ” $png ”
else
echo ’ jpg2png : e r r o r : f a i l e d output saved i n ” j p g . t o . png” . ’ >&2
ex i t 1
fi
done
echo a l l c o n v e r s i o n s s u c c e s s f u l
ex it 0
4
proc fo r { initCmd t e s t E x p r advanceCmd b o d y S c r i p t } {
uplevel 1 $initCmd
set testCmd [ l i s t expr $ t e s t E x p r ]
while { [ uplevel 1 $testCmd ] } {
uplevel 1 $ b o d y S c r i p t
uplevel 1 $advanceCmd
}
}
#! / u s r / b i n / p e r l
use s t r i c t ;
use w a r nin g s ;
use IO : : Handle ;
my ( $remaining , $ t o t a l ) ;
$ r e m a i n i n g = $ t o t a l = s h i f t (@ARGV) ;
STDOUT−>a u t o f l u s h ( 1 ) ;
while ( $ r e m a i n i n g ) {
p r i n t f ( ” Remaining %s/ %s \ r ” , $remaining −−, $ t o t a l ) ;
sleep 1 ;
}
print ”\n” ;
public :
// C l a s s ( s t a t i c ) f u n c t i o n s
s t a t i c void ∗ c l a s s M e t h o d 1 ( ) ;
static r e t u r n t y p e classMethod2 ( ) ;
s t a t i c r e t u r n t y p e c l a s s M e t h o d 3 ( param1 type param1 varName ) ;
// I n s t a n c e ( member ) f u n c t i o n s
r e t u r n t y p e instanceMethod1With1Parameter ( param1 type param1 varName ) ;
r e t u r n t y p e instanceMethod2With2Parameters ( param1 type param1 varName , param2 typ
};
5
Paradigmas de la Programación – Primer Parcial
20 de Abril de 2017
Apellido y Nombre:
1. [10 pt.] Muestre con un ejemplo que la siguiente gramática es ambigua, y modifı́quela para que se
convierta en una gramática inambigua.
<s> : : = <v> | <v> ’ pip ’ <v> | <v> ’ pop ’ <v>
<t> : : = ’ a ’ | ’ b ’ | ’ c ’ | ’ d ’
<v> : : = <v> <t> | <t>
2. [15 pt.] Diagrame los sucesivos estados por los que pasa la pila de ejecución al ejecutar el siguiente
programa, teniendo en cuenta que el lenguaje tiene alcance estático.
Program A( )
{ x , y , z : integer ;
p r o c e d u r e B( )
{ y : integer ;
y=0;
x=z +1;
z=y+2;
}
p r o c e d u r e C( )
{ z : integer ;
p r o c e d u r e D( )
{ x : integer ;
x = z + 1;
y = x + 1;
c a l l B( ) ;
}
z = 5;
c a l l D( ) ;
}
x = 10;
y = 11;
z = 12;
c a l l C( ) ;
print x , y , z ;
}
3. [10 pt.] En el programa del ejercicio anterior, ¿qué se imprimirá si el lenguaje tiene alcance estático?
¿Qué se imprimirá si el lenguaje tiene alcance dinámico?
1
4. [15 pt.] En el siguiente código en Java, explique qué sucede (cuáles son los cambios en los sucesivos
estados de la máquina, a nivel de pila de ejecución) cuando trato de abrir un archivo que se existe y
qué sucede cuando trato de abrir un archivo que no existe. Ayúdese de pilas de ejecución para ilustrar
su explicación cuando lo crea conveniente. No es necesario diagramar todos los estados de la ejecución
si no lo cree necesario, pero sı́ explicar detalladamente qué sucede en la ejecución del programa.
p u b l i c s t a t i c v o i d c a t ( F i l e named ) {
RandomAccessFile i n p u t = n u l l ;
String line = null ;
try {
i n p u t = new RandomAccessFile ( named , ” r ” ) ;
w h i l e ( ( l i n e = i n p u t . r e a d L i n e ( ) ) != n u l l ) {
System . out . p r i n t l n ( l i n e ) ;
}
return ;
} c a t c h ( FileNotFoundException f n f ) {
System . e r r . p r i n t l n ( ” F i l e : ” + named + ” not found . ” ) ;
} catch ( Exception e ) {
System . e r r . p r i n t l n ( e . t o S t r i n g ( ) ) ;
} finally {
i f ( i n p u t != n u l l ) {
try {
input . c l o s e ( ) ;
} c a t c h ( IOException i o ) {
}
}
}
}
5. [10 pt.] En Javascript no hay clases pero sı́ objetos. Vea cómo se instancian objetos en el siguiente
código de Javascript y explique cuál serı́a la función de la palabra clave constructor estableciendo
un paralelismo con lenguajes como C++ o Java.
f u n c t i o n User ( theName , theEmail ) {
t h i s . name = theName ;
t h i s . e m a i l = theEmail ;
this . currentScore = 0;
}
User . p r o t o t y p e = {
c o n s t r u c t o r : User ,
s a v e S c o r e : f u n c t i o n ( theScoreToAdd ) {
t h i s . q u i z S c o r e s . push ( theScoreToAdd )
},
changeEmail : f u n c t i o n ( newEmail ) {
t h i s . e m a i l = newEmail ;
r e t u r n ”New Email Saved : ” + t h i s . e m a i l ;
}
}
6. [10 pt.] Para solucionar los name clashes, algunos lenguajes implementan heurı́sticas para decidir
qué implementación seleccionar en el caso de un name clash. Explique qué estrategia utiliza Java para
evitar este tipo de problema.
2
7. [10 pt.] Quisimos hacer un programa que convierta un arreglo de números en una cifra, por ejemplo,
que convierta [3,8,5] en 385. Hicimos dos versiones del mismo, las que se ven acá abajo:
d e f toNum ( q : s c a l a . c o l l e c t i o n . Queue1 [ I n t ] ) = {
var num = 0
w h i l e ( ! q . isEmpty ) {
num ∗= 10
num += q . dequeue
}
num
}
d e f toNum ( q : s c a l a . c o l l e c t i o n . Queue2 [ I n t ] ) = {
d e f r e c u r s e ( qr : s c a l a . c o l l e c t i o n . Queue2 [ I n t ] , num : I n t ) : I n t = {
i f ( qr . isEmpty )
num
else {
v a l ( d i g i t , newQ) = qr . dequeue
r e c u r s e (newQ , num ∗ 10 + d i g i t )
}
}
r e c u r s e (q , 0)
}
Estas dos variantes, escritas en Scala, usan var y val, que son palabras claves para definir variables en
Scala, una para definir variables mutables y otra para definir variables inmutables (que no pueden ser
actualizadas) ¿Cuál de estas dos variantes tiene todas las propiedades de una componente declarativa,
y cuál tiene alguna propiedad no declarativa?
8. [10 pt.] En el siguiente código, ¿puedo asegurar si este lenguaje tiene pasaje de parámetros por call-
by-value, call-by-reference, call-by-value-resultx, call-by-need o call-by-name? ¿Qué tipos de pasaje por
parámetros voy a poder distinguir según el output?
begin
array a [ 1 . . 1 0 ] of i n t e g e r ;
integer n;
procedure p(b : i n t e g e r ) ;
begin
print (b ) ;
n := n+1;
print (b ) ;
end ;
a [ 1 ] := 1 0 ;
a [ 2 ] := 2 0 ;
a [ 3 ] := 3 0 ;
a [ 4 ] := 4 0 ;
n := 1 ;
p(a [ n+2]);
print (a );
end ;
9. [10 pt.] Las funciones virtuales de C++ se implementan mediante dynamic dispatch, es decir, selec-
cionando el código que se aplica en tiempo de ejecución. Partiendo de este hecho, explique por qué las
funciones virtuales de C++ suponen más overhead que las funciones estáticas, y qué ventaja aportan.
3
190