La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

Agenda Clase 16 Motivación e Historia de la Programación Funcional y la Programación Lógica. Concepto y Características de la Programación Funcional. Ventajas.

Presentaciones similares


Presentación del tema: "Agenda Clase 16 Motivación e Historia de la Programación Funcional y la Programación Lógica. Concepto y Características de la Programación Funcional. Ventajas."— Transcripción de la presentación:

1 Agenda Clase 16 Motivación e Historia de la Programación Funcional y la Programación Lógica. Concepto y Características de la Programación Funcional. Ventajas y Desventajas de los lenguajes funcionales. Caso de Estudio: Scheme.

2 Programación lógica y funcional: Motivación
Los lenguajes lógicos son utilizados por lo general para especificaciones formales y pruebas de teoremas. Pej: diseño de circuitos digitales, estudio formal de la semántica de los lenguajes. Por su parte los lenguajes funcionales se han vuelto populares para aplicaciones científicas y de negocio. A pesar de que los lenguajes imperativos se utilizan más extensamente, existen implementaciones industriales importantes para los lenguajes funcionales y lógicos orientadas básicamente a los ejemplos mencionados en la lámina. Por otro lado, existen similitudes entre los lenguajes funcionales, lógicos e imperativos como: Todos manejan el concepto y establecen reglas de nombre y alcance. Tienen definidos tipos, expresiones y flujos de control (en particular de selección y recursión). Deben pasar por las fases de “escaneo”, parseo y análisis semántico. Con respecto a las diferencias, resaltando por ejemplo el caso de los lenguajes funcionales vs. los imperativos, los primeros hacen mayor uso de las subrutinas y además es más común la concurrencia y el no determinismo.

3 Programación lógica y funcional: Antecedentes Históricos.
30’s Alan Turing, Alonzo Church, Stephen Kleene y Emil Post desarrollan por separado formalizaciones de la noción de algoritmo. Turing  Máquina de Turing Church  equivalencia entre lenguajes, Lambda Cálculo. Kleene y Post  definiciones abstractas sin vinculación directa a la implementación de un lenguaje de programación. El modelo de Turing define una máquina o autómata finito de pila con la habilidad de acceder arbitrariamente a las celdas de una cinta ilimitada, lo cual es similar a la noción de los programas imperativos de alterar valores de variables en memoria. Por su parte Church además de establecer su tésis en la que define que todo modelo intuitivo de cómputo puede ser equivalente a otro (por ejemplo, un problema establecido en un lenguaje puede ser resuelto en cualquier otro), es el mentor del Lamda Cálculo que sirve de base a la programación funcional y que está basado en la noción de expresiones parametrizadas, donde el cómputo se lleva a cabo sustituyendo parámetros en dichas expresiones tal como se lleva a cabo en programas con funciones de alto nivel.

4 Programación Funcional: Concepto.
La programación funcional define las salidas de un programa como una función matemática de sus entradas, sin noción de estado interno, y, por tanto sin efectos de borde. Lisp  lenguaje funcional original y el más utilizado. Algunos lenguajes funcionales: Scheme, Common Lisp, Ml, Miranda, Haskell, Sisal. En la programación funcional se mantiene la noción de la denominada “transparencia referencial” que establece que el significado de una expresión depende únicamente del significado de sus subexpresiones por lo que no hay efectos de borde. Existen dos grandes categorías de lenguajes funcionales: los funcionales puros y los híbridos. La diferencia entre ambios radica en que los últimos son menos “estrictos” que los primeros, ya que admiten conceptos como secuencia de instrucciones o asignación de variables que son propias de los lenguajes procedimentales. Entre los lenguajes funcionales puros tenemos a Haskell y Miranda y entre los híbridos a Lisp y Scheme.

5 Programación Funcional: Características prácticas
Valores de Funciones de Primer Orden y Funciones de Orden Superior. Polimorfismo. Tipo Lista y sus Operadores. Recursión. Retorno de datos estructurados. Constructores de objetos estructurados. Recolección de Basura. En términos generales , un valor de prmer orden o clase es aquel que puede ser pasado como parámetro, retornado por una subrutina o asignado a una variable (en lenguajes que permitan esta operación de efecto de borde). Por tanto implica la posibilidad de crear nuevos valores o determinar el comportamiento de una subrutina a tiempo de ejecución. El orden superior implica la posibilidad de pasar funciones como argumento, o bien retornarlas como resultado de una función. El polimorfismo permite que los parámetros recibidos por las funciones puedan ser definidos de la forma más general posible. Lisp y sus dialectos son dinámicamente tipeados y por tanto inherentemente son polimórficos. Por su parte ML, Miranda y Haskell obtinen su polimorfismo a través de la inferencia de tipos. La noción de lista es sumamente importante por su definición recursiva, que permite operar sobre su primer elemento y continuar el proceso con el resto. Con respecto a la recursión, es el único mecanismo definido para hacer repetidamente alguna tarea, lo cual se ve facilitado por la ausencia de efectos de borde. Sin embargo la conducta y desempeño del programa dependerá fuertemente del modo de evaluación de los parámetros. Los lenguajes funcionales puros no admiten la construcción de un objeto estructurado a través de asignaciones a subcomponentes, sino que trabajan con el concepto de agregación. Ya que se desea otorgar extensión “ilimitada” a las funciones de primer orden los lenguajes funcionales tienden a utilizar un heap para todos los datos alojados dinámicamente o al menos para aquellos datos de los que el compilador no puede dar testimonio de que se encuentran a salvo en el stack. Por tanto es necesario una recolección de basura para la eliminación, ntre otras cosas, de estos datos alojados cuando ya no se utilizan.

6 Ventajas y Desventajas de los lenguajes funcionales.
Más fáciles de escribir, depurar y mantener que los lenguajes imperativos gracias a la ausencia de efectos de borde. Desventajas: Se quedan cortos en portabilidad, riqueza de librerías, interfaces con otros lenguajes y herramientas de depuración. Los programas definidos con lenguajes funcionales suelen ser menos extensos y por tanto más mantenibles que los realizados en lenguajes imperativos, sin embargo carecen de una amplia gama de librerías que si posee éstos últimos que permiten utilizar estructuras y elementos ya definidos en la construcción del programa.

7 Caso de Estudio: Scheme
Se analiza ya que procede de Lisp que es el lenguaje funcional más utilizado. Características adicionales de Lisp aplicables a Scheme: Homogeneidad de programas y datos. Autodefinición. Interacción con el usuario a través de un ciclo “leer-evaluar-imprimir”. Homogeneidad de programas y datos: los programas son en sí listas que pueden ser manipuladas con los mismos mecanismos utilizados para manipular los datos. Autodefinición: la semántica operacional de Lisp puede ser definida en términos de un interpretador escrito en el mismo lenguaje. Ciclo “leer-evaluar-imprimir”: el interpretador lee repetidamente una expresión desde la entrada estándar, evalúa la expresión e imprime el valor resultante.

8 Scheme: Características
Notación Cambridge-Polish La utilización de paréntesis indican la aplicación de una función o el uso de un macro. El símbolo de quoting (‘) evita que el interpretador evalúe una expresión. El tipo de los elementos es determinado a tiempo de corrida. La notación cambridge polish o prefija establece la colocación del operador seguido de los operandos, lo cual guarda perfecta concordancia con la forma en que se definen las funciones, es decir, nombre de función y argumentos que toma. Pej: (+ 4 5)  9 Se debe colocar la función con sus argumentos entre paréntesis separados por espacio en blanco. Ya que toda expresión parentizada es evaluada, se puede evitar dicha evaluación precediendo la expresión por el símbolo o la palabra “quote”. Pej: ‘( + 4 5)  (+ 4 5) o bien (quote(+ 4 5)) (+ 4 5) La mayoría de las funciones predefinidas realizan chequeos dinámicos para asegurar que sus argumentos son del tipo adecuado. Un programa definido en un archivo de texto puede ser “cargado” a través de la función (load “nombre_arvhivo”).

9 Scheme: Predicados A pesar de ser un lenguaje funcional Scheme ofrece entre otros los siguientes predicados: (boolean? x) (char? x) (string? x) (symbol? x) (member? x) (pair? x) (list? x) #t #f Cada uno de los predicados devuelve true (#t) si es un elemento del tipo por el que preguntan, y false en caso contrario. Los símbolos (symbols) son como strings porque tienen una secuencia de carateres, pero difieren de estos en que sólo un símbolo puede tener una secuencia específica de caracteres que se denomina “nombre de impresión”.

10 Scheme: Funciones definidas por el usuario.
Las funciones se definen a través de la palabra reservada “lambda” (lambda (x) (* x x)) Lista de parámetros formales de la función Cuerpo de la función El operador lambda es una función de nivel superior que sirve para definir funciones. El resultado de su aplicación es una función anónima que podría aplicarse a argumentos dados, o bien enlazarse con un nombre para luego ser aplicada. Aparte de la funciones existen formas especiales que son similares a las funciones pero tienen reglas especiales de evaluación y no poseen argumentos, aunque pueden definirse informalmente las subexpresiones como sus argumentos. La expresiones que conforman una función son evaluadas en orden y el retorno corresponde a la última expresión evaluada. Las expresiones de Scheme son evaluadas en orden aplicativo a excepción de las expresiones “lambda” e “if”. El “if” chequea cual expresión evalúa primero a true retornando el valor del segundo argumento sin evaluar el tercero, y de caso contrario retorna el tercer argumento sin evaluar el segundo. Expresiones condicionales pueden ser escritas utilizando un “if”. Pej: (if (< 2 3) 4 5 )  4

11 Scheme: Asociación (Binding)
Para asociar un nombre a una función se utilizan las expresiones “let” (localmente) o “define” (globalmente). (let ((a 3) (b 4) (square (lambda (x) (*x x))) (plus +)) (sqrt (plus (square a) (square b) ))) Lista de pares nombre valor Aplicación de funciones definidas. Se utiliza la función letrec para funciones recursivas. (letrec ((fact (lambda (n) (if (= n 1) 1 (* n (fact (- n 1))))))) (fact 5)) Nótese que el alcance de las asociaciones realizadas por “let” abarca sólo el segundo argumento. El problema es que una vez que se asocia un valor a un nombre dentro de su alcance, no admite nuevos valores, por lo que imposibilita su utilización en funciones recursivas, por tanto se utiliza para estos casos “letrec”. Tanto let como letrec no afectan el significado de nombres globales (declarados utilizando “define”).

12 Scheme: Listas y Números
Los principales operadores definidos para la manipulación de listas son los siguientes: car  retorna la cabeza de la lista. cdr  retorna el resto de la lista. cons  agrega un elemento a la cabecera de la lista. null?  determina si u argumento es la lista vacía. Tipos Numéricos: integer, rational, real, complex, number. Ejemplos: (car ‘(2 3 4))  2 (cdr ‘(2 3 4))  (3 4) (cons 2 ‘(3 4))  (2 3 4) Aparte de las listas existen vectores que se encuentran indexados por enteros como los arreglos y además admiten datos heterogéneos como los registros. Con respecto a los tipos numéricos, ciertas implementaciones únicamente proporcionan representación de reales. Otras implementaciones permiten reacionales almacenándolos internamente como pares (numerador, denominador). Adicionalmente los tipo complex y number son opcionales.

13 Scheme: Búsqueda y Pruebas de Igualdad.
LISTA LISTA DE ASOCIACIÓN eq? memq assq eqv? memv assv equal? member assoc eq?  prueba si los argumentos ser refieren al mismo objeto. eqv?  prueba si los argumentos son simultáneamente equivalentes. equal?  prueba si los argumentos tienen la misma estructura. memq, memv, member permiten determinar si un elemento pertenece a una lista y usa para ello las comparaciones eq, eqv e iqual respectivamente. Una lista de asociación (A-list) es un diccionario implementado como lista de pares donde el primer elemento es la clave de búsqueda y el segundo la información referente a esa clave. Las funciones definidas assq, assv y assoc toman una lista-A como primer argumento y retorna el primer par de la lista que sea eq?, eqv? o equal? A la clave respectivamente. Si no hay éxito retorna #f. *****Completar con ejemplos de pruebas de igualdad cuando pregunte dudas al respecto.*****

14 Scheme: Flujo de Control y Asignaciones.
Una secuencia de intrucciones al estilo “if-else” puede implementarse con la función “cond”. Las asignaciones se realizan a través de los operadores: set! set-car! set-cdr! En el flujo de control solo se permite la utilización del “else” como última expresión. Las asignaciones se utilizan cuando se desean producir side-effects, en conbinación de la secuenciación e iteración que veremos en la siguiente lámina.

15 Scheme: Secuenciación e iteración.
La secuenciación se implementa a través de “begin”. La iteración hace uso de “do” en combinación con “for-each”. Nótese que el primer argumento del “do” es una lista de triples que contienen: Como primer elemento una nueva variable. Como segundo elemento el valor inicial para dicha variable. Y como último elemento el valor a ser asignado a la variable al finalizar cada iteración. Posteriormente viene un par que especifica la condición de terminación y el valor que debe ser retornado. Por su parte la función “for-each” toma como argumentos una función y una secuencia de listas del mismo tamaño, invocando repetidamente la función hasta que se complete la secuencia de listas. La función call/cc (call-with-current-continuation) permite que el contexto actual sea almacenado como clausura y pasado a una subrutina específica .

16 Scheme: Programas como listas.
Un programa en Scheme es una lista y por tanto pueden aplicársele las mismas operaciones que a éstas. Scheme es un lenguaje que se representa en términos de si mismo, por tanto un programa escrito en este lenguaje es una lista que puede ser manipulada por completo como tal. S-expresión  String de símbolos balanceadamente parentizados. función eval permite evaluar una lista que fue creada como una estructura de datos. función list retorna una lista compuesta por los elementos que recibe como argumento. Cuando las listas creadas por list pasan a eval como primer argumento, ésta las evalúa a la función deseada. El segundo argumento que toma list establece el contexto o ambiente de referencia en el que será evaluada la expresión (scheme-report-environment). En la lámina se muestra un ejemplo de estas funciones y la contraparte sin utilizar eval y list. Como dato adicional de Scheme, existe la función “apply” que toma dos argumentos, una función y una lista, produciendo el efecto de llamar a la función con los elementos de la lista como argumentos.

17 Scheme: Orden de Evaluación.
Orden Aplicativo + Pasaje by sharing Orden Normal + Pasaje Por Nombre Función Estricta. Evaluación Perezosa.  Efectos de Borde. Operadores delay y force Recordando… el orden aplicatiovo establece que todos los parámetros deben ser evaluados antes de ser pasados, mientras que el normal realiza la evaluación cuando se utilizan. Ahora bien, Scheme posee orden de evaluación aplicativo para todas sus funciones exceptuando los casos de las formas especiales como el “cond”, en el que se evita trabajo adicional de evaluación llevándola a cabo de forma normal. Junto con el orden normal y aplicativo, las formas en que se llevan a cabo los pasajes de parámetros son by sharing para las funciones y por nombre para las formas normales. Adicionalmente cada forma especial es libre de escoger internamente cuándo evaluar los parámetros. Por otra parte, las formas especiales en conjunción con las funciones conforman lo que se denomina “tipo de expresiones”, los cuales a su vez pueden ser primitivos si forman parte del lenguaje o derivados si se construyen a partir de los primitivos. Así las funciones definidas a través de la primitiva “lambda” son funciones derivadas, y en el caso de las formas especiales derivadas tenemos los macros, que no se asemejan a los de C o C++ sino que más bien son funciones cuyos argumentos son pasados por nombre en lugar de by sharing. Función Estricta  requiere que todos sus argumentos sean definidos y por tanto su resutlado no dependerá del orden de evaluación. Lenguajes Estricto  si exige que todas las funciones definidas sean estrictas. Con esto las funciones pueden ser evaluadas aplicativamente sin problema. Evaluación Perezosa  se evalúa cuando es necesario manteniendo el valor para cuando se requiera nuevamente. Para esto Scheme se vale de unas etiquetas o memos asociados a los parámetros indicando su valor a través de la intrucción “delay”, que además conserva la clausura o contexto del parámetro en el momento en que se hizo la evaluación. Luego se utiliza la instrucción “force” para devolver el valor del memo reevaluándola haciendo uso de la clausura en caso de ser necesario. Cabe destacar que este mecanismo puede producir efectos de borde ya que por ejemplo si un argumento contiene una referencia a una variable que puede ser modificada por una asignación, el valor del argumento dependerá de si fue evaluada antes o después de la asignación.

18 Entrada/Salida, Streams y Monads.
read y display  Efectos de borde. Streams  definición de mecanismos de entrada y salida. Para las operaciones de entrada y salida en Scheme se puede utilizar las instrucciones read y display, pero éstas suelen producir efectos de borde, ya que en el caso de la primera cada invocación arroja un resultado diferente y en el caso de la segunda varias invocaciones pueden provocar que no se despliegue ninguna información. Para evitar estos inconvenientes, es preferible hacer uso de streams (flujo de caracteres) en conjunción con esas funciones para definir mecanismos de E/S tal como se establece en el ejemplo de la lámina. Nótese que la función driver despliega caracter por caracter obteniendolos de la cabeza del stream y vuelve a llamarse con el resto de la lista. Por su parte “squares” ejemplifica la interacción con el usuario ayudándose de las funciones eof-object? y #\newline, para determinar si no se pasó ningún parámetro en el primer caso y para producir una nueva línea en el segundo. Para las primeras versiones de Haskell, el mecanismo de streams era la base de E/S, sin embargo, éstos presentan problemas con manejo de gráficos o archivos aleatorios, por tanto se introdujo el concepto de “monads”, que es un tipo abstracto de datos que soporta secuenciación. Los valores de un E/S monad son acciones que el programador forza a que ocurran en un orden específico, como la lectura de un caracter desde entrada o la escritura de un caracter en salida. Para llevar a cabo las operaciones E/S Haskell se vale además de las funciones map e interact; la primera toma una función y una lista de argumentos devolviendo una lista que contiene el resultado de aplicar la función a los elementos de la lista; la segunda toma como argumento una función de String en String y le pasa como argumento lo que recibe por la entrada estándar, retornando a su vez lo que obtiene por la salida estándar. Monads  utilzado por versiones recientes de Haskell para E/S. [map, interact]


Descargar ppt "Agenda Clase 16 Motivación e Historia de la Programación Funcional y la Programación Lógica. Concepto y Características de la Programación Funcional. Ventajas."

Presentaciones similares


Anuncios Google