El paradigma de programación funcional

download El paradigma de programación funcional

of 24

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