Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4....

63
1. El paradigma de Programación Funcional 1.1 Definición y orígenes del paradigma funcional 1.2. Programación declarativa 1.3. Valores y referencias 1.4. Modelo de computación de sustitución 1.5. Aplicaciones prácticas de la programación funcional 2. Scheme como lenguaje de programación funcional 2.1. Funciones y formas especiales 2.2. Formas especiales en Scheme: define , if , cond 2.3. Símbolos y formas especiales quote y eval 2.4. Listas 2.5. Recursión 3. Tipos de datos compuestos en Scheme 3.1. El tipo de dato pareja 3.2. Las parejas son objetos de primera clase 3.3. Diagramas caja-y-puntero 4. Listas en Scheme 4.1. Implementación de listas en Scheme 4.2. Listas con elementos compuestos 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. Funciones como tipos de datos de primera clase 5.1. Forma especial lambda 5.2. Las funciones son objetos de primera clase 5.3. Funciones de orden superior Tema 3: Programación funcional Contenidos

Transcript of Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4....

Page 1: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

1. El paradigma de Programación Funcional

1.1 Definición y orígenes del paradigma funcional1.2. Programación declarativa1.3. Valores y referencias1.4. Modelo de computación de sustitución1.5. Aplicaciones prácticas de la programación funcional

2. Scheme como lenguaje de programación funcional

2.1. Funciones y formas especiales2.2. Formas especiales en Scheme: define , if , cond

2.3. Símbolos y formas especiales quote y eval

2.4. Listas2.5. Recursión

3. Tipos de datos compuestos en Scheme

3.1. El tipo de dato pareja3.2. Las parejas son objetos de primera clase3.3. Diagramas caja-y-puntero

4. Listas en Scheme

4.1. Implementación de listas en Scheme4.2. Listas con elementos compuestos4.3. Funciones recursivas sobre listas4.4. Funciones con número variable de argumentos

5. Funciones como tipos de datos de primera clase

5.1. Forma especial lambda

5.2. Las funciones son objetos de primera clase5.3. Funciones de orden superior

Tema 3: Programación funcional

Contenidos

Page 2: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

6. Ambito de variables, clausuras y let

6.1. Ámbitos de variables6.2. Clausuras6.3 Forma especial let

En un sentido estricto, la programación funcional define un programa como una funciónmatemática que convierte unas entradas en unas salidas, sin ningún estado interno y ningúnefecto lateral.

La no existencia de estado interno (celdas de memoria en las que se guardan y se modificanvalores, por ejemplo) y la ausencia de efectos laterales es una característica también de laprogramación declarativa.

Otras características del paradigma funcional son las siguientes:

RecursiónFunciones como tipos de datos primitivosUso de listas

Cálculo lambda (Alonzo Church) en los años 30Años 30 distintos matemáticos proponen modelos computacionales y demuestran suequivalencia (Turing - Máquina de Turing, Kleen - Sustituciones algebráicas, …)Modelo de cálculo lambda basado en la definición de funciones y la aplicación de estasfunciones a argumentos

Lisp es el primer lenguaje de programación de alto nivel basado en el paradigma funcionalCreado en 1958 por John McCarthyLisp es un lenguaje revolucionario e introduce nuevos conceptos de programación:funciones como objetos primitivos, funciones de orden superior, polimorfismo, listas,recursión, símbolos, homogeneidad de datos y programas, bucle REPL “read-eval-print”La herencia del Lisp llega a lenguajes derivados de él (Scheme, Golden Common Lisp) ya nuevos lenguajes de paradigmas no estrictamente funcionales, como C#, Python, Ruby,Objective-C o Scala

1. El paradigma de Programación Funcional

1.1. Definición e historia del paradigma funcional

1.1.1 Orígenes históricos

1.1.2 Historia del Lisp

Page 3: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

En Lisp (y en Scheme) existen instrucciones que se salen del paradigma funcional puro ypermiten estado local y efectos laterales (programación imperativa). Por ello son lenguajes enlos que es posible programar de forma imperativa, no funcional. Nosotros no vamos a utilizaresas instrucciones en esta primera parte de la asignatura, escribiendo siempre códigofuncional.

Lisp se diseñó con el objetivo de ser un lenguaje de alto nivel capaz de resolver problemasprácticos de Inteligencia Artificial, no con la idea de ser un lenguaje formal basado un únicomodelo de computación.

Con el paso de los años y el avance en los diseños de compiladores e intérpretes ha sidoposible diseñar lenguajes de programación que siguen más estrictamente las característicasdeclarativas del paradigma funcional y que también son útiles y prácticos para desarrollarprogramas en el mundo real, como Haskell, Miranda o ML.

Hablamos de programación declarativa para referirnos a lenguajes de programación (osentencias de código) en los que se declaran los valores, objetivos o características finales delos elementos del programa, pero no se especifican detalles de implementación, ni de controlde flujo. Estos elementos se resuelven por la componente de run-time del lenguaje.

Por ejemplo, un conjunto de reglas de Prolog son sentencias declarativas. O una definición deuna interfaz en Java.

Una característica fundamental del código declarativo es que no utiliza pasos de ejecución, niasignación destructiva. Define un conjunto de reglas y definiciones de estilo matemático.

La programación declarativa no es exclusiva de los lenguajes funcionales. Existen muchoslenguajes no funcionales que son declarativos (como el Prolog). De la misma forma queexisten lenguajes que tienen características funcionales y que no son declarativos (como elLisp o Scheme).

En esta primera parte de la asignatura, en la que vamos a tratar el paradigma de programaciónfuncional, vamos a usar sólo las características declarativas de Scheme.

Avanzamos un resumen de las características fundamentales de la programación declarativafrente a la programación imperativa. En los siguientes apartados explicaremos más estascaracterísticas.

1.1.3. Lisp no es un lenguaje estricto de programación funcional

1.2. Programación declarativa

Page 4: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Características de la programación declarativa

Variable = nombre dado a un valor (declaración)No existe asignación ni cambio de estadoNo existe mutación, se cumple la transferencia referencial: dentro de un mismo ámbitotodas las ocurrencias de una variable y las llamadas a funciones devuelven el mismo valorNo existen referencias

Características de la programación imperativa

Variable = nombre de una zona de memoriaAsignaciónReferenciasPasos de ejecución

El estilo de programación imperativa se basa en pasos de ejecución que modifican el estadode variables:

int x = x + 1;int y = y + 3;

Las expresiones anteriores son típicas expresiones de asignación que modifican valoresanteriores de una variable por nuevos valores. El estado de las variables (su valor) cambia conla ejecución de los pasos del programa.

Por contra, un ejemplo de programación declarativa en el que declaramos una función quetoma como entrada un número y devuelve su cuadrado:

(define (cuadrado x) (* x x))

Otra característica de la programación declarativa es que no existe el estado local mutable.

En programación imperativa esto no es así. Por ejemplo, una de las características de laprogramación orientada a objetos es guardar estado mutable en variables de instancia declases.

Por ejemplo, en Java:

1.2.1. Programación declarativa vs. imperativa

Page 5: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

public class Contador { int c; public Contador(int valorInicial) { c = valorInicial; } public int valor() { c++; return c; }}

Contador cont = new Contador(10);cont.valor(); // 11cont.valor(); // 12cont.valor(); // 13

Cada llamada al método valor() modifica el estado del objeto cont .

También se pueden definir funciones con estado local mutable en C:

int function contador () { static int c = 0; c++; return c;} contador() ;; 1contador() ;; 2contador() ;; 3

Los lenguajes funcionales puros tienen la propiedad de transparencia referencial: es posiblesustituir una expresión por su valor sin que se introduzca ninguna modificación en el resultadofinal del programa.

Como consecuencia, en programación funcional, una función siempre devuelve el mismo valorcuando se le llama con los mismos parámetros.

Las funciones no modifican ningún estado, no acceden a ninguna variable ni objeto global ymodifican su valor.

1.2.2. Las funciones devuelven siempre el mismo valor

Page 6: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

En programación funcional pura una vez declarada una variable no se puede modificar suvalor. En algunos lenguajes de programación (como Scala) este concepto se refuerzadefiniendo la variable como inmutable (con la directiva val ).

En programación imperativa es habitual modificar el valor de una variable en distintos pasos deejecución

Ejemplo:

1. int x = 1;2. x = x+1;3. int y = x+1;4. y = x;

Líneas 1 y 3: sentencias declarativasLíneas 2, 4: sentencias imperativas

En programación declarativa sólo existen valores, no hay referencias. Cuando se realiza unaasignación de un valor a una variable debemos considerar que estamos dando un nombre aun objeto matemático que no puede ser modificado o que estamos copiando el valor en lavariable.

Por ejemplo, en Java, los tipos de datos primitivos son valores. Las asignaciones valores deestos tipos a variables realizan copias de valores:

int a = 4;int b = 2;int c = b;b = 3;

En la variable a se copia el valor 4 y en las variables b y c se copia el valor 2 . Nohay forma de modificar (mutar) esos valores. Podríamos cambiar las variables guardando enella otros valores, pero los valores propiamente dichos son inmutables. En la última instrucciónmodificamos el valor de la variable b , pero el valor de la variable c sigue siendo 2.

Los tipos de datos cuyos valores son inmutables y sus asignaciones tienen una semántica decopia reciben el nombre de tipos de valor (value types en inglés).

En programación imperativa, sin embargo, existe la distinción entre valores y referencias.

1.2.3. Diferencia entre declaración y modificación de variables

1.3. Valores y referencias

Page 7: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Diferencia entre valor y referencia

Las variables b y c son referencias que están apuntando a una misma posición, en la quese encuentra el número 2. Si modificamos (mutamos) el contenido de esa posición se modificatambién el valor de las dos variables.

Este comportamiento es el de los tipos de referencia. Se trata de tipos de datos mutables enlos que la asignación funciona con semántica de referencia.

Distintos lenguajes de programación tienen distintas formas de trabajar con los tipos dereferencia. Por ejemplo, cualquier objeto en Java tiene una semántica de referencia. Cuandoasignamos un objeto a una variable, estamos guardando en la variable una referencia alobjeto.

Point2D p1 = new Point2D(3.0, 2.0); // la coord x de p1 es 3.0Point2D p2 = p1;p2.setCoordX(10.0);p1.getCoordX(); // la coord x de p1 es 10.0, sin que ninguna sentencia haya modificado p1

Es fundamental entender la semántica de referencia, porque distintas variables puedenreferenciar un mismo objeto y se pueden producir efectos laterales (side effects en inglés). Eldato guardado en una variable cambia después de una sentencia en la que no se ha usadoesa variable.

En programación funcional no existe la asignación destructiva, en la que se modifica un valorpreviamente asignado. Sí que se pueden dar valor a variables o identificadores, entendiéndolocomo una definición de un valor constante.

La forma especial define en Scheme crea una nueva variable y le da el valor definido.

1.3.1 Asignación

Page 8: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define mi-nombre "Alejandro Perez")

Sin embargo, en programación imperativa una variable guarda una referencia a una posiciónde memoria (o estructura de datos) que puede ser modificada posteriormente mediante unanueva asignación:

char *miNombre = "Alejandro Perez"miNombre[3] = 'a'

En programación funcional debemos entender las variables como variables matemática quetienen un valor y que tienen la propiedad de transparencia referencial. No debemosentenderlas como una referencia a una posición de memoria que puede ser modificada. Losvalores son inmutables y no existen efectos laterales.

En esta página de la Wikipedia se explica bastante bien la semántica de la asignación enprogramación funcional.

En los lenguajes que trabajan con referencias es necesario definir dos tipos de igualdades:igualdad de valor e igualdad de referencia.

Por ejemplo, en Java se utiliza la igualdad == para comprobar la igualdad de referencia y elmétodo equals para la igualdad de valor:

String s1 = "Hola";String s2 = s1;String s3 = "Hola";Boolean v1 = (s1 == s2); // trueBoolean v2 = (s1 == s3); // falseBoolean v3 = (s1.equals(s3)); // true

El uso de referencias es común en la mayoría de los lenguajes modernos. En lenguajes comoPython, Ruby, Smalltalk, Objective-C, etc. la asignación funciona como en Java, copiando lasreferencias a los objetos. Esto permite construir grafos de objetos relacionados en tiempo deejecución que mantienen el estado compartido de la aplicación.

El estado compartido mutable es muy eficiente, porque con una única modificación de un únicoobjeto se actualiza todas las variables que están apuntando a él. Pero el estado compartidomutable también puede hacer que el código sea difícil de mantener y que sea complicado

1.3.2. Igualdad en lenguajes con referencias

1.3.3. Ventajas e inconvenientes de las referencias mutables

Page 9: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

razonar matemáticamente sobre su corrección.

El código que se añade al proyecto puede modificar el comportamiento del código ya existente,pudiendo introducirse bugs al aumentar las funcionalidades del programa.

El código escrito en programación funcional no contiene referencias mutables y está libre delos efectos laterales. La construcción de un programa se puede hacer de forma incremental,añadiendo funciones que nunca van a modificar el comportamiento de las funciones yaexistentes.

Scheme también tiene sentencias de mutación, pero no las usaremos en esta primera parte dela asignatura en la que estamos escribiendo código funcional.

Un modelo computacional es un formalismo (conjunto de reglas) que definen el funcionamientode un programa. En el caso de los lenguajes funcionales basados en la evaluación deexpresiones, el modelo computacional define cuál va a ser el resultado de evaluar unadeterminada expresión.

El modelo de computación de sustitución es un modelo muy sencillo que permite definir lasemántica de la evaluación de expresiones en lenguajes funcionales como Scheme.

Es muy fácil de implementar: como reto proponemos que lo escribáis en forma de programa enalgún lenguaje que conozcáis (Java, Python, …)

El modelo de sustitución se basa en una versión simplificada de la regla de reducción delcálculo lambda.

Reglas para evaluar una expresión e usando el modelo de sustitución:

1. Si e es un valor primitivo, devolver ese mismo valor.2. Si e es una variable, devolver su valor asociado con un define .3. Si e es una expresión del tipo (f arg1 … argn), donde f el nombre de una función primitiva

(‘+’, ‘-’, …), evaluar arg1 … argn y llamar a la función con el resultado.4. Si e es una expresión del tipo (f arg1 … argn), donde f el nombre de una función definida

por el usuario (definida con un define en Scheme), sustituir f por su cuerpo,reemplazando cada parámetro formal del procedimiento por el correspondiente argumentoevaluado. Evaluar la expresión resultante.

En el orden aplicativo se realizan las evaluaciones de dentro a fuera de los paréntesis. Cuando

1.4. Modelo de computación de sustitución

1.4.1 Orden normal vs. orden aplicativo

Page 10: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

se llega a una expresión primitiva se evalúa.

En el orden normal se realizan todas las sustituciones hasta que se tiene una larga expresiónformada por expresiones primitivas; se evalúa entonces.

Scheme utiliza el orden aplicativo.

Supongamos las siguientes definiciones de funciones:

(define (double x) (+ x x))(define (square y) (* y y))(define (f z) (+ (square (double z)) 1))

¿Cuál sería el resultado de evaluar (f (+ 2 1)) con orden aplicativo?

Solución:

(f (+ 2 1)) -> ; evaluamos (+ 2 1) por ser + una función primitiva(f 3) -> ; sustituimos (f 3) por su definición(+ (square (double 3)) 1) -> ; sustituimos (double 3)(+ (square (+ 3 3)) 1) -> ; evaluamos (+ 3 3)(+ (square 6) 1) -> ; sustituimos (square 6)(+ (* 6 6) 1) -> ; evaluamos (* 6 6)(+ 36 1) -> ; evaluamos (+ 36 1)37

¿Y en orden normal?

1.4.2. Ejemplo

Page 11: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(f (+ 2 1)) -> ; sustituimos (f (+ 2 1)) ; por su definición, con z = (+ 2 1)(+ (square (double (+ 2 1))) 1) -> ; sustituimos (double (+ 2 1))(+ (square (+ (+ 2 1) (+ 2 1))) 1) -> ; sustituimos (square ...)(+ (* (+ (+ 2 1) (+ 2 1)) (+ (+ 2 1) (+ 2 1))) 1) -> ; evaluamos (+ 2 1)(+ (* (+ 3 3) (+ 3 3)) 1) -> ; evaluamos (+ 3 3)(+ (* 6 6) 1) -> ; evaluamos (* 6 6)(+ 36 1) -> ; evaluamos (+ 36 1)37

En programación funcional el resultado de evaluar una expresión es el mismoindependientemente del tipo de orden.

Supongamos una función (random x) que devuelve un entero aleatorio entre 0 y x. Estafunción no cumpliría el paradigma funcional, porque devuelve un valor distinto con el mismoparámetro de entrada.

Evaluamos las siguientes expresiones con orden aplicativo y normal, para comprobar que elresultado es distinto

(define (zero x) (- x x))(zero (random 10))

En los años 60 la programación funcional (Lisp) fue dominante en departamentos deinvestigación en Inteligencia Artificial (MIT por ejemplo).

En los años 70, 80 y 90 se fue relegando cada vez más a los nichos académicos y deinvestigación; en la empresa se impusieron los lenguajes imperativos y orientados a objetos.

En la primera década del 2000 han aparecido lenguajes multi-paradigma (muchos de ellosinterpretados) como Ruby, Python, Groovy, Objective-C, Lua o Scala que incluyen elparadigma funcional.

1.4.3. El orden de evaluación sí importa si no tenemos programación funcional

1.5. Aplicaciones prácticas de la programación funcional

1.5.1. El renacimiento de la programación funcional

Page 12: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

La gran ventaja del paradigma funcional es la ausencia de estado. Estos lenguajes aprovechanesta característica para implementar programas fácilmente escalables en arquitecturas demúltiples procesadores concurrentes o de múltiples servidores.

Un ejemplo es el uso de Scala en Tumblr con el que se consigue crear código que no tieneestado compartido y que es fácilmente paralelizable entre los más de 800 servidoresnecesarios para atender picos de más de 40.000 peticiones por segundo:

“Scala encourages no shared state. Finagle is assumed correct because it’s tested byTwitter in production. Mutable state is avoided using constructs in Scala or Finagle. Nolong running state machines are used. State is pulled from the database, used, andwritten back to the database. Advantage is developers don’t need to worry about threadsor locks.”

Otro ejemplo es el uso de las técnicas de programación funcional en los nuevos sistemas debases de datos NoSQL como es el MapReduce de Google. Como dice Joel Spolsky en suartículo The Perils of JavaSchools:

“Without understanding functional programming, you can’t invent MapReduce, thealgorithm that makes Google so massively scalable.”

Los programas complejos se construyen a base de ir definiendo y probando elementoscomputacionales cada vez más complicados.

Abelson y Sussman comentan en el SICP:

In general, computational objects may have very complex structures, and it would beextremely inconvenient to have to remember and repeat their details each time we want touse them. Indeed, complex programs are constructed by building, step by step,computational objects of increasing complexity.

The interpreter makes this step-by-step program construction particularly convenientbecause name-object associations can be created incrementally in successiveinteractions. This feature encourages the incremental development and testing ofprograms and is largely responsible for the fact that a Lisp program usually consists of alarge number of relatively simple procedures

Esta metodología de programación se denomina programación iterativa o evolutiva.

1.5.2. La programación funcional refuerza la metodología de programación evolutiva

2. Scheme como lenguaje de programación funcional

Page 13: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Vamos ver ejemplos concretos de características de un lenguaje de programación funcionalestudiando las características funcionales de Scheme.

En concreto, veremos:

Definición de funciones y otras primitivas de programación funcional en SchemeSímbolos y primitivas quote y eval

Definición de funciones recursivas en Scheme

En el seminario de Scheme hemos visto un conjunto de primitivas que podemos utilizar enScheme.

Podemos clasificar las primitivas en funciones y formas especiales. Las funciones seevalúan usando el modelo de sustitución aplicativo ya visto:

Primero se evalúan los argumentos y después se sustituye la llamada a la función por sucuerpo y se vuelve a evaluar la expresión resultante.Las expresiones siempre se evalúan desde los paréntesis interiores a los exteriores.

Las formas especiales son expresiones primitivas de Scheme que tienen una forma deevaluarse propia, distinta de las funciones.

Sintaxis

(define <símbolo> <expresión>)

Evaluación

1. Evaluar <expresión>2. Asociar el valor resultante con el <símbolo>

Ejemplo

(define base 10)(define altura 12)(define area (/ (* base altura) 2)

2.1 Funciones y formas especiales

2.2. Formas especiales en Scheme: define, if, cond

2.2.1 Forma especial define

Page 14: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Sintaxis

(define (<nombre-funcion> <argumentos>) <cuerpo>)

Evaluación

La semana que viene veremos con más detalle la semántica, y explicaremos la forma especiallambda que es la que realmente crea la función. Hoy nos quedamos en la siguiente

descripción de alto nivel de la semántica:

1. Crear la función con el cuerpo2. Dar a la función el nombre nombre-función

Ejemplo

(define (factorial x) (if (= x 0) 1 (factorial (- x 1))))

Sintaxis

(if <condición> <expresión-true> <expresión-false>)

Evaluación

1. Evaluar <condición>2. Si el resultado es #t evaluar la <expresión-true>, en otro caso, evaluar la <expresión-

false>

Ejemplo

(if (> 10 5) (substring "Hola qué tal" (+ 1 1) 4) (/ 12 0))

Sintaxis

Forma especial define para definir funciones

2.2.2. Forma especial if

2.2.3. Forma especial cond

Page 15: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(cond (<exp-cond-1> <exp-consec-1>) (<exp-cond-2> <exp-consec-2>) ... (else <exp-consec-else>))

Evaluación

1. Se evalúan de forma ordenada todas las expresiones hasta que una de ellas devuelva #t

2. Si alguna expresión devuelve #t , se devuelve el valor del consecuente de esaexpresión

3. Si ninguna expresión es cierta, se devuelve el valor resultante de evaluar el consecuentedel else

Ejemplo

(cond ((> 3 4) '3-es-mayor-que-4) ((< 2 1) '2-es-menor-que-1) ((= 3 1) '3-es-igual-que-1) ((= 2 2) '2-es-igual-que-2) ((> 3 2) '3-es-mayor-que-2) (else 'ninguna-condicion-es-cierta))

A diferencia de los lenguajes imperativos, Scheme trata a los identificadores (nombres que seles da a las variables) como datos del lenguaje de tipo symbol.

Los símbolos son distintos de las cadenas. Una cadena es un tipo de dato compuesto,mientras que los símbolos se almacenan con un valor único denominado valor hash.

Ejemplos de funciones Scheme con símbolos:

2.3. Símbolos y formas especiales quote y eval

Page 16: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define x 12)(symbol? 'x) ; -> #t(symbol? x) ; -> #f ¿Por qué?(symbol? 'hola-que<>)(symbol->string 'hola-que<>)'mañana'lápiz ; aunque sea posible, no vamos a usar acentos en los símbolos; pero sí en los comentarios(symbol? 'hola) ; #t(symbol? "hola") ; #f(symbol? #f) ; #f(symbol? (car '(hola cómo estás))) ; #t(equal? 'hola 'hola)(equal? 'hola "hola")

Sintaxis

(quote <simbolo>)(quote <expresion>)

Evaluación

Se devuelve el símbolo o la expresión sin evaluar. Las expresiones deben estar definidas conun número balanceado de paréntesis y definen listas de elementos que pueden ser recorridascon las funciones car y cdr . Se abrevia en con el carácter '

Ejemplo

(quote x)'hola'(+ 1 2 3 4)(quote (1 2 3 4))'(* (+ 1 (+ 2 3)) 5)

Un símbolo es un identificador que puede asociarse o ligarse (bind) a un valor (cualquier datode primera clase).

Cuando escribimos un símbolo en el prompt de Scheme el intérprete lo evalúa y devuelve suvalor:

2.3.1. Forma especial quote

2.3.2. Evaluación de símbolos

Page 17: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define pi 3.14159)pi⇒3.14159

Los nombres de las funciones ( equal?, sin, `+, …) son también símbolos (los de las macrosno) y Scheme también los evalúa (en un par de semanas hablaremos de las funciones comoobjetos primitivos en Scheme):

sin⇒ procedure:sin+⇒ procedure:+(define (cuadrado x) (* x x))⇒ procedure:cuadrado

Los símbolos son tipos primitivos del lenguaje: pueden pasarse como parámetros o ligarse avariables.

(define x 'hola)x⇒ hola

La forma especial eval es una característica importantísima de los lenguajes funcionales(incluyendo Scheme) que no existe en la mayoría de lenguajes imperativos.

Permite evaluar la expresión que le pasamos como parámetro:

Sintaxis

(eval <expresión>)

Evaluación

Se evalúa la <expresión> y se devuelve el resultado.

Ejemplos

Para que funcionen los siguientes ejemplos hay que cambiar el lenguaje del DrRacket aPretty Big, en R5RS eval necesita argumentos adicionales.

2.3.4. Símbolos como tipos primitivos

2.3.5. La forma especial eval

Page 18: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Probamos los siguientes ejemplos para entender mejor el funcionamiento de símbolos yvalores:

(eval 8)(define a 8)(define b 'a)ab(eval 'a)(eval b)(eval 'b)(eval '(+ 1 2 3))

La forma especial eval permite una característica importante de la programación funcional:la dualidad entre datos y programas.

Los programas en Scheme son expresiones entre paréntesis y una expresión es una lista desímbolos. Esto permite tratar a los programas como datos y viceversa.

Por ejemplo:

(define mi-lista (list '+ 1 2 3 4))(eval mi-lista)

Otro ejemplo: supongamos que queremos sumar una lista de números

(define (suma-lista lista-nums)(eval (cons '+ lista-nums)))

Históricamente, la introducción de eval en Lisp fue el momento en el que se descubrió laposibilidad de hacerlo interpretado.

A continuación vemos un ejemplo de intérprete de Scheme utilizando la forma especialeval . Es una implementación muy sencilla del bucle “Read-Eval-Print” del intérprete de

Scheme. Utiliza alguna forma especial que no hemos visto todavía como let , para definirun nuevo ámbito en el que se declara una nueva variable (en este caso, la variable expr enla que se guarda la expresión que el usuario introduce que se lee con la función read ).

Para que funcione el código en el intérprete DrRacket hay que seleccionar el lenguaje “Muy

2.3.6. Dualidad entre datos y programas

2.3.7. Un intérprete de Scheme en Scheme

Page 19: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

grande” o “Pretty Big”.

¡Cuidado!: el ejemplo utiliza características de programación imperativa como los pasosde ejecución o la forma especial begin .

(define (rep-loop) (display "mi-interprete> ") ; imprime un prompt (let ((expr (read))) ; lee una expresión (if (eq? expr 'adios) ; el usuario quiere parar? (begin (display "saliendo del bucle read-eval-print") (newline)) (begin ; expresión distinta de 'adios (write (eval expr)) ; evaluar e imprimir (newline) (rep-loop)))))

Otra de las características fundamentales del paradigma funcional es la utilización de listas.Repasamos las características y funciones más importantes de Scheme para trabajar conlistas.

Creación de listas: función list y forma especial quote

(define a 1)(define b 2)(define c 3)(define lista2 (list a b c))(define lista1 '(a b c))

Lista vacía: ()

Lista con otras listas como elemento:

(define lista4 '(1 (2 3) (4 5 (6))))

La lista4 tiene 3 elementos:

1La lista ’(2 3)La lista ’(4 5 (6))

2.4. Listas

Page 20: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Selección de elementos de una lista:Primer elemento: función car

Resto de elementos: función cdr

(define lista3 '((1 2) 3 4))(car lista3) ;-> (1 2)(cdr lista3) ;-> (3 4)

Creación de nuevas listas:

Función cons para crear una lista nueva resultado de añadir un elemento alcomienzo de la lista. Esta función es la forma habitual de construir nuevas listas apartir de una lista ya existente y un nuevo elemento.

(cons 1 '(1 2 3 4)) ;-> '(1 1 2 3 4)(cons 'hola '(como estás)) ;-> '(hola como estás)(cons '(1 2) '(1 2 3 4)) ; -> '((1 2) 1 2 3 4)

Función append para crear una lista nueva resultado de concatenar dos o máslistas

(append list1 list2 list3)

Otra característica fundamental de la programación funcional es la no existencia de bucles.Unbucle implica la utilización de pasos de ejecución en el programa y esto es característico de laprogramación imperativa

Las iteraciones se realizan con recursión.

Para entender correctamente la recursión hay que mirarla de forma declarativa, como unadefinición matemática, entendiendo lo que hace la llamada recursiva y confiando en quedevuelve lo que tiene que devolver. No es conveniente entrar en la recursión e intentarcomprobar su funcionamiento haciendo una traza de las sucesivas llamadas.

Confía en la recursión.

Ejemplo típico, factorial:

2.5. Recursión

2.5.1. Función factorial

Page 21: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (factorial x) (if (= x 0) 1 (* x (factorial (- x 1)))))

Una interpretación declarativa (definición matemática) de la llamda recursiva sería:

Para calcular el factorial de x debemos multiplicar x por el factorial de x–1.

Lo cual es cierto, dada la siguiente relación:

factorial x = x * (x–1) * (x–2) * … * 1 = x * factorial (x–1)

Función (suma-hasta x) que suma los números 0+1+2+…+x.

Definición matemática:

Para sumar desde 0 hasta x debemos sumar a x el resultado de sumar desde 0 hasta x–1.

En Scheme:

(define (suma-hasta x) (if (= 0 x) 0 (+ x (suma-hasta (- x 1)))))

Podemos calcular la longitud de una lista con la siguiente expresión recursiva:

La longitud de una lista l es 1 + la longitud del resto de l

En Scheme, la función (longitud lista) que calcula la longitud de una lista:

(define (longitud lista) (if (null? lista) 0 (+ 1 (longitud (cdr lista)))))

2.5.2. Función suma-hasta

2.5.3. Función longitud

2.5.4. Función suma-lista

Page 22: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

La suma de todos los números de una lista de enteros se puede calcular con la siguienteexpresión recursiva:

La suma de los números de una lista es el primer elemento + la suma de los elementosdel resto.

En Scheme:

(define (suma-lista lista) (if (null? lista) 0 (+ (car lista) (suma-lista (cdr lista)))))

Vamos a definir la función (veces pal car) que cuenta el número de veces que apareceun carácter en una palabra (una cadena).

Definimos las funciones auxiliares primero y resto :

(define (primero pal) (string-ref pal 0))

(define (resto pal) (substring pal 1 (string-length pal)))

La función veces se define recursivamente de la siguiente forma:

Para calcular el número de veces que aparece una letra c en una palabra, cuento elnúmero de veces que aparece en el resto de la palabra y le sumo 1 si la primera letracoincide con c.

En Scheme:

(define (veces pal car) (cond ((equal? "" pal) 0) ((equal? (primero pal) car) (+ 1 (veces (resto pal) car)) (else (veces (resto pal) car)))))

Vamos a definir la función (codifica cadena) que transforma una cadena por los

2.5.5. Función recursiva veces

2.5.6. Función recursiva codifica

Page 23: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

siguientes caracteres.

Definimos las funciones siguiente y anterior sobre caracteres

(define (siguiente car) (integer->char (+ 1 (char->integer car))))

(define (anterior car) (integer->char (- (char->integer car) 1)))

Y la función codifica se define como sigue:

Para codificar una palabra concatena la codificación del primer carácter con el resto de lapalabra codificada.

En Scheme:

(define (codifica pal) (if (equal? "" pal) "" (string-append (string (siguiente (primero pal))) (codifica (resto pal)))))

Ejemplo:

(codifica "hola que tal")⇒ "ipmb!rvf!ubm"

La función descodifica hace lo contrario

(define (descodifica pal) (if (equal? "" pal) "" (string-append (string (anterior (primero pal))) (descodifica (resto pal)))))

Ejemplo:

(descodifica "ipmb!rvf!ubm")⇒ "hola que tal"

2.5.7. Ejemplo de uso de la recursión para construir listas

Page 24: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

La función (cuadrados-hasta x) devuelve una lista con los cuadrados de los númeroshasta x:

Para construir una lista de los cuadrados hasta x, añado el cuadrado de x a la lista de loscuadrados hasta x–1

Es muy importante el caso base: si x=1, devuelvo una lista formada por el 1.

En Scheme:

(define (cuadrados-hasta x) (if (= x 1) '(1) (cons (cuadrado x) (cuadrados-hasta (- x 1)))))

Ejemplo:

(cuadrados-hasta 10)⇒ (100 81 64 49 36 25 16 9 4 1)

Es muy habitual recorrer una lista y comprobar condiciones de sus elementos, construyendouna lista con los que cumplan una determinada condición.

Por ejemplo, la siguiente función filtra-pares construye una lista con los números paresde la lista que le pasamos como parámetro:

(define (filtra-pares lista) (cond ((null? lista) '()) ((even? (car lista)) (cons (car lista) (filtra-pares (cdr lista)))) (else (filtra-pares (cdr lista)))))

Ejemplo:

(filtra-pares '(1 2 3 4 5 6))⇒ (2 4 6)

2.5.8. Ejemplo de recursión que construye una lista nueva

2.5.9. Función primo?

Page 25: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Terminamos con un ejemplo completo en el que vamos a construir de forma incremental lafunción primo? que comprueba si un número es primo. En principio no nos va a preocuparla eficiencia; consideradlo sólo un ejemplo de una solución recursiva basada en listas.

Un número es primo cuando sólo tiene dos divisores, el número 1 y él mismo. Definimosentonces la función (primo? x) de la siguiente forma:

(define (primo? x) (= 2 (length (divisores x))))

Suponemos que la función (divisores x) nos devuelve una lista con todos los divisoresdel número x. Vamos a construirla de la siguiente forma:

1. Creamos una lista de todos los números del 1 a x2. Filtramos la lista para dejar los divisores de x

La función (lista-hasta x) devuelve una lista de números 1..x:

(define (lista-hasta x) (if (= x 0) '() (cons x (lista-hasta (- x 1)))))

Definimos la función (divisor? x y) que nos diga si x es divisor de y:

(define (divisor? x y) (= 0 (remainder y x)))

La función (filtra-divisores lista x) devuelve una lista con los números de la listaoriginal que son divisores de x

(define (filtra-divisores lista x) (cond ((null? lista) '()) ((divisor? (car lista) x) (cons (car lista) (filtra-divisores (cdr lista) x))) (else (filtra-divisores (cdr lista) x))))

Por último, la función (divisores x) devuelve la lista con los divisores de un número:

Page 26: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (divisores x) (filtra-divisores (lista-hasta x) x))

Os propongo como ejercicio que encontréis una solución más eficiente.

En Scheme el tipo de dato compuesto más simple es la pareja: una entidad formada por doselementos. Se utiliza la función cons para construirla:

(cons 1 2) -> (1 . 2)(define c (cons 1 2))

Dibujamos una pareja de la siguiente forma:

Tipo compuesto pareja

La instrucción cons construye un dato compuesto a partir de otros dos datos (quellamaremos izquierdo y derecho). La expresión (1 . 2) es la forma que el intérprete tienede imprimir las parejas.

Una vez definida una pareja, podemos obtener el elemento correspondiente a su parteizquierda con la función car y su parte derecha con la función cdr :

(define c (cons 1 2))(car c) ; -> 1(cdr c) ; -> 2

Las funciones cons , car y cdr quedan perfectamente definidas con las siguientes

3. Tipos de datos compuestos en Scheme

3.1. El tipo de dato pareja

Función de construcción de parejas cons

Funciones de acceso car y cdr

Definición declarativa

Page 27: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

ecuaciones algebraicas:

(car (cons x y)) = x(cdr (cons x y)) = y

Inicialmente los nombres eran CAR y CDR (en mayúsculas). La historia se remonta al año1959, en los orígenes del Lisp y tiene que ver con el nombre que se les daba a ciertosregistros de la memoria del IBM 709.

Podemos leer la explicación completa en The origin of CAR and CDR in LISP.

Ya hemos comprobado que Scheme es un lenguaje débilmente tipeado. Las funciones puedendevolver y recibir distintos tipos de datos.

Por ejemplo, podríamos definir la siguiente función suma que sume tanto números comocadenas:

(define (suma x y) (cond ((and (number? x) (number? y)) (+ x y)) ((and (string? x) (string? y)) (string-append x y)) (else 'error)))

En la función anterior los parámetros x e y pueden ser números o cadenas (o incluso decualquier otro tipo). Y el valor devuelto por la función será un número, una cadena o el símbolo'error .

Sucede lo mismo con el contenido de las parejas. Es posible guardar en las parejas cualquiertipo de dato y combinar distintos tipos. Por ejemplo:

(define c (cons 'hola #f))(car c) -> 'hola(cdr c) -> #f

La función pair? nos dice si un objeto es atómico o es una pareja:

¿De dónde vienen los nombres car y cdr

Las parejas pueden contener cualquier tipo de dato

Función pair?

Page 28: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(pair? 3) -> #f(pair? (cons 3 4)) -> #t

Recordemos que en los paradigmas de programación declarativa y funcional no existe elestado mutable. Una vez declarado un valor, no se puede modificar. Esto debe sucedertambién con las parejas: una vez creada una pareja no se puede modificar su contenido.

En Lisp y Scheme estándar (R5RS) las parejas sí que pueden ser mutadas. Pero durante todaesta primera parte de la asignatura no lo contemplaremos, para no salirnos del paradigmafuncional.

En Scala y otros lenguajes de programación es posible definir estructuras de datosinmutables que no pueden ser modificadas una vez creadas. Lo veremos también másadelante.

En un lenguaje de programación un elemento es de primera clase cuando puede:

Asignarse a variablesPasarse como argumentoDevolverse por una funciónGuardarse en una estructura de datos mayor

Las parejas son objetos de primera clase.

Una pareja puede asignarse a una variable:

(define p1 (cons 1 2))(define p2 (cons #f "hola"))

Una pareja puede pasarse como argumento y devolverse en una función:

(define (suma-parejas p1 p2) (cons (+ (car p1) (car p2)) (+ (cdr p1) (cdr p2))))

(suma-parejas '(1 . 5) '(4 . 12))⇒ (5 . 17)

Las parejas son objetos inmutables

3.2. Las parejas son objetos de primera clase

Page 29: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Y, por último, las parejas pueden formar parte de otras parejas. Es lo que se denomina lapropiedad de clausura de la función cons :

El resultado de un cons puede usarse como parámetro de nuevas llamadas acons .

Ejemplo:

(define p1 (cons 1 2))(define p2 (cons 3 4))(define p (cons p1 p2))

Expresión equivalente:

(define p (cons (cons 1 2) (cons 3 4)))

Podríamos representar esta estructura así:

Propiedad de clausura: las parejas pueden contener parejas

Pero se haría muy complicado representar muchos niveles de anidamiento. Por eso utilizamosla siguiente representación, entendiendo las flechas como contiene y no como referencia.

Las flechas denotan contenido

Llamamos a estos diagramas diagramas caja-y-puntero (box-and-pointer en inglés).

3.3. Diagramas caja-y-puntero

Page 30: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Al escribir expresiones complicadas con cons anidados es conveniente para mejorar sulegibilidad utilizar el siguiente formato:

(define p (cons (cons 1 (cons 3 4)) 2))

Para entender la construcción de estas estructuras es importante recordar que las expresionesse evalúan de dentro a afuera.

¿Qué figura representaría la estructura anterior?

Solución:

Es conveniente que pruebes a crear distintas estructuras de parejas con parejas y a dibujar sudiagrama caja y puntero. Y también a recuperar un determinado dato (pareja o dato atómico)una vez creada la estructura.

La función print-pareja que vimos en el seminario de Scheme puede ser útil:

Page 31: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (print-pareja pareja) (if (pair? pareja) (begin (display "(") (print-dato (car pareja)) (display " . ") (print-dato (cdr pareja)) (display ")"))))

(define (print-dato dato) (if (pair? dato) (print-pareja dato) (display dato)))

Repetimos lo mismo que dijimos entonces: la función de arriba contiene sentencias comobegin o display propias de la programación imperativa; no usarlas en

programación funcional.

Al trabajar con estructuras de parejas anidades es muy habitual realizar llamadas del tipo:

(cdr (cdr (car p)))⇒ 4

Es equivalente a la función cadar de Scheme:

(cddar p)⇒ 4

El nombre de la función se obtiene concatenando a la letra “c”, las letras “a” o “d” segúnhagamos un car o un cdr y terminando con la letra “r”.

Hay definidas 24 funciones de este tipo: caaaar , caaadr , …, cddddr .

Recordemos que Scheme permite manejar listas como un tipo de datos básico. Hemos vistofunciones para crear, añadir y recorrer listas.

Funciones c????r

4. Listas en Scheme

4.1. Implementación de listas en Scheme

Page 32: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Como repaso, podemos ver las siguientes expresiones. Fijaros que las funciones car ,cdr y cons son exactamente las mismas funciones que las vistas anteriormente.

¿Por qué? ¿Qué relación hay entre las parejas y las listas?

(list 1 2 3 4)'(1 2 3 4)

(define hola 1)(define que 2)(define tal 3)

(list hola que tal)'(hola que tal)

(define a '(1 2 3))(car a)(cdr a)(length a)(length '())(cons 1 '(1 2 3 4))(append '(1) '(2 3 4) '(5 6 7) '())

Ya debes haber descubierto la relación: en Scheme las listas se implementan con parejas.

Una lista es (definición recursiva):

Una pareja que contiene en su parte izquierda el primer elemento de la lista y en su partederecha el resto de la listaUn símbolo especial “’()” que denota la lista vacía

Por ejemplo, una lista muy sencilla con un solo elemento, ’(1), se define con la siguientepareja:

(cons 1 '())

La pareja cumple las condiciones anteriores:

La parte izquierda de la pareja es el primer elemento de la lista (el número 1)La parte derecha es el resto de la lista (la lista vacía)

Definición de listas con parejas

Page 33: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

La lista ’(1)

El objeto es al mismo tiempo una pareja y una lista. La función list? permite comprobar siun objeto es una lista:

(define l (cons 1 '()))(pair? l)(list? l)

Por ejemplo, la lista ’(1 2 3 4) se construye con la siguiente secuencia de parejas:

(cons 1 (cons 2 (cons 3 (cons 4 '()))))

La primera pareja cumple las condiciones de ser una lista:

Su primer elemento es el 1Su parte derecha es la lista ’(2 3 4)

Parejas formando una lista

Al comprobar la implementación de las listas en Scheme, entendemos por qué las funcionescar y cdr nos devuelven el primer elemento y el resto de la lista.

La lista vacía es una lista:

(list? '())⇒ #t

Lista vacía

Page 34: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Y no es un símbolo ni una pareja:

(symbol? '())⇒ #f(pair? '())⇒ #f

Para saber si un objeto es la lista vacía, podemos utilizar la función null? :

(null? '())⇒ #t

Las listas pueden contener cualquier tipo de elementos, incluyendo otras parejas.

La siguiente estructura se denomina lista de asociación. Son listas cuyos elementos sonparejas (clave, valor):

(list (cons 'a 1) (cons 'b 2) (cons 'c 3))⇒ ((a.1)(b.2)(c.2))

¿Cuál sería el diagrama box and pointer de la estructura anterior?

La expresión equivalente utilizando conses es:

(cons (cons 'a 1) (cons (cons 'b 2) (cons (cons 'c 3) '())))

4.2. Listas con elementos compuestos

Page 35: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Si la pareja que guardamos como elemento de la lista es la cabeza de otra lista tenemos unalista que contiene a otra lista:

(define lista (list 1 (list 1 2 3) 3))

La lista anterior también se puede definir con quote:

(define lista '(1 (1 2 3) 3))

El diagrama box and pointer de la lista es:

Lista que contiene otra lista como segundo elemento

Es muy importante utilizar el nivel de abstracción correcto a la hora de trabajar con listas quecontienen otros elementos compuestos, como otras parejas u otras listas.

Sólo hace falta bajar al nivel de caja y puntero cuando estemos definiendo funciones de bajonivel que tratan la estructura de datos para obtener elementos concretos de las parejas.

Si, por el contrario, estamos recorriendo la lista principal y queremos tratar sus elementos,debemos verla como una lista normal y recorrerla con las funciones car para obtener suprimer elemento y cdr para obtener el resto. O list-ref para obtener un elementodeterminado (que puede ser atómico o compuesto).

Por ejemplo, la lista anterior (1 (1 2 3) 3) es una lista de 3 elementos. Si queremosobtener su segundo elemento (la lista (1 2 3) ) bastaría con:

(define lista '(1 (1 2 3) 3))(car (cdr lista))(list-ref lista 1)

Listas de listas

Distintos niveles de abstracción

Page 36: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Una vez vista la estructura interna de una lista, es posible entender completamente elfuncionamiento de funciones que construyen listas, como append . Recordemos la funciónconstruye una lista en la que se concatenan las listas que se pasan como argumento. Veamoscomo se podría implementar utilizando cons:

(define (mi-append l1 l2) (if (null? l1) l2 (cons (car l1) (mi-append (cdr l1) l2))))

Como ejercicio te sugerimos dibujar los diagramas caja-y-puntero de dos listas antes ydespués de llamar a la función mi-append .

Verás que mi-append construye la nueva lista creando tantas parejas nuevas comoelementos de la primera lista y colocando en la última pareja construida una referencia a lasegunda lista. La segunda lista no se recorre, sino que directamente se coloca en la partederecha de la última pareja construida.

Veamos cómo implementar algunas funciones más sobre listas

La función length :

(length '(1 2 3 (4 5 6)))⇒ 4

Una implementación:

(define (mi-length items) (if (null? items) 0 (+ 1 (mi-length (cdr items)))))

La función (list-ref n lista) devuelve el elemento enésimo de una lista (empezandoa contar por 0):

4.3. Funciones recursivas sobre listas

length

list-ref

Page 37: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define lista '(1 2 3 4 5 6))(list-ref lista 3)⇒ 4

Una implementación:

(define (mi-list-ref lista n)(cond ((null? lista) (error "indice demasiado largo")) ((= n 0) (car lista)) (else (mi-list-ref (cdr lista) (- n 1)))))

La función (list-tail lista n) devuelve la lista resultante de quitar n elementos de lalista original:

(list-tail '(1 2 3 4 5 6 7) 2)⇒ (3 4 5 6 7)

Una implementación:

(define (mi-list-tail lista n) (cond ((null? lista) (error "indice demasiado largo")) ((= n 0) lista) (else (mi-list-tail (cdr lista) (- n 1)))))

La función reverse invierte una lista

(reverse '(1 2 3 4 5 6))⇒ (6 5 4 3 2 1)

Una implementación:

(define (mi-reverse l) (if (null? l) '() (append (mi-reverse (cdr l)) (list (car l)))))

list-tail

reverse

4.4. Funciones con número variable de argumentos

Page 38: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Hemos visto algunas funciones primitivas de Scheme, como + o max que admiten unnúmero variable de argumentos. ¿Podemos hacerlo también en las funciones definidas pornosotros?

La respuesta es sí, utilizando lo que se denomina notación dotted-tail (punto-cola) para definirlos parámetros de la función. En esta notación se coloca un punto antes del último parámetro.Los parámetros antes del punto (si existen) tendrán como valores los argumentos usados en lallamada y el resto de argumentos se pasarán en forma de lista en el último parámetro.

Por ejemplo, si tenemos la definición

(define (f x y . z) <cuerpo>)

podemos llamar al procedimiento f con dos o más argumentos:

(f 1 2 3 4 5 6)

En la llamada, los parámetros x e y tomarán los valores 1 y 2. El parámetro z tomarácomo valor la lista (3 4 5 6) .

También es posible permitir que todos los argumentos sean opcionales:

(define (g . w) <cuerpo>)

Si hacemos la llamada

(g 1 2 3 4 5 6)

el parámetro w tomará como valor la lista (1 2 3 4 5 6) .

El elemento principal de la programación funcional es la función. Hemos aprendido a definirfunciones que no producen efectos laterales, que no tienen estado, que toman unos objetoscomo entrada y producen otros objetos como salida.

Para simbolizar el hecho de que las funciones toman parámetros de entrada y devuelven unaúnica salida, vamos a representar las funciones como un símbolo especial, una pequeña casainvertida con unas flechas en la parte superior que representan las entradas y una única flechaque representa la salida. Por ejemplo, la función que eleva al cuadrado un número,multiplicándolo por si mismo, la representaremos de la siguiente forma:

5. Funciones como tipos de datos de primera clase

Page 39: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Una de las características fundamentales de la programación funcional es considerar a lasfunciones como objetos de primera clase (entendemos la palabra objeto no como un objeto deun lenguaje OO sino en su acepción más genérica de elemento de un lenguaje deprogramación o de un programa en ejecución). Recordemos que un tipo de primera clase esaquel que:

Puede ser asignado a una variablePuede ser pasado como argumento a una funciónPuede ser devuelto como resultado de una invocación a una funciónPuede ser parte de un tipo mayor

Por ejemplo, en Scheme, al igual que en la mayoría de lenguajes de programación, los valoresde booleanos, enteros, caracteres, cadenas, etc. cumplen las condiciones anteriores y sonobjetos o tipos de primera clase.

Vamos a ver que las funciones son también objetos de primera clase: vamos a poder crearfunciones sin nombre y asignarlas a variables, pasarlas como parámetro de otras funciones yguardarlas en tipos de datos compuestos como listas.

La posibilidad de usar funciones como objetos de primera clase es una característicafundamental de los lenguajes funcionales. Es una característica de muchos lenguajes multi-paradigma con características funcionales como JavaScript, Python, Objective-C (donde sedenominan blocks) o incluso en la última versión de Java, Java 8, (donde se denominanexpresiones lambda).

Todos los objetos de primera clase se pueden crear en tiempo de ejecución:

(define mi-cadena "hola")(+ 2 3)

5.1. Forma especial lambda

Page 40: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

La cadena "hola" o los números 2 y 3 se crean en tiempo de ejecución y después seasignan a una variable o se pasan como parámetros de una función. Cuando en el intérpreteescribimos "hola" estamos creando una cadena anónima que después vamos a utilizar(darle nombre con un símbolo o pasarla como parámetro). Cualquier tipo de primera clasedebe poder ser creado de esta forma.

¿Es posible hacer lo mismo con funciones en Scheme?

Sí, la forma especial lambda es la forma en Scheme (y Lisp) de construir funciones entiempo de ejecución sin darles un nombre. Por ejemplo, la siguiente expresión crea unafunción sin nombre con un argumento x que eleva al cuadrado su argumento

(lambda (x) (* x x))⇒ #<procedure>

La sintaxis de la forma especial lambda es:

(lambda (<arg1> ... <argn>) <cuerpo>)

El cuerpo del lambda define un bloque de código y sus argumentos son los parámetrosnecesarios para ejecutar ese bloque de código.

Es importante darse cuenta que el código creado por lambda se crea en tiempo deejecución. La invocación a lambda devuelve un procedimiento y hay que hacer algo con él.

Podemos darle un nombre a la función y después invocarla:

(define cuadrado (lambda (x) (* x x)))(cuadrado 10)⇒ 100

También podemos invocar la función sin darle un nombre:

((lambda (x) (* x x)) 3) ⇒ 9

El paréntesis a la izquierda de lambda invoca a la forma especial y construye la funciónanónima y el primer paréntesis de la expresión invoca a la función creada por el lambda

Sintaxis de la forma especial lambda

Con la función creada por lambda podemos

Page 41: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

con el argumento 3.

Y también podemos pasarla como parámetro de otra función, como en el siguiente ejemplo,que suma todos los números de una lista después de aplicarles a cada uno la función que sepasamos como parámetro:

(define (suma-lista-f f lista) (if (null? lista) 0 (+ (f (car lista)) (suma-lista-f f (cdr lista)))))

(suma-lista-f (lambda (x) (* x x)) '(1 2 3 4 5))⇒ 55

Es importante remarcar que con lambda estamos creando una función en tiempo deejecución. Es código que creamos para su posterior invocación.

No hay que dejar que la sintaxis o la palabra lambda confunda. Lo único que estamoshaciendo es crear un bloque de código y definir sus argumentos. Quizás ayuda ver cómo serealiza la definición anterior en distintos lenguajes.

Java 8

Integer x -> {x*x}

Scala

(x:Int) => {x*x}

Objective C

^int (int x){ x*x};

Tras conocer lambda ya podemos explicarnos por qué cuando escribimos en el intérpretede Scheme el nombre de una función, se evalúa a un procedure:

¿Qué hay en el nombre de una función?

Page 42: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

> +⇒ <procedure:+>

El identificador se evalúa y devuelve el objeto función al que está ligado. En Scheme losnombres de las funciones son realmente símbolos a los que están ligados objetos de tipofunción. Gracias a esto podemos asignar funciones ya existentes a nuevos identificadoresusando define , como en el ejemplo siguiente:

> +⇒ <procedure:+>> (define suma +)> (suma 1 2 3 4)⇒ 10

Es muy importante darse cuenta que la expresión (define suma +) se evalúa de formaidéntica a (define y x) . Primero se evalúa el identificador + , que devuelve el objetofunción suma, que se asigna a la variable suma . El resultado final es que tanto + comosuma tienen como valor el mismo procedimiento:

La forma especial define para definir una función no es más que azucar sintáctico. Laforma especial

(define (<nombre> <args>) <cuerpo>)

siempre se convierte internamente en:

(define <nombre> (lambda (<args>) <cuerpo>))

Por ejemplo

Azucar sintáctico

Page 43: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (cuadrado x) (* x x))

es equivalente a:

(define cuadrado (lambda (x) (* x x)))

Podemos comprobar si algo es una función utilizando el predicado de Schemeprocedure? .

Por ejemplo:

(procedure? (lambda (x) (* x x)))⇒ #t(define suma +)(procedure? suma)⇒ #t(procedure? '+)⇒ #f

Hemos visto que las funciones pueden asignarse a variables. También cumplen las otrascondiciones necesarias para ser consideradas objetos de primera clase.

Por ejemplo, podemos definir la función aplica que toma una función f y dos valoresx e y y devuelve el resultado de invocar a la función f con x e y :

(define (aplica f x y) (f x y))

(aplica + 2 3) ⇒ 5(aplica * 4 5) ⇒ 10(aplica string-append "hola" "adios") ⇒ "holaadios"(aplica (lambda (x y) (sqrt (+ (* x x) (* y y)))) 3 4) ⇒ 5

Otro ejemplo, la función aplica-2 que toma dos funciones f y g y un argumento x

Predicado procedure?

5.2. Las funciones son objetos de primera clase en Scheme

Una función puede ser el argumento de otra función

Page 44: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

y devuelve el resultado de aplicar f a lo que devuelve la invocación de g con x :

(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)⇒ 11

La siguiente función hacer-sumador-5 que crea y devuelve una función que suma 5 a suentrada.

(define (hacer-sumador-5) (lambda (x) (+ x 5))) (define g (hacer-sumador-5))

Cuando llamamos a (define g (hacer-sumador-5)) se crea en tiempo de ejecuciónuna función que se guarda en la variable g . Es una función de un argumento que suma 5 asu entrada.

Podemos invocarla como cualquier otra función:

(g 10)⇒ 15

La última característica de los tipos de primera clase es que pueden formar parte de tipos dedatos compuestos, como una pareja. Por ejemplo, podemos crear una pareja que contenga en

Una función puede ser el valor devuelto por otra

Una función pueden formar parte de otras estructuras de datos

Page 45: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

su parte izquierda una función y en su parte derecha un dato:

(define p1 (cons (lambda (x) (+ x 3)) 5))

¿Cómo podríamos llamar a la función de la parte izquierda pasándole el valor de la derechacomo argumento?:

((car p1) (cdr p1)) ⇒ 8

Podemos hacer más legible la expresión anterior encapsulándola en una funciónprocesa-pareja :

(define (procesa-pareja p) ((car p) (cdr p)))

De forma que podríamos llamar a procesa-pareja para evaluar el contenido de unapareja con estas características:

(define p1 (cons (lambda (x) (+ x 3)) 5))(define p2 (cons cuadrado 10))

(procesa-pareja p1)⇒ 8(procesa-pareja p2)⇒ 100

Por último, veamos un ejemplo en el que guardamos funciones en listas.

Definimos una función (aplica-funcs lista-funcs x) que aplica de forma sucesivatodas las funciones de lista-funcs al número x .

Por ejemplo, si en lista-funcs tenemos:

(cuadrado cubo suma-1)

Page 46: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

La llamada a (aplica-funcs lista-funcs 5) debería devolver:

(cuadrado (cubo (suma-1 5))46656

El caso general de la recursión de la función aplica-funcs se define como:

Para aplicar una lista de funciones a un número debemos aplicar la primera función alresultado de aplicar el resto de funciones al número

El caso base sería en el que la lista de funciones tiene sólo una función.

La implementación en Scheme es:

(define (aplica-funcs lista-funcs x) (if (null? (cdr lista-funcs)) ((car lista-funcs) x) ((car lista-funcs) (aplica-funcs (cdr lista-funcs) x))))

Hay que notar los paréntesis en la expresión ((car lista-funcs) x) . Se obtiene laprimera función de la lista y la aplica al argumento x .

Un ejemplo de uso de la función:

(define lista-funcs (list (lambda (x) (* x x)) (lambda (x) (* x x x)) (lambda (x) (+ x 1))))(aplica-funcs lista-funcs 5)⇒ 46656

La función apply es un ejemplo de una función definida en Scheme que toma comoparámetro otra función. En concreto toma dos parámetros: una función y una lista deelementos:

(apply f lista)

Devuelve el resultado de llamar a la función f con la lista de argumentos como parámetros.Por ejemplo:

Función apply

Page 47: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(apply + '(1 2 3 4))⇒ 10(apply cuadrado '(3))⇒ 9

Cuidado, los siguientes ejemplos no funcionan. Piensa por qué:

(apply 'cuadrado '(3))(apply cuadrado 3)

La posibilidad de pasar funciones como parámetros de otras es una poderosa herramienta deabstracción. Por ejemplo, supongamos que queremos calcular el sumatorio de a hasta b :

(define (sum-x a b) (if (> a b) 0 (+ a (sum-x (+ a 1) b))))

(sum-x 1 10)⇒ 55

Supongamos ahora que queremos calcular el sumatorio de a hasta b sumando losnúmeros al cuadrado:

(define (sum-cuadrado-x a b) (if (> a b) 0 (+ (* a a) (sum-cuadrado-x (+ a 1) b))))

(sum-cuadrado-x 1 10)⇒ 385

Y el sumatorio de a hasta b sumando los cubos:

5.3. Funciones de orden superior

Generalización

Page 48: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (sum-cubo-x a b) (if (> a b) 0 (+ (* a a a) (sum-cubo-x (+ a 1) b))))

(sum-cubo-x 1 10)⇒ 3025

Vemos que el código de las tres funciones anteriores es muy similar, cada función la podemosobtener haciendo un copy-paste de otra previa. Lo único que cambia es la función a aplicar acada número de la serie.

Siempre que hagamos copy-paste al programar tenemos que empezar a sospechar que noestamos generalizando suficientemente el código. Un copy-paste arrastra también bugs yobliga a realizar múltiples modificaciones del código cuando en el futuro tengamos quecambiar cosas.

La posibilidad de pasar una función como parámetro es una herramienta poderosa a la hora degeneralizar código. En este caso, lo único que cambia en las tres funciones anteriores es lafunción a aplicar a los números de la serie. Podemos tomar esa función como un parámetroadicional y definir una función genérica sum-f-x que generaliza las tres funcionesanteriores. Tendríamos el sumatorio desde a hasta b de f(x) :

(define (sum-f-x f a b) (if (> a b) 0 (+ (f a) (sum-f-x f (+ a 1) b))))

Las funciones anteriores son casos particulares de esta función que las generaliza. Porejemplo, para calcular el sumatorio desde 1 hasta 10 de x al cubo:

(define (cubo x) (* x x x))

(sum-f-x cubo 1 10)⇒ 3025

Llamamos funciones de orden superior (higher order functions en inglés) a las funciones quetoman otras como parámetro o devuelven otra función. Permiten generalizar soluciones con unalto grado de abstracción.

Funciones de orden superior

Page 49: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Los lenguajes de programación funcional como Scheme, Scala o Java 8 tienen ya predefinidasalgunas funciones de orden superior que permiten tratar listas o streams de una forma muyconcisa y compacta.

Vamos a comenzar viendo la función map de Scheme, como ejemplo típico de función deorden superior. Después definiremos nosotros las funciones filter y fold . Yterminaremos viendo cómo la utilización de funciones de orden superior es una excelenteherramienta de la programación funcional que permite hacer código muy conciso y expresivo.

La combinación de funciones de nivel superior con listas es una de las características máspotentes de la programación funcional.

Veamos la función de Scheme (map funcion lista) como un primer ejemplo. Lafunción map toma como parámetros una función y una lista. Devuelve una nueva listaresultante de aplicar la función a cada uno de los elementos de la lista inicial.

(map cuadrado '(1 2 3 4 5))⇒ (1 4 9 25)

También es posible que la función a mapear tenga más de un parámetro. En ese caso hay quepasarle como argumentos tantas listas como parámetros tenga la función. Los elementos delas listas se irán cogiendo de forma correlativa como parámetros de la función que se mapea:

(define (suma x y) (+ x y))(map suma '(1 2 3) '(4 5 6))⇒ (5 7 9)(map * '(1 2 3) '(4 5 6))⇒ (4 10 18)

Otro ejemplo, en el que map toma como parámetro una función anónima creada conlambda para aplicarla a una lista:

(map (lambda (x) (+ x 5)) '(1 2 3)')⇒ (6 7 8)

Podríamos definir una función en la que definimos como parámetro el número a sumar:

Función map

Page 50: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (suma-n lista n) (map (lambda (x) (+ x n)) lista))

(suma-n '(1 2 3 4) 10)⇒ (11 12 13 14)

Un buen ejercicio es intentar implementar la función map nosotros, de forma recursiva.Llamamos a la función mi-map para no confundirla con la función map de Scheme:

(define (mi-map f lista) (if (null? lista) '() (cons (f (car lista)) (mi-map f (cdr lista)))))

Vamos a definir una nueva función de orden superior que trabaja sobre listas.

La función (filter predicado lista) toma como parámetro un predicado y una lista ydevuelve como resultado los elementos de la lista que cumplen el predicado.

Se implementa de la siguiente forma:

(define (filter pred lista) (cond ((null? lista) '()) ((pred (car lista)) (cons (car lista) (filter pred (cdr lista)))) (else (filter pred (cdr lista)))))

Un ejemplo de uso:

(filter even? '(1 2 3 4 5 6 7 8))⇒ (2 4 6 8)

Por último vamos a definir una potente función de orden superior, que permite recorrer unalista aplicando una función binaria de forma acumulativa a sus elementos.

Implementación de map

Función filter

Función fold

Page 51: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Se trata de la función (fold func base lista) (función plegado) que toma comoparámetros una función binaria func , un caso base a aplicar con el último elemento de lalista, y una lista de elementos.

La definición recursiva es:

Para hacer el fold de una función f de dos argumentos y de una lista, debemos llamara f con el primer elemento de la lista y el resultado de hacer el fold del resto de la lista.En el caso en que la lista no tenga elementos, se devolverá el caso base.

Por ejemplo:

(fold + 0 '(1 2 3))⇒ (+ 1 (fold + 0 '(2 3)))⇒ (+ 1 (+ 2 (fold + 0 '(3))))⇒ (+ 1 (+ 2 (+ 3 (fold + 0 '()))))⇒ (+ 1 (+ 2 (+ 3 0)))⇒ 6

La implementación recursiva en Scheme es la siguiente:

(define (fold func base lista) (if (null? lista) base (func (car lista) (fold func base (cdr lista)))))

Podemos comprobar la potencia de la función fold con los siguientes ejemplos:

(fold string-append "" '("hola" "que" "tal"))⇒ "holaquetal"(fold (lambda (x y) (* x y)) 1 '(1 2 3 4 5 6 7 8))⇒ 40320(fold cons '() '(1 2 3 4))⇒ (1 2 3 4)

El uso de funciones de orden superior permite realizar un código muy conciso y expresivo. Yano es necesario escribir la recursión de forma explícita, sino que la función de orden superiores la que se encarga de abstraerla.

Por ejemplo, supongamos que queremos definir la función(contienen-letra caracter lista-pal) que devuelve las palabras de una lista que

Uso de funciones de orden superior

Page 52: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

contienen un determinado carácter.

Por ejemplo:

(contienen-letra #\a '("En" "un" "lugar" "de" "la" "Mancha")) ⇒ ("lugar" "la" "Mancha")

Podemos implementar contienen-letra usando la función de orden superior filter ,y pasando como función de filtrado una función auxiliar que creamos con la llamada alambda y que comprueba si la palabra contiene el carácter:

(define (contienen-letra caracter lista-pal) (filter (lambda (pal) (letra-en-pal? caracter pal)) lista-pal))

Sólo nos falta implementar la función letra-en-pal? que comprueba si una palabracontiene un carácter. Usando las funciones primero u resto definidas anteriormenteque nos devuelven el primer carácter de una cadena y el resto de la cadena, la podemosimplementar con una recursión sencilla:

(define (letra-en-pal? caracter palabra) (cond ((equal? "" palabra) #f) ((equal? caracter (primero palabra)) #t) (else (letra-en-pal? caracter (resto palabra)))))

Ahora que hemos introducido la forma especial lambda y la idea de que una función es unobjeto de primera clase, podemos revisar el concepto de ámbito de variables e introducir unimportante concepto: clausura (closure en inglés).

Vamos a definir un nuevo modelo de evaluación de expresiones, que contemple la posibilidadde definir variables locales y variables globales.

Definimos el concepto de ámbito como un conjunto de asociaciones entre variables y valorescreados en tiempo de ejecución. Representamos gráficamente un ámbito como una caja conlas variables definidas y sus valores. En los ámbitos se pueden evaluar expresiones y las

6. Ambito de variables, let y closures

6.1. Ámbitos de variables

Ámbito global de variables

Page 53: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

variables toman el valor de la variable en ese ámbito.

En Scheme existe un ámbito global de las variables en el que se les da valor utilizando laforma especial define.

(define a "hola")(define b (string-append "adios" a))(define cuadrado (lambda (x) (* x x)))

Todas estas variables se definen en el ámbito global.

Además del ámbito global definimos ámbitos locales.

Cuando se invoca a una función se crea un ámbito local en el que los argumentos de lafunción toman los valores de los parámetros usados en la llamada a la función y en el que seevalúa el cuerpo.

Por ejemplo, supongamos las expresiones:

(define x 5)(define (suma-3 x) (+ x 3))

(suma-3 12)⇒ 15

La invocación a (suma-3 12) crea un ámbito local en el que la variable x (el argumentode suma-3 ) toma el valor 12. Dentro de ese ámbito se evalúa el cuerpo de la función, laexpresión (+ x 3) , devolviéndose el valor 15. Escribimos a la derecha del ámbito lasexpresiones que se evalúan en él.

La siguiente figura representa los ámbitos creados:

Ámbito local

Page 54: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

En el ámbito local también se pueden utilizar variables definidas en el ámbito superior. Porejemplo:

(define y 12)(define (suma-3-bis x) (+ x y 3))

(suma-3-bis 5) ⇒ 20

La expresión (+ x 3 y) se evalúa en el ámbito local en el que x vale 5, pero y noestá definida. Se utiliza la definición de y en el ámbito superior:

¿Qué sucede cuando creamos una función dentro un ámbito local?

Supongamos que definimos la siguiente función (make-sumador k) :

6.2. Clausuras

Page 55: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (make-sumador k) (lambda (x) (+ x k)))

(define f (make-sumador 10))

La llamada a (make-sumador 10) crea un ámbito local en el que k vale 10. Dentro deese ámbito se realiza la llamada a (lambda (x) (+ x k)) que crea una función entiempo de ejecución, la devuelve y queda ligada a la variable f .

Podríamos representar el resultado con el siguiente diagrama de ámbitos:

Supongamos que ahora invocamos a (f 8) . El parámetro de f , x , toma el valor 8.¿Qué valor va a devolver esta expresión? ¿En qué ámbito se va a evaluar el cuerpo(+ x k) ? ¿Cuánto valdrá k ?

(f 8)⇒ 18

Lo que ha sucedido es que la función a la que apunta f es una clausura y su invocación seevalúa en el mismo ámbito local en el que la función se ha creado. La variable local k quedaencerrada en el ámbito junto con la función devuelta por lambda .

El dibujo de ámbitos sería el siguiente:

Page 56: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Lo que hemos visto se define con el nombre de closure: una función creada en tiempo deejecución junto con el entorno en el que ésta se ha creado.

Mediante el uso de clausuras es posible asociar una función con una o más variables libres aun ámbito local en el que se definen estas variables.

En muchos lenguajes de programación modernos existe el concepto de closure, aunquepueden recibir nombre variados.

Por ejemplo, en Objective-C recibe el nombre de block:

Closures

Page 57: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

#include <stdio.h> #include <Block.h> typedef int (^IntBlock)();

IntBlock MakeCounter(int start, int increment) { __block int i = start;

return Block_copy( ^ { int ret = i; i += increment; return ret; }); }

int main(void) { IntBlock mycounter = MakeCounter(5, 2); printf("First call: %d\n", mycounter()); printf("Second call: %d\n", mycounter()); printf("Third call: %d\n", mycounter());

/* because it was copied, it must also be released */ Block_release(mycounter);

return 0; } /* Output: First call: 5 Second call: 7 Third call: 9 */

Muchos lenguajes de programación permiten utilizar código como objeto de primera clase ypasarlo como parámetro a otras funciones. Este tipo de funciones también recibe el nombre defunciones anónimas (ver un interesante artículo en la Wikipedia con ejemplos en muchoslenguajes de programación).

Es muy importante tener en cuenta de que no todas las funciones anónimas usan el mismotipo de semántica. En lenguajes dinámicos como Scheme, las funciones son creadas entiempo de ejecución, quedando encerradas en el ámbito en que han sido creadas yejecutándose precisamente en ese ámbito. También sucede en otros lenguajes comoObjective-C. Es lo que se llama deep binding y cuyos efectos hemos visto en los ejemplosanteriores.

Funciones anónimas

Page 58: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

En otros lenguajes las funciones anónimas se implementan mediante código pre-compiladoque se evalúa en el ámbito en el que finalmente se ejecuta. Esto no daría lugar a una clausura.

En Scheme se define la forma especial let que permite crear un ámbito local en el que se davalor a variables.

Sintaxis:

(let ((<var1> <exp-1>) ... (<varn> <exp-n>)) <exp>)

Las variables var1, … varn toman los valores devueltos por las expresiones exp1, … expn y laexp se evalúa con esos valores.

Por ejemplo:

(let ((x 1) (y 2)) (+ x y))

El uso de let permite crear variables locales en las funciones, aumentando la legibilidad delos programas.

Por ejemplo:

(define (distancia x1 y1 x2 y2) (let ((dx (- x2 x1)) (dy (- y2 y1))) (sqrt (+ (cuadrado dx) (cuadrado dy)))))

Otro ejemplo:

6.3 Forma especial let

Forma especial let

Let mejora la legibilidad de los programas

Page 59: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

(define (intersecta-intervalo a1 a2 b1 b2) (let ((dentro-b1 (and (>= b1 a1) (<= b1 a2))) (dentro-b2 (and (>= b2 a1) (<= b2 a2)))) (or dentro-b1 dentro-b2)))

Las variables definidas en el let sólo tienen valores en el ámbito de la forma especial. Elfuncionamiento es idéntico al que hemos visto anteriormente de una llamada a una función.

(define x 5) (let ((x 1) (y 2)) (+ x y)) ⇒ 3 x ⇒ 5 y ⇒ error, no definida

Cuando ha terminado la evaluación del let el ámbito local desaparece y quedan los valoresdefinidos en el ámbito superior.

Al igual que en la invocación a funciones, desde el ámbito definido por el let se puede usarel á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))

El diagrama de ámbitos que representa gráficamente lo que sucede con las expresionesanteriores es el siguiente:

El ámbito de las variables definidas en el let es local

Let permite usar variables definidas en un ámbito superior

Page 60: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Las expresiones del let se evalúan todas antes de asociar ningún valor con las variables. Nose realiza una asignación secuencial:

(define x 1) (let ((w (+ x 3)) (z (+ w 2))) ;; Error (+ w z))

1. Evaluar todas las expresiones de la derecha de las variables y guardar sus valores envariables auxiliares locales.

2. Definir un ámbito local en el que se ligan las variables del let con los valores de lasvariables auxiliares.

3. Evaluar el cuerpo del let en el ámbito local

La semántica anterior queda clara cuando comprobamos que let se puede definir en función delambda. En general, la expresión:

(let ((<var1> <exp1>) ... (<varn> <expn>)) <cuerpo>)

se puede implementar con la siguiente llamada a lambda:

((lambda (<var1> ... <varn>) <cuerpo>) <exp1> ... <expn>)

Variables en las asignaciones del let

Semántica del let

Let se define utilizando lambda

Page 61: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Ejemplo:

(let ((x (+ 2 3)) (y (+ x 3))) (+ x y))

Equivale a:

((lambda (x y) (+ x y)) (+ 2 3) (+ x 3))

La forma especial let* permite una asignación secuencial de las variables y lasexpresiones:

(let* ((x (+ 1 2)) (y (+ x 3)) (z (+ y x))) (* x y z))

¿Cómo 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 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) .

SICP: Cap. 1.1.1–1.1.6, 1.3, 2.2 (2.2.1) 2.3.1PLP: Cap. 10Concepts in Programming Languages Cap. 4.4

Semana 1

Conocer los hitos más importantes en la historia del paradigma funcional

Se puede hacer una asignación secuencial con let*

9. Bibliografía

Objetivos

Page 62: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Conocer las características más importantes del paradigma funcionalSaber diferenciar entre lenguajes y sentencias imperativas y lenguajes y sentenciasdeclarativasAplicar el modelo de computación de sustitución (en orden aplicativo y normal) para laevaluación de expresionesDiferenciar entre funciones y formas especiales en SchemeConocer y saber utilizar las formas especialies principales de SchemeConocer el tipo símbolo de Scheme y las funciones que trabajan con ellosConocer y saber utilizar la forma especial eval y la dualidad entre datos y programasComprender el uso de la recursión como forma de implementar la iteración en Scheme ylenguajes funcionalesComprender funciones recursivas sencillas que trabajen sobre estructuras compuestas(listas y cadenas)

Semana 2

Ser capaz de construir y representar gráficamente estructuras de datos compuestasformadas por parejasDada una estructura de parejas ser capaz de escribir expresiones en Scheme queobtengan sus elementosEntender la utilización de parejas para implementar las listas en SchemeDada una estructura de parejas, identificar si se trata o no de una listaEntender e implementar funciones recursivas sobre listasEntender e implementar funciones que contengan un número variable de argumentos

Semana 3

Conocer las condiciones para definir un tipo de dato como de primera claseEntender y saber utilizar la forma especial lambda para crear funciones sin darles unnombreEntender el funcionamiento de la forma especial define para construir una funciónEntender y diseñar funciones de orden superiorEntender y saber utilizar la forma especial let

Entender la semántica de la forma especial let y cómo se puede construir usandolambda

Entender la forma especial let*

Entender los ámbitos de variables en Scheme, saber representar gráficamente losámbitos creados por expresiones let y lambda

Entender el concepto y los ejemplos de closures y saber crear este tipo de funcionesDiferenciar el funcionamiento del deep y el shallow binding

Page 63: Tema 3: Programación funcional - dccia.ua.es · 4.3. Funciones recursivas sobre listas 4.4. Funciones con número variable de argumentos 5. ... Máquina de Turing, Kleen - Sustituciones

Lenguajes y Paradigmas de Programación, curso 2013–14© Departamento Ciencia de la Computación e Inteligencia Artificial, Universidad de AlicanteDomingo Gallardo, Cristina Pomares