Tema 3: Características de la programación funcional Sesión 5: El paradigma funcional (1) martes 22 de febrero de 2011 Referencias • Capítulo 1.1.5 SICP: [[http://mitpress.mit.edu/sicp/full-text/book/book-Z- H-10.html#%_sec_1.1.5][The Substitution Model for Procedure Application]] • Capítulo 10 PLP: Functional Languages martes 22 de febrero de 2011
51
Embed
Tema 3: Características de la programación funcional · 2016-04-25 · Conceptos de la programación funcional •Lenguajes funcionales puros: Miranda, Haskell, pH, Sisal, ML...
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Tema 3: Características de la programación funcional
Sesión 5: El paradigma funcional (1)
martes 22 de febrero de 2011
Referencias
• Capítulo 1.1.5 SICP: [[http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-10.html#%_sec_1.1.5][The Substitution Model for Procedure Application]]
• Capítulo 10 PLP: Functional Languages
martes 22 de febrero de 2011
Hoy veremos
• Historia del paradigma funcional
• Características del paradigma funcional puro
• Modelo de computación basado en sustitución
martes 22 de febrero de 2011
Orígenes históricos
• Raíces teóricas, antes de aparecer los ordenadores
• 1930s: Alan Turing, Alonzo Church, Stephen Kleene, Emil Post, etc.
• Turing: Máquina de Turing, programación imperativa
• Kleene y Post: métodos abstractos, sustituciones algebraicas
• Church: Cálculo lambda, programación funcional
• Modelo de cálculo lambda basado en la definición de funciones y la aplicación de estas funciones a argumentos
• (1958, John MacCarthy). LISP es el primer lenguaje de alto nivel basado en el paradigma funcional
• LISP no es programación funcional pura, tienen algunas instrucciones imperativas que permiten estado local y efectos laterales
• LISP lenguaje revolucionario, introduce nuevos conceptos: funciones como objetos primitivos, funciones orden superior, polimorfismo, listas, recursión, símbolos, homogeneidad de datos y programas, bucle “read-eval-print”
En un sentido estricto, la programación funcional define un programa como una función matemática que convierte unas entradas en unas salidas, sin estado
interno ni efectos laterales
martes 22 de febrero de 2011
Características de la programación funcional
• Programación declarativa:
• no hay asignación ni cambio de estado
• no hay referencias: identificadores asociados a valores
• no hay efectos laterales
• Recursión
• Funciones como tipos de datos primitivos
martes 22 de febrero de 2011
Programación declarativa vs imperativa
• Programación imperativa: pasos de ejecución y estado de variables
• Ejemplo de estado local (no existe en programación declarativa):
int x = x + 1;int y = y + 3;
(define (cuadrado x) (* x x))
int function contador () { static int c = 0;
c++; return c;}
contador(): 1contador(): 2contador(): 3
martes 22 de febrero de 2011
Programación declarativa vs imperativa
• Programación declarativa: Dentro del ámbito de declaración de las variables x1 … xn todas las ocurrencias de una expresión e que contiene únicamente las variables x1 … xn tienen el mismo valor.
• Consecuencia: optimización. Si una expresión e aparece en varios lugares dentro de un mismo ámbito, sólo es necesario evaluarla una vez.
(define (f x) ...)(+ (f 2) (f 2))
(define (f x) ...)(define y (f 2))(+ y y)
Una función llamada con los mismos argumentos siempre devuelve el mismo resultado
martes 22 de febrero de 2011
Modelo de computación de sustitución
• Un modelo computacional es un formalismo (conjunto de reglas) que definen el funcionamiento de un programa.
• En los lenguajes funcionales basados en la evaluación de expresiones, el modelo computacional define cuál va a ser el resultado de evaluar una determinada expresión.
• El modelo de sustitución se basa en una versión simplificada de la regla de reducción del cálculo lambda.
martes 22 de febrero de 2011
Modelo de computación de sustitución
• Reglas para evaluar una expresión e de Scheme:
• Si e es un valor primitivo, devolver ese mismo valor.
• Si e es una variable, devolver su valor asociado.
• Si e es una expresión del tipo (f arg1 … argn), donde f el nombre de un procedimiento primitivo (+, -, …), evaluar arg1 … argn y aplicar el procedimiento al resultado.
• Si e es una expresión 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 parámetro formal del procedimiento por el correspondiente argumento evaluado. Evaluar la expresión resultante.
martes 22 de febrero de 2011
(define (double x) (+ x x))(define (square y) (* y y))(define (f z) (+ square (double z)) 1))
Ejemplo modelo de sustitución
• Evaluamos (f (+ 2 1)) con orden aplicativo
martes 22 de febrero de 2011
Orden normal vs orden aplicativo
• Orden aplicativo (Scheme): se evalúan primero los argumentos, luego se sustituye
• Orden normal: se realizan todas las sustituciones hasta que se tiene una larga expresión formada por expresiones primitivas; se evalúa entonces
• Evaluamos (f (+ 2 1)) con orden normal
En programación funcional el orden normal y el orden aplicativo siempre devolverán el mismo resultado
martes 22 de febrero de 2011
Orden normal vs orden aplicativo
• El orden importa si no tenemos programación funcional pura:
• Los programas en Scheme son expresiones entre paréntesis
• Una expresión entre paréntesis es una lista de símbolos
• Por tanto, los programas (sin evaluar) se pueden considerar como datos (listas) y viceversa y, por tanto, podemos construirlos, desconstruirlos, y manipularlos como las listas
• Ejemplo: utilizando quote y eval, vamos a sumar una lista de números
El ámbito de las variables definidas en el let es local
• Las variables definidas en el let sólo tienen valores en el ámbito de la forma especial
(define x 5)(let ((x 1) (y 2)) (+ x y))xy
martes 1 de marzo de 2011
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 código se usa la variable z definida en el ámbito superior.
(define z 8)(let ((x 1) (y 2)) (+ x y z))
martes 1 de marzo de 2011
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))))
martes 1 de marzo de 2011
Variables en las asignaciones del let
• Las expresiones del let se evalúan todas antes de asociar ningún valor con las variables.
• No se realiza una asignación secuencial:
(define x 1)(let ((w (+ x 3)) (z (+ w 2))) ;; Error (+ w z))
martes 1 de marzo de 2011
Semántica del let
• Evaluar todas las expresiones de la derecha de las variables y guardar sus valores en variables auxiliares locales.
• Definir un ámbito local en el que se ligan las variables del let con los valores de las variables auxiliares.
• Evaluar el cuerpo del let en el ámbito local
martes 1 de marzo de 2011
Es posible implementar let utilizando lambda
• La semántica anterior queda clara cuando comprobamos que let se puede definir en función de lambda. La expresión:
(define x 5)(let ((x (+ 2 3)) (y (+ x 3))) (+ x y))
((lambda (x y) (+ x y)) (+ 2 3) (+ x 3))
martes 1 de marzo de 2011
let* permite asignaciones secuenciales
• La forma especial let* permite una asignación secuencial de las variables y las expresiones:
• ¿Cómo se puede implementar con let?
(let* ((x (+ 1 2)) (y (+ x 3)) (z (+ y x))) (* x y z)
martes 1 de marzo de 2011
let* permite asignaciones secuenciales
• La forma especial let* permite una asignación secuencial de las variables y las expresiones:
• ¿Cómo se puede implementar con let?
(let* ((x (+ 1 2)) (y (+ x 3)) (z (+ y x))) (* x y z)
(let ((x (+ 1 2))) (let ((y (+ x 3))) (let ((z (+ y x))) (* x y z))))
martes 1 de marzo de 2011
let* permite asignaciones secuenciales
• El primer let crea un ámbito local en el que se evalúa el segundo let, que a su vez crea otro ámbito local en el que se evalúa el tercer let. Se crean tres ámbitos locales anidados, y en el último se evalúa la expresión (* x y z).
(let ((x (+ 1 2))) (let ((y (+ x 3))) (let ((z (+ y x))) (* x y z))))
martes 1 de marzo de 2011
El ámbito definido en el let puede sobrevivir
• Un ejemplo avanzado del funcionamiento del let, en el que creamos una función en el ámbito del let:
• Sucede lo siguiente:
(define h (let ((x 3)) (lambda (y) (+ x y))))
1. Se invoca al let y se crea un entorno local en el que x vale 32. En ese entorno se crea una función de un parámetro que usa la variable x3. Se devuelve la función y se asocia a h
martes 1 de marzo de 2011
El ámbito definido en el let puede sobrevivir
• ¿Qué pasa cuando se llama a h? ¿Y si modificamos el valor de x en el ámbito global?
• Funcionamiento: cuando se llama a la función, el intérprete restaura el entorno que estaba activo cuando la expresión lambda se evaluó. Y lo aumenta con las variables de los parámetros formales ligadas al valor del argumento. En este ámbito se evalúa el cuerpo de la función.
(define h (let ((x 3)) (lambda (y) (+ x y))))
(h 5)(define x 10)(h 5)
martes 1 de marzo de 2011
El mismo efecto sin let
• En el ejemplo siguiente la llamada (make-sumador 3) produce exactamente el mismo efecto que el let anterior.
(define (make-sumador x) (lambda (y) (+ x y)))(define h (make-sumador 3))(define x 10)(h 5)
martes 1 de marzo de 2011
El ámbito definido en el let puede sobrevivir
• Importante:
• Seguimos estando en el paradigma de programación funcional declarativa en el que no hay efectos laterales.
• El modelo de sustitución no explica correctamente este comportamiento. Veremos más adelante el modelo de entornos que sí lo hace.
• Llamamos entorno (environment) a un conjunto de valores asociados a variables. Utilizamos las palabras ámbito y entorno como sinónimos.
martes 1 de marzo de 2011
Closure
• En muchos lenguajes de programación modernos existe el concepto de closure
• Se llama closure a una función creada en tiempo de ejecución junto con el entorno en el que ésta se ha creado
• Lo veremos en profundidad más adelante
martes 1 de marzo de 2011
Dualidad entre datos y programas
• Los programas en Scheme son expresiones entre paréntesis
• Una expresión es una lista de símbolos
• Esto permite tratar a los programas como datos y viceversa
• La forma especial eval evalúa una expresión
• Un ejemplo: supongamos que queremos sumar una lista de números
• Permite llamar a una función con una lista de argumentos
• Sintaxis:
• Ejemplos:
(apply <func> <lista-args>)
(apply + '(1 2 3 4))(apply '+ '(1 2 3 4)) ; no funciona: '+ es un identificador(apply (lambda (x y) (* x y)) '(2 4))
martes 1 de marzo de 2011
Ejemplo de dualidad de datos y programa calculadora
• Este programa no es funcional: realiza pasos de ejecución para leer las expresiones introducidas por el usuario y para imprimir el resultado de su evaluación.
(define (rep-loop) (display "mi-interprete> ") ; imprime un prompt (let ((expr (read))) ; lee una expresión (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)))))
martes 1 de marzo de 2011
Tema 3: Características de la programación funcional
Sesión 8: El paradigma funcional (4)
jueves 3 de marzo de 2011
Referencias
• Capítulo 10 PLP: Functional Languages
• Capítulo 2.2 SICP, Abelson & Sussman
jueves 3 de marzo de 2011
Hoy veremos
• Características de la programación funcional en LISP/Scheme
jueves 3 de marzo de 2011
Características de la programación funcional
• Evaluación sin efectos laterales: paradigma declarativo y modelo de sustitución
• Funciones como tipos de datos primitivos: forma especial lambda
• Ámbitos de variables: forma especial let
• Dualidad entre datos y programas: formas especiales eval y apply
• Listas como elemento fundamental de procesamiento: parejas, cons, car y cdr
• Recursión
jueves 3 de marzo de 2011
Ejemplo de dualidad de datos y programa calculadora
• Este programa no es funcional: realiza pasos de ejecución para leer las expresiones introducidas por el usuario y para imprimir el resultado de su evaluación.
Ejemplo de dualidad de datos y programa calculadora
• Es importante darse cuenta de que la expresión:
• devuelve una lista que es una expresión lambda correcta, cuya evaluación va a crear un procedimiento al que se llama después. Por ejemplo:
• Si hiciéramos (append '(lambda) params cuerpo) ¿qué obtendríamos?
(append '(lambda) (list params) (list cuerpo))
(define params '(x y))(define cuerpo '(+ x y))
jueves 3 de marzo de 2011
Ejemplo de dualidad de datos y programa calculadora
• Es importante darse cuenta de que la expresión:
• devuelve una lista que es una expresión lambda correcta, cuya evaluación va a crear un procedimiento al que se llama después. Por ejemplo:
• Si hiciéramos (append '(lambda) params cuerpo) obtendríamos una lista con 6 elementos:
• Necesitamos obtener la lista '(lambda (x y) (+ x y)), una lista de 3 elementos
(append '(lambda) (list params) (list cuerpo))
(define params '(x y))(define cuerpo '(+ x y))
(append '(lambda) params cuerpo)-> (lambda x y + x y)
jueves 3 de marzo de 2011
Datos compuestos en Scheme
• En Scheme es posible crear datos compuestos.
• La forma de hacerlo es definiendo una construcción muy simple y usando esa construcción simple para hacer cosas más complicadas.
• El tipo de dato compuesto más simple es la pareja: una entidad formada por dos elementos.
• Se utiliza la función cons para construirla.
(cons 1 2)(1 . 2)
jueves 3 de marzo de 2011
Diagramas caja y puntero
• Las parejas se representan mediante este tipo de figuras
(cons 1 2)(1 . 2)
jueves 3 de marzo de 2011
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
• Definición declarativa: Las funciones cons, car y cdr quedan perfectamente definidas con las siguientes ecuaciones
(define c (cons 1 2))> (car c) 1> (cdr c) 2
(car (cons x y)) = x(cdr (cons x y)) = y
jueves 3 de marzo de 2011
Nota histórica
• ¿De dónde vienen los nombres car y cdr?
• Realmente los nombres eran CAR y CDR (en mayúsculas)
• La historia se remonta al año 1959, en los orígenes del LISP y tiene que ver con el nombre que se les daba a ciertos registros de la memoria del IBM 709.
• Define el procedimiento (insertar-list lista l2 n) que reciba dos listas y un número n. Deberá insertar en el mismo nivel, la l2 en la lista en la posición indicada por n (suponemos que n nunca será mayor que la longitud de la lista):
> (define (insertar-list lista l2 n) (if (= n 0) (append l2 lista) (cons (car lista) (insertar-list (cdr lista) l2 (- n 1)))))> (insertar-list '(1 2 3 4) '(a b) 3)(1 2 3 a b 4)
jueves 3 de marzo de 2011
Ejercicios
• Dibuja el diagrama caja y puntero asociado a estas estructuras de datos:
• Dibuja el diagrama caja y puntero asociado a esta estructura de datos:
• Implementa la función mi-lis-ref que reciba un número n y una lista como argumento y devuelva el elemento n-ésimo de la lista (empezando a contar en 0).
• Implementa la función mi-list-tail que reciba un número n y una lista como argumento y devuelva la lista resultante de quitar n elementos de la lista original
((x) y z)(x (y z))((a) b (c ()))
(cons (cons 2 (cons 3 (cons 4 ‘()))) (cons 1 2))
jueves 3 de marzo de 2011
list devuelve la primera pareja de la lista
• La construcción de una lista devuelve la primera pareja de la misma. Así, tras evaluar la expresión:
• 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:
• Implementa la función mi-append que reciba dos listas como argumento y devuelva una nueva lista compuesta por la concatenación de las dos listas recibidas.
martes 8 de marzo de 2011
Implementación de funciones sobre listas
• Implementa la función mi-append que reciba dos listas como argumento y devuelva una nueva lista compuesta por la concatenación de las dos listas recibidas.
• La función anterior es equivalente a la función 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 (mi-cons x y) (lambda (m) (cond ((equal? m 'car) x) ((equal? m 'cdr) y) (else (error "mensaje no definido: " m)) )))
martes 8 de marzo de 2011
Los datos compuestos pueden ser funciones
• Definimos la función mi-cons como:
• ¿Cómo la utilizamos? Pensad en un ejemplo
(define (mi-cons x y) (lambda (m) (cond ((equal? m 'car) x) ((equal? m 'cdr) y) (else (error "mensaje no definido: " m)) )))
martes 8 de marzo de 2011
Los datos compuestos pueden ser funciones
• Definimos la función mi-cons como:
• ¿Cómo la utilizamos? Pensad en un ejemplo
(define (mi-cons x y) (lambda (m) (cond ((equal? m 'car) x) ((equal? m 'cdr) y) (else (error "mensaje no definido: " m)) )))
> (define p (mi-cons 'hola #f))> (p 'car)hola> (p 'cdr)#f
martes 8 de marzo de 2011
Los datos compuestos pueden ser funciones
• Definimos la función mi-cons como:
• Funciones
• Funciones que encapsulan la llamada a la función:
• Ahora no hay ninguna diferencia entre el comportamiento de las funciones originales y el de las nuestras
(define (mi-cons x y) (lambda (m) (cond ((equal? m 'car) x) ((equal? m 'cdr) y) (else (error "mensaje no definido: " m)) )))
martes 8 de marzo de 2011
Los datos compuestos pueden ser funciones
• Definimos la función mi-cons como:
• Funciones
• Funciones que encapsulan la llamada a la función:
• Ahora no hay ninguna diferencia entre el comportamiento de las funciones originales y el de las nuestras
(define (mi-cons x y) (lambda (m) (cond ((equal? m 'car) x) ((equal? m 'cdr) y) (else (error "mensaje no definido: " m)) )))
(define (mi-car pareja) (pareja 'car))
(define (mi-cdr pareja) (pareja 'cdr))
martes 8 de marzo de 2011
Los datos compuestos pueden ser funciones
• Definimos la función mi-cons como:
• Funciones
• Funciones que encapsulan la llamada a la función:
• Ahora no hay ninguna diferencia entre el comportamiento de las funciones originales y el de las nuestras
(define (mi-cons x y) (lambda (m) (cond ((equal? m 'car) x) ((equal? m 'cdr) y) (else (error "mensaje no definido: " m)) )))