El paradigma de programación funcional
-
Upload
juan-pablo-pineda -
Category
Documents
-
view
31 -
download
2
Transcript of El paradigma de programación funcional
-
El paradigma de programacin funcional
Table of Contents
1 Bibliografa
2 Paradigmas de programacin
o 2.1 El Lisp y el paradigma funcional
3 Caractersticas declarativas del paradigma funcional
o 3.1 El Clculo Lambda
o 3.2 Modelo de sustitucin
o 3.3 Orden de evaluacin normal vs. aplicativo
4 Funciones como datos de primer orden
o 4.1 Forma especial lambda
o 4.2 Con una funcin creada podemos
o 4.3 Una funcin puede ser el argumento de otra funcin
o 4.4 Una funcin puede ser el valor devuelto por otra
o 4.5 Funciones de orden superior
o 4.6 Las funciones pueden formar parte de otras estructuras de datos
5 Closures
o 5.1 mbito global de variables
o 5.2 mbito local
o 5.3 Let mejora la legibilidad de los programas
o 5.4 El mbito de las variables definidas en el let es local
o 5.5 Let permite usar variables definidas en un mbito superior
o 5.6 Las variables del let pueden asociarse con cualquier valor
o 5.7 Variables en las asignaciones del let
o 5.8 Semntica del let
o 5.9 Es posible implementar let utilizando lambda
o 5.10 Se puede hacer una asignacin secuencial con let*
o 5.11 El mbito definido en el let puede sobrevivir
o 5.12 El mismo efecto sin let
o 5.13 Notas importantes
o 5.14 Closure
6 Dualidad entre datos y programas
o 6.1 Forma especial apply
o 6.2 Ejemplo de dualidad datos y programas calculadora
o 6.3 Ejemplo avanzado
7 Datos compuestos en Scheme
o 7.1 Datos compuestos en Scheme
o 7.2 Cmo dibujamos las parejas
o 7.3 Funciones de acceso a una pareja
o 7.4 Definicin declarativa
o 7.5 Nota histrica
o 7.6 Podemos hacer parejas con cualquier tipo de datos
o 7.7 Un ejemplo ms complicado
o 7.8 Seguimos en el paradigma funcional
o 7.9 Las parejas son un tipo de dato de primer orden
o 7.10 Propiedad de clausura: parejas de parejas
-
o 7.11 Ejemplos y figuras de parejas
o 7.12 Funciones c????r
o 7.13 Funcin pair?
o 7.14 Curiosidad: los datos compuestos pueden ser funciones
8 Listas
o 8.1 Repaso de listas
o 8.2 Construccin de listas con parejas
o 8.3 Car y cdr para recorrer listas
o 8.4 Cons para formar una nueva lista
o 8.5 Lista vaca
o 8.6 List devuelve la primera pareja de la lista
o 8.7 Listas con elementos compuestos
o 8.8 Listas de asociacin
o 8.9 Listas de listas
o 8.10 Implementacin de funciones sobre listas: append
o 8.11 Implementacin de length
o 8.12 Funciones que modifican listas
1 Bibliografa
El tema est basado en los siguientes materiales. Os recomendamos que los estudieis y,
si os interesa y os queda tiempo, que exploris tambin en los enlaces que hemos dejado
en los apuntes para ampliar informacin.
Abelson & Sussman
o Captulo 1.1.5: The Substitution Model
o Caputlo 1.3: Abstraction with Higher-Order Procedures
o Captulo 2.2: Hierarchical Data
Captulo 10 Scott: Functional Languages
2 Paradigmas de programacin
Cuando hablamos de paradigma de programacin nos referimos a un conjunto de
caractersticas comunes que definen una cierta estructura o forma de funcionar de un
lenguaje de programacin que usa ese paradigma. El paradigma viene definido por esas
caractersticas. A los lenguajes de programacin que utilizan esas caractersticas se les
pone el adjetivo del paradigma. As, por ejemplo, Java es un lenguaje imperativo y
orientado a objetos. Prolog es un lenguaje lgico. Lisp y Scheme son lenguajes con
caractersticas imperativas y funcionales.
Aunque en el tema pasado hablbamos de 4 paradigmas principales, la realidad es que
existen tantos paradigmas como posibles clasificaciones se hagan de los lenguajes de
programacin. Por ejemplo, una lista publicada en Wikipedia enumera ms de 20
paradigmas. Adems, como en cualquier sistema de clasificacin arbitrario, muchas
veces existen interpretaciones y ambiguedades en estas definiciones, y lo que algunos
entienden como una caractersticas fundamental de un paradigma, otros la consideran
lateral.
-
Los distintos paradigmas no siempre son exclusivos; hay caractersticas comunes a
varios paradigmas. Por ejemplo, el uso de las variables de forma declarativa (sin efectos
laterales) es comn a la programacin lgica y a la programacin funcional pura (no
imperativa). La programacin orientada a objetos es imperativa.
Tambin hay casos en los que esto no sucede y los paradigmas son disjuntos, o incluso
contrarios. Por ejemplo, el paradigma imperativo y el paradigma declarativo definen
caractersticas antagnicas; no es posible que un lenguaje sea declarativo e imperativo al
mismo tiempo.
Los lenguajes se construyen usando caractersticas de mltiples paradigmas. Por
ejemplo, el C o el Pascal permiten el uso de la recursin y son lenguajes imperativos. El
Fortran no usa la recursin. En este sentido, podramos decir que el C es ms funcional
que el Fortran.
Incluso existen lenguajes que han sido diseados con la idea de que contengan mltiples
paradigmas. Se denominan lenguajes multiparadigmas. Por ejemplo, Scala o Ruby.
Como dice Timothy Budd en The Leda Programming Language (1995):
The idea of a multiparadigm language is to provide a framework in
which programmers can work in a variety of styles, freely intermixing
constructs from different paradigms. The techniques supported by Leda
include imparative programming, the object-oriented approach, logic
programming, and functional programming.
En la asignatura veremos un ejemplo de este tipo de lenguajes: Scala.
Vamos a centrar este tema, y en general la primera parte de la asignatura, en uno de los
paradigmas ms importantes y menos conocidos de la programacin: el paradigma
funcional. Lo vamos a ver desde un punto de vista ms terico, hablando de su
componente declarativa, y desde un punto de vista prctico, estudiando las
caractersticas funcionales de Scheme.
Scheme es un lenguaje que hereda la mayor parte de caractersticas funcionales del
Lisp, el lenguaje ms importante del paradigma funcional.
2.1 El Lisp y el paradigma funcional
El origen del Lisp se remonta al ao 1956, en el que John McCarthy estaba buscando
una solucin para programar el computador IBM 704 en los primeros proyectos de
inteligencia artificial. A finales de 1958 McCarthy, ya profesor de Ingeniera
Electrnica y Marvin Minsky, profesor de matemticas, ambos en el MIT, comenzaron
el MIT Artificial Intelligence Project e iniciaron la implementacin del Lisp.
Entre los elementos que inspiraron el Lisp se encuentra el formalismo de programacin
funcional llamado clculo lambda (ver siguiente seccin).
Uno de los factores que contribuyeron ms al xito del Lisp es su carcter pragmtico.
No se trata de un lenguaje funcional puro (no es declarativo), sino que es posible
realizar sentencias imperativas en las que se modifican el valor de posiciones de
-
memoria a las que hacen referencia variables. Tambin sucede lo mismo en Scheme, y
lo veremos en la ms adelante en la asignatura.
El lenguaje de programacin Lisp ha dejado una huella profunda en los lenguajes de
programacin y en la historia de la informtica. El trmino programacin funcional se
utiliza normalmente para referirse a las caractersticas introducidas por el lenguaje.
Fueron caractersticas revolucionarias que no tenan ninguno de los lenguajes que
existan en la poca. Destastacamos las siguientes:
Uso de listas y de expresiones-S
Funciones como tipos de datos primitivos y funciones de orden superior
Polimorfismo
Smbolos y dualidad entre datos y cdigo
Recursin
Algunas de ellas van ms all de las caractersticas funcionales puras representadas por
el paradigma declarativo. En este sentido Lisp puede considerarse tambin pionero en
su carcter de lenguaje multiparadigma.
Vamos a estudiar algunos de estos conceptos en este tema. A la recursin le
dedicaremos un tema completo.
3 Caractersticas declarativas del paradigma funcional
El paradigma de programacin funcional pura comparte, junto con el de programacin
lgica, caractersticas de programacin declarativa.
La caracterstica fundamental del paradigma declarativo es que no existe la asignacin
ni el cambio de estado en un programa. Las variables son identificadores de valores que
no cambian en toda la evaluacin (como constantes definidas con un DEFINE de C).
Slo existen valores y expresiones matemticas que devuelven nuevos valores a partir
de los declarados.
En los lenguajes imperativos, sin embargo, se realizan asignaciones que cambian el
valor de una variable ya existente.
Consideremos el siguiente ejemplo:
1. { int x = 1;
2. x = x+1;
3. int y = x+1;
4. { int x = y;
5. y = x+2; }
6. y = x;}
En este fragmento de programa se mezclan instrucciones imperativas con instrucciones
declarativas. Por ejemplo, las instrucciones 1, 3 y 4 son declarativas, ya que estn
definiendo una variable con un valor asociado (estn dando un nombre a un valor). Sin
-
embargo, las sentencias 2, 5 y 6 son imperativas, ya que estn modificando el valor de
una variable mediante asignaciones de nuevos valores.
Otro elemento interesante del ejemplo es el mbito de alcance (scope en ingls) de las
declaraciones de las variables. Por ejemplo, la variable x declarada en la lnea 4 tiene un
mbito distinto de la declarada en la lnea 1. La x de la lnea 5 es la declarada en la lnea
4, mientras que la x de la lnea 6 es la declarada en la lnea 1.
Dentro de un mismo mbito podemos renombrar todas las ocurrencias de una variable
sin que el programa cambie. Por ejemplo, el siguiente programa es equivalente al
anterior.
1. { int x = 1;
2. x = x+1;
3. int y = x+1;
4. { int z = y;
5. y = z+2;}
6. y = x;}
La caracterstica pricipal de los programas declarativos es que dentro del mbito de
declaracin de las variables x1 xn todas las ocurrencias de una expresin e que contiene nicamente las variables x1 xn tienen el mismo valor. Esto es lo que tambin se denomina transparencia referencial.
Una consecuencia muy importante de esta propiedad es que en un paradigma
declarativo una funcin llamada con los mismos argumentos siempre devuelve el
mismo resultado.
Los lenguajes declarativos tienen una interesante propiedad de optimizacin: si una
expresin e aparece en varios lugares dentro de un mismo mbito, slo es necesario
evaluarla una vez. Veamos por ejemplo, el siguiente programa:
(define (f x)
...)
(+ (f 2) (f 2))
La expresin (f 2) se utiliza dos veces para sumar el resultado. En un paradigma
declarativo esta expresin no va a cambiar de valor y va a devolver siempre el mismo
valor. Por ello, es posible guardar el valor que devuelve en una nueva variable y slo
realizar la llamada una vez:
(define (f x)
...)
(define y (f 2))
(+ y y)
Hemos dicho que en los lenguajes declarativos las variables denotan nombres asociados
a valores. En los lenguajes imperativos, sin embargo, las variables denotan referencias a
valores que existen en algn lugar del ordenador (estado) y que pueden ser cambiados
-
con sucesivas instrucciones. Este es otro elemento que distingue los lenguajes
imperativos de los declarativos. En los lenguajes declarativos no existe ese concepto de
valor que reside en algn lugar del ordenador al que nos referimos mediante una
variable.
Por ejemplo, veamos el siguiente programa imperativo:
int a[3] = {1, 5, 7}
int *b = a;
b[0] = 3;
int c = a[0]+a[2];
En este programa, las variables a y b hacen referencia al mismo array (a la misma
posicin de memoria). De hecho, cuando en la tercera instruccin cambiamos el valor
de la primera componente de b, tambin estamos modificando los valores a los que se
refiere a, de forma que en la ltima instruccin c tomar el valor de 6. Al modificar b
hemos modificado a ya que ambos se refieren (apuntan, si utilizamos una terminologa
de C) al mismo valor. Esto se denomina un efecto lateral.
Un lenguaje declarativo no contiene referencias en el sentido del ejemplo anterior. Las
referencias son exclusivas de los lenguajes imperativos. El uso de referencias permite
que un mismo objeto (dato, valor) sea referenciado por ms de un identificador y
permite efectos laterales como el que hemos visto. Un lenguaje declarativo est libre de
efectos laterales.
3.1 El Clculo Lambda
La principal base terica del paradigma funcional es el Clculo Lambda (Lambda
Calculus en ingls) desarrollado en la dcada de los 30 por Alonzo Church como
modelo de computacin con el mismo poder computacional que una Mquina de
Turing.
El clculo lambda proporciona una notacin muy simple (pero bastante crptica al
mismo tiempo) de definicin de funciones matemticas. Slo son necesarias tres reglas
sintcticas para definir las expresiones del clculo lambda:
Expresin lambda para definir funciones sin nombre
Abstraccin para dar un nombre a una expresin lambda
Aplicacin para evaluar una expresin lambda
La aplicacin de una expresin-lambda a un argumento se consigue reemplazando en el
cuerpo de la expresin-lambda su variable ligada por el argumento.
3.2 Modelo de sustitucin
El modelo de sustitucin es el modelo computacional que explica la semntica de las
expresiones escritas en un lenguaje funcional puro (declarativo).
-
Un modelo computacional es un formalismo (conjunto de reglas) que definen el
funcionamiento de un programa. En el caso de Scheme, y de los lenguajes funcionales
basados en la evaluacin de expresiones, el modelo computacional define cul va a ser
el resultado de evaluar una determinada expresin.
El modelo de sustitucin se basa en una versin simplificada de la regla de reduccin
del clculo lambda.
Las reglas para evaluar una expresin e son las siguientes:
1. Si e es un valor primitivo, devolver ese mismo valor. 2. Si e es una variable, devolver su valor asociado. 3. Si e es una expresin del tipo (f arg1 argn), donde f el nombre de un
procedimiento primitivo (+, -, ), evaluar arg1 argn y aplicar el procedimiento al resultado.
4. Si e es una expresin del tipo (f arg1 argn), donde f es
el nombre de un procedimiento compuesto (definido con un define), sustituir f por su
cuerpo, reemplazando cada parmetro formal del procedimiento por el correspondiente
argumento evaluado. Evaluar la expresin resultante.
Para ilustrar estas regla, vamos a definir unas funciones en Scheme:
(define (double x) (+ x x))
(define (square y) (* y y))
(define (f z) (+ square (double z)) 1))
Y ahora vamos a ver qu ocurre cuando ejecutamos (f (+ 2 1)) utilizando el modelo
de sustitucin:
1. Evaluamos la expresin: f es un procedimiento. La expresin (+ 2 1) se evala a 3. Se evala =(f 3)
2. Ahora tomamos el cuerpo de f y sustituimos z por 3: (+ square (double 3)) 1)
3. Ahora evaluamos la expresin resultante: + denota el procedimiento suma, 1 se
evala a 1.
4. square y double don procedimientos compuestos. Aplicamos los
procedimientos. Empezamos por double sustituyendo el cuerpo de la funcin: (+ (square (+ 3 3)) 1)
5. (+ 3 3) > 6; (+ (square 6) 1)
6. Ahora se sustituye el cuerpo de square: (+ (* 6 6) 1)
7. (* 6 6) > (+ 36 1)
8. (+ 36 1) > 37
3.3 Orden de evaluacin normal vs. aplicativo
4 Funciones como datos de primer orden
Las funciones son datos de primer orden de los lenguajes funcionales.
-
Un dato de primer orden es aquel que (Abelson & Sussman):
Puede ser asignado a una variable
Puede ser pasado como argumento a una funcin
Puede ser devuelto como resultado de una invocacin a una funcin
Puede ser parte de un tipo mayor
4.1 Forma especial lambda
La forma especial lambda es la forma en Scheme de construir funciones sin
darles un nombre.
Igual que al escribir "hola" se crea una cadena (que antes no exista), al llamar a
lambda se crea una funcin en tiempo de ejecucin.
Ejemplo
(lambda (x) (* x x))
Sintaxis:
(lambda ( ... ) )
4.2 Con una funcin creada podemos
Invocarla:
((lambda (x) (* x x)) 3)
Darle un nombre:
(define cuadrado (lambda (x) (* x x)))
4.3 Una funcin puede ser el argumento de otra funcin
(define (aplica f x y)
(f x y))
(aplica + 2 3)
(aplica * 4 5)
(aplica string-append "hola" "adios")
(aplica (lambda (x y) (sqrt (+ (* x x) (* y y)))) 3 4)
Otro ejemplo:
(define (aplica-2 f g x)
(f (g x)))
(define (suma-5 x)
(+ x 5))
(define (doble x)
(+ x x))
(aplica-2 suma-5 doble 3)
4.4 Una funcin puede ser el valor devuelto por otra
-
(define (hacer-sumador-k k)
(lambda (x)
(+ x k)))
(define g (hacer-sumador-k 5))
(g 10)
4.5 Funciones de orden superior
Llamamos funciones de orden superior (higher order functions en ingls) a las
funciones que toman otras como parmetro o devuelven otra funcin
La funcin map de Scheme es un ejemplo
(map cuadrado '(1 2 3 4 5))
(map * '(1 2 3) '(4 5 6))
Otro ejemplo: operar
(define (operar f base lista)
(if (null? lista)
base
(f (car lista)
(operar f base (cdr lista)))))
4.6 Las funciones pueden formar parte de otras estructuras de datos
Por ejemplo, una lista de funciones
Veamos un ejemplo de cmo usarlo en aplica-funcs
(define (aplica-funcs lista-funcs x)
(if (null? (cdr lista-funcs))
((car lista-funcs) x)
((car lista-funcs)
(aplica-funcs (cdr lista-funcs) x))))
(define lista-funcs (list doble suma-5 cuadrado))
(aplica-funcs lista-funcs 10)
5 Closures
Las funciones como tipos de datos de primer orden se han introducido en otros
lenguajes de programacin modernos como Ruby o Scala. En esos lenguajes se ha
desarrollado y popularizado un concepto reciente, que curiosamente tiene su origen en
Scheme. Se trata del concepto de closure, que combina las funciones como datos
primitivos y el mbito local de las variables en el que se crea la funcin.
Vamos a introducir este concepto. Para ello necesitamos introducir previamente el
concepto de mbito local de variables en Scheme.
5.1 mbito global de variables
-
Ahora que hemos introducido la forma especial lambda y la idea de que una
funcin es un objeto primitivo, podemos revisar el concepto de mbito de
variables.
En Scheme existe un mbito global de las variables en el que se les da valor
utilizando la forma especial define.
(define a "hola")
(define b (string-append "adios" a))
(define cuadrado (lambda (x) (* x x)))
5.2 mbito local
En Scheme se define la forma especial let que permite crear un mbito local en
el que da valor a variables.
Sintaxis:
(let (( )
...
( ))
)
Las variables var1, varn toman los valores devueltos por las expresiones exp1, expn y la exp se evala con esos valores.
Ejemplos:
(let ((x 1)
(y 2))
(+ x y))
5.3 Let mejora la legibilidad de los programas
El uso de let permite abstraer operaciones y dar un nombre a los resultados,
aumentando la legibilidad de los programas:
(define (distancia x1 y1 x2 y2)
(let ((dx (- x2 x1))
(dy (- y2 y1)))
(sqrt (+ (cuadrado dx)
(cuadrado dy)))))
5.4 El mbito de las variables definidas en el let es local
Las variables definidas en el let slo tienen valores en el mbito de la forma especial.
(define x 5)
(let ((x 1)
-
(y 2))
(+ x y))
x
y
5.5 Let permite usar variables definidas en un mbito superior
Desde el mbito definido por el let se puede usar el mbito superior. Por ejemplo, en el
siguiente cdigo se usa la variable z definida en el mbito superior.
(define z 8)
(let ((x 1)
(y 2))
(+ x y z))
Podemos representar el comportamiento de esta expresin con la siguiente figura:
5.6 Las variables del let pueden asociarse con cualquier valor
Un ejemplo en el que definimos funciones de mbito local: area-triangulo y
apotema:
(define (area-hexagono lado)
(let ((area-triangulo (lambda (base altura)
(/ (* base altura) 2)))
(apotema (lambda (lado)
(* (sqrt 3) (/ lado 2))))))
(* 6 (area-triangulo lado (apotema lado))))
5.7 Variables en las asignaciones del let
Las expresiones del let se evalan todas antes de asociar ningn valor con las
variables.
No se realiza una asignacin secuencial
(define x 1)
(let ((w (+ x 3))
(z (+ w 2))) ;; Error
(+ w z))
5.8 Semntica del let
1. Evaluar todas las expresiones de la derecha de las variables y guardar sus valores en variables auxiliares locales.
2. Definir un mbito local en el que se ligan las variables del let con los valores de las variables auxiliares.
-
3. Evaluar el cuerpo del let en el mbito local
5.9 Es posible implementar let utilizando lambda
La semntica anterior queda clara cuando comprobamos que let se puede definir en
funcin de lambda. En general, la expresin:
(let (( ) ... ( )) )
se puede implementar con la siguiente llamada a lambda:
((lambda ( ... ) ) ... )
Ejemplos:
(define x 5)
(let ((x (+ 2 3))
(y (+ x 3)))
(+ x y))
;;
;; equivale a:
;;
((lambda (x y) (+ x y)) (+ 2 3) (+ x 3))
5.10 Se puede hacer una asignacin secuencial con let*
La forma especial let* permite una asignacin secuencial de las variables y las
expresiones:
(let* ((x (+ 1 2))
(y (+ x 3))
(z (+ y x)))
(* x y z))
Cmo se puede implementar con let?
(let ((x (+ 1 2)))
(let ((y (+ x 3)))
(let ((z (+ y x)))
(* x y z))))
-
El primer let crear un mbito local en el que se evala el segundo let, que a su vez
crea otro mbito local en el que se evala el tercer let. Se crean tres mbitos locales
anidados, y en el ltimo se evala la expresin (* x y z).
5.11 El mbito definido en el let puede sobrevivir
Un ejemplo avanzado del funcionamiento del let. Veamos el siguiente ejemplo, en el
que creamos una funcin en el mbito del let:
(define h (let ((x 3))
(lambda (y) (+ x y))))
Sucede lo siguiente:
1. Se invoca al let y se crea un entorno local en el que x vale 3 2. En ese entorno se crea una funcin de un parmetro que usa la variable x
3. Se devuelve la funcin y se asigna a h
Qu pasa cuando se llama a h? Y si modificamos el valor de x en el mbito global?
(h 5)
(define x 10)
(h 5)
Funcionamiento: cuando se llama a la funcin, el intrprete restaura el entorno que
estaba activo cuando la expresin lambda se evalu. Y lo aumenta con las variables de
los parmetros formales ligadas al valor del argumento. En este mbito se evala el
cuerpo de la funcin.
5.12 El mismo efecto sin let
En el ejemplo siguiente la llamda (make-sumador 5) produce exactamente el mismo
efecto que el let anterior.
(define (make-sumador x)
(lambda (y) (+ x y)))
(define h (make-sumador 5))
(define x 10)
(h 5)
5.13 Notas importantes
-
Seguimos estando en el paradigma de programacin funcional declarativa en el
que no hay efectos laterales.
El modelo de sustitucin no explica correctamente este comportamiento.
Veremos ms adelante el modelo de entornos que s lo hace.
Llamamos entorno (environment en ingls) a un conjunto de valores asociados a
variables. Utilizamos las palabras mbito y entorno como sinnimos.
5.14 Closure
En muchos lenguajes de programacin modernos existe el concepto de closure.
Lo que hemos visto es una closure: una funcin creada en tiempo de ejecucin
junto con el entorno en el que sta se ha creado.
Lo veremos en ms profundidad ms adelante.
6 Dualidad entre datos y programas
Los programas en Scheme son expresiones entre parntesis (expresiones-S o S-
expressions en ingls)
Una expresin es una lista de smbolos
Esto permite tratar a los programas como datos y viceversa utilizando la forma
especial eval
Un ejemplo: sumpongamos que queremos sumar una lista de nmeros
(define (suma-lista lista-nums)
(eval (cons '+ lista-nums)))
6.1 Forma especial apply
Permite llamar a una funcin con una lista de argumentos
Sintaxis:
(apply )
Ejemplos:
(apply + '(1 2 3 4))
(apply '+ '(1 2 3 4)) ; no funciona: '+ es un identificador, no
una funcin
(apply (lambda (x y) (* x y)) '(2 4))
6.2 Ejemplo de dualidad datos y programas calculadora
(define (calculadora)
(display "Introduce parmetros entre parntesis: ")
(define params (read))
-
(display "Introduce cuerpo")
(define cuerpo (read))
(define func (eval (append '(lambda) (list params) (list
cuerpo))))
(display "Introduce argumentos entre parntesis: ")
(define args (read))
(apply func args))
Nota: Este programa no es funcional; realiza pasos de ejecucin para leer las
expresiones introducidas por el usuario y para imprimir el resultado de su evaluacin.
6.3 Ejemplo avanzado
(define (rep-loop)
(display "mi-interprete> ") ; imprime un prompt
(let ((expr (read))) ; lee una expresin
(cond
((eq? expr 'adios) ; el usuario quiere parar?
(display "saliendo del bucle read-eval-print")
(newline))
(else ; en otro caso
(write (eval expr)) ; evaluar e imprimir
(newline)
(rep-loop)))))
7 Datos compuestos en Scheme
Otra de las caractersticas de Scheme, LISP y la programacin funcional es el uso de
listas. Las listas en LISP y Scheme se basan en una estructura de datos mucho ms
simple, la pareja. La pareja es la nica estructura de datos compuesta primitiva de
Scheme. De forma muy elegante, todas las demas estructuras compuestas se construyen
en base a ella.
7.1 Datos compuestos en Scheme
En Scheme es posible crear datos compuestos.
La forma de hacerlo es definiendo una construccin muy simple y usando esa
construccin simple para hacer cosas ms complicadas.
El tipo de dato compuesto ms simple es la pareja: una entidad formada por dos
elementos.
Se utiliza la funcin cons para construirla.
Ejemplo:
(cons 1 2) -> (1 . 2)
(define c (cons 1 2))
7.2 Cmo dibujamos las parejas
-
7.3 Funciones de acceso a una pareja
Podemos obtener el elemento correspondiente a la parte izquierda con el
operador car y su parte derecha con el operador cdr
Ejemplos:
(define c (cons 1 2))
(car c) -> 1
(cdr c) -> 2
7.4 Definicin declarativa
Las funciones cons, car y cdr quedan perfectamente definidas con las siguientes
ecuaciones:
(car (cons x y)) = x
(cdr (cons x y)) = y
7.5 Nota histrica
De dnde vienen los nombres car y cdr?
Realmente los nombres eran CAR y CDR (en maysculas)
La historia se remonta al ao 1959, en los orgenes del LISP y tiene que ver con
el nombre que se les daba a ciertos registros de la memoria del IBM 709.
Explicacin completa: http://www.iwriteiam.nl/HaCAR_CDR.html
7.6 Podemos hacer parejas con cualquier tipo de datos
Es posible construir una pareja con cualquier tipo de datos, mezclando distintos tipos de
datos:
(define c (cons 'hola #f))
(car c) -> 'hola
(cdr c) -> #f
7.7 Un ejemplo ms complicado
(define p1 (cons (lambda (x) (+ x 3))
5))
-
Cmo se podra llamar a la funcin de la parte izquierda de la pareja con la parte
derecha como argumento?
((car p1) (cdr p1)) -> 8
Podramos incluso definir una funcin que trabajara con esta estructura:
(define (procesa-pareja p)
((car p) (cdr p)))
7.8 Seguimos en el paradigma funcional
Seguimos en el paradigma funcional y una vez creada una pareja no es posible
modificar sus contenidos.
Scheme tiene primitivas para modificar el contenido de las parejas.
7.9 Las parejas son un tipo de dato de primer orden
Una pareja es tambin un tipo de datos de primer orden: puede pasarse como argumento
y devolverse como resultado de una funcin.
7.10 Propiedad de clausura: parejas de parejas
Las parejas pueden formar parte de otras parejas.
Esta ltima propiedad se denomina clausura de la operacin cons.
El resultado de un cons puede usarse para construir otros conses (parejas).
Ejemplo:
(define p1 (cons 1 2))
(define p2 (cons 3 4))
(define p (cons p1 p2))
Expresin equivalente
(define p (cons (cons 1 2)
(cons 3 4)))
Podramos representar esta estructura de esta forma:
Pero se hara muy complicado representar muchos niveles de anidamiento. Por eso
utilizamos la siguiente representacin, entendiendo las flechas como contiene y no
como referencia.
-
7.11 Ejemplos y figuras de parejas
(define p (cons (cons 1
(cons 3 4))
2))
Qu figura representara la estructura anterior?
7.12 Funciones c????r
(cdr (cdr (car p))) -> 4
Es equivalente a la instruccin cadar de Scheme:
(cddar p) -> 4
El nombre de la funcin se obtiene concatenando a la letra "c", las letras "a" o
"d" segn hagamos un car o un cdr y terminando con la letra "r"
Hay definidas 24 funciones de este tipo: caaaar, caaadr, , cddddr.
7.13 Funcin pair?
La funcin pair? nos dice si un objeto es atmico o es una pareja:
(pair? 3) -> #f
(pair? (cons 3 4)) -> #t
7.14 Curiosidad: los datos compuestos pueden ser funciones
Definimos la funcin mi-cons como:
(define (mi-cons x y)
(lambda (m)
(cond ((equal? m 'car) x)
((equal? m 'cdr) y)
(else (error "mensaje no definido: " m)) )))
La funcin anterior es equivalente a la funcin cons de Scheme.
Se devuelve un procedimiento que admite un argumento m (mensaje).
-
Cuando al procedimiento se le pasa el mensaje ='car= devolver el argumento x
original de mi-cons y cuando se le pasa el mensaje ='cdr= devolver el
argumento y:
> (define p (mi-cons 'hola #f))
> (p 'car)
hola
> (p 'cdr)
#f
Funciones que encapsulan la llamada a la funcin:
(define (mi-car pair)
(pair 'car))
(define (mi-cdr pair)
(pair 'cdr))
Ahora no hay ninguna diferencia entre el comportamiento de las funciones originales y
el de las nuevas:
> (define p (mi-cons (mi-cons 1
(mi-cons 3 4))
2))
> (mi-car (mi-cdr (mi-car p)))
3
8 Listas
En Scheme todos los datos compuestos se construyen a partir de las parejas. En
concreto las listas se definen de una forma recursiva muy elegante como secuencias de
parejas. Esta caracterstica se remonta al origen del LISP en el que John McCarthy
defini el concepto de S-expression e introdujo la notacin del "." para definir una
pareja.
8.1 Repaso de listas
(list 1 2 3 4) -> (1 2 3 4)
'(1 2 3 4) -> (1 2 3 4)
(define hola 1)
(define que 2)
(define tal 3)
(list hola que tal) -> (1 2 3 4)
'(hola que tal) -> (hola que tal)
(define a '(1 2 3))
(car a) -> 1
(cdr a) -> (2 3)
-
(length a) -> 3
(length '()) -> 0
(append '(1) '(2 3 4) '(5 6 7) '()) -> (1 2 3 4 5 6 7)
8.2 Construccin de listas con parejas
Definicin recursiva con parejas. Una lista es:
Una parejas en la que el primer elemento es un dato y el segundo el resto de la
lista (otra lista).
Un smbolo especial '() que denota la lista vaca
Ejemplo:
(cons (1
(cons 2
(cons 3
'())))))
8.3 Car y cdr para recorrer listas
Ahora se entiende mejor por qu las funciones que permiten recorrer una lista son car y
cdr como en el siguiente ejemplo:
(define lista (cons 1 (cons 2 (cons 3 (cons 4 '())))))
(car lista)
(cdr lista)
8.4 Cons para formar una nueva lista
> (define l1 '(1 2 3 4))
> (cons 'hola l1)
(hola 1 2 3 4)
8.5 Lista vaca
La lista vaca:
No es un smbolo
No es una pareja
Es una lista
(symbol? '()) -> #f
(pair? '()) -> #f
(length '()) -> 0
-
La funcin list? nos permite comprobar si un dato es una lista:
(list? '(1 2 3)) -> #t
(list? '()) -> #t
(list? 2) -> #f
Para saber si un objeto es la lista vaca, podemos utilizar la funcin null?:
(null? '()) -> #t
8.6 List devuelve la primera pareja de la lista
Tambin hay que hacer notar que la construccin de una lista devuelve la primera pareja
de la misma. As, tras evaluar la expresin
(define lista (list 1 2 3))
la variable lista tomar el valor de la primera pareja de la lista, la pareja formada por
un 1 y el resto de la lista. Es lo mismo que cuando hacemos:
(define lista (cons 1
(cons 2
(cons 3 '()))))
8.7 Listas con elementos compuestos
Las listas pueden contener cualquier tipo de elementos, incluyendo otras parejas.
La siguiente estructura se denomina lista de asociacin. Una lista cuyos elementos son
parejas:
(list (cons 'a 1)
(cons 'b 2)
(cons 'c 3)) -> ((a.1)(b.2)(c.2))
El diagrama box and pointer de esta estructura es el siguiente:
La expresin equivalente utilizando conses es:
(cons (cons 'a 1)
(cons (cons 'b 2)
(cons (cons 'c 3)
'())))
-
8.8 Listas de asociacin
Las listas de asociacin son unas estructuras muy usadas en LISP y Scheme. Las parejas
de la lista permiten almacenar elementos en forma de (clave,/valor/).
De hecho, existe una funcin primitiva de Scheme que permite buscar parejas en una
lista de asociacin por su clave. Se trata de (assq clave lista), que devuelve la
primera pareja de la lista de asociacin cuya parte izquierda coincide con la clave que
pasamos como parmetro. En el caso en que no exista la clave, devuelve falso.
(define l-assoc (list (cons 'a 1) (cons 'b 2) (cons 'c 3) (cons
'b 5)))
(assq 'a l-assoc) -> 1
(assq 'b l-assoc) -> 2
(assq 'd l-assoc) -> #f
Una implementacin de esta funcin de forma recursiva es la siguiente (la llamamos
my-assq):
(define (my-assq x lista)
(cond
((null? lista) #f)
((equal? (caar lista) x) (car lista))
(else (my-assq x (cdr lista)))))
8.9 Listas de listas
(define l1 (list 1 2 3))
(define l2 (list 1 l1 2 3))
La lista anterior tambin se puede definir con quote:
(define l2 '(1 (1 2 3) 2 3))
La representacin con parejas es la siguiente:
8.10 Implementacin de funciones sobre listas: append
(define (mi-append l1 l2)
(if (null? l1)
l2
(cons (car l1)
(append (cdr l1) l2))))
-
8.11 Implementacin de length
> (length '(1 2 3 (4 5 6)))
4
(define (mi-length items)
(if (null? items)
0
(+ 1 (length (cdr items)))))
8.12 Funciones que modifican listas
Dado en programacin funcional no es posible modificar el valor de una pareja,
tampoco es posible modificar el contenido de una lista. Por eso, las funciones siempre
devuelven estructuras nuevas, creadas a partir de los parmetros de entrada. Esto tiene
sus ventajas e inconvenientes.
Entre las ventajas se encuentra la sencillez, mantenibilidad, reusabilidad y robustez del
enfoque debido a la no existencia de efectos laterales en las funciones. En la
computacin procedural, donde existen estructuras de datos que pueden ser modificadas
por distintos procedimientos, hay que tener mucho cuidado los efectos laterales
provocados por que una funcin modifica una estructura en la que se basa otra funcin.
Entre los inconvenientes se encuentra el coste temporal de la operacin. Al no existir la
posibilidad de modificar slo una parte de la estructura se obliga a copiar todos sus
datos en una estructura nueva recin creada. Esto impone un coste mnimo de O(n).
En el caso concreto de las listas, las funciones que devuelven una lista modificada
deben construir una nueva lista. Ya lo hemos visto con mi-append, veamos algn
ejemplo ms.
La funcin (cuadrado lista) recibe una lista de nmeros y devuelve otra lista con los
nmeros elevados al cuadrado. Va creando la nueva lista con la llamada a cons para
aadir por delante el nevo nmero a la lista que devuelve la llamada recursiva.
(define (cuadrado-lista lista)
(if (null? lista)
'()
(cons (cuadrado (car lista))
(cuadrado-lista (cdr lista)))))
La generalizacin de la funcin anterior es la funcin map. Su implementacin sera la
siguiente.
(define (mi-map f lista)
(if (null? lista)
'()
(cons (f (car lista))
(mi-map f (cdr lista)))))
-
Lenguajes y Paradigmas de Programacin
Curso 2010-2011
Departamento de Ciencia de la Computacin e Inteligencia Artificial
Universidad de Alicante
Sitio web realizado con org-mode y el estilo CSS del proyecto Worg
Validate XHTML 1.0