- Introducción a la programación funcional.
- Lenguajes de programación ¿Por qué hay tantos y aparecen nuevos?
- Evolución de los lenguajes funcionales
- Un lenguaje funcional avanzado: Miranda
- Perspectiva histórica de los lenguajes de programación.
- Criterios de evaluación de los LP
- Modelo de compilación: análisis
- Generación de código intermedio
- Generación del código objeto
- Gramáticas de contexto libre
- Glosario
- Índice histórico de los lenguajes funcionales
- Bibliografía
MIRANDA
1985
MIRANDA es un lenguaje funcional propuesto por D. Turner en 1985. Este mismo autor había desarrollado anteriormente otros lenguajes del mismo tipo: SASL (1976) y KRC (1981). HASKELL es un sucesor de MIRANDA.
Todos los lenguajes de la familia de MIRANDA se caracterizan porque los argumentos se pasan a las funciones sin evaluar (lazy evaluation): el argumento de una función se evalúa cuando la función necesita su valor.
Un programa en este tipo de lenguajes consiste en un conjunto de declaraciones de ecuaciones recursivas.
Objetivos
El paradigma de programación funcional es uno de los fundamentales entre los llamados de programación declarativa. Como tal, permite aunar los componentes de especificación y programación en las tareas de solución automática de problemas. Los lenguajes funcionales ofrecen al programador un buen número de recursos expresivos que permiten resolver problemas complejos mediante programas pequeños y robustos. Entre ellos cabe destacar: un sistema de tipos polimórficos que permite definir una amplia variedad de estructuras de datos de uso genérico, la posibilidad de definir funciones que aceptan otras funciones como argumentos y devuelven funciones como resultado, facilidades para definir y manipular estructuras de datos infinitas, un modelo computacional simple, claro y bien fundamentado, etc. De no menor importancia es la posibilidad de razonar, de forma sencilla, acerca de las propiedades de los programas: su corrección, su eficacia, su comportamiento en ejecución, … Esto permite optimizar las tareas de implementación de los lenguajes funcionales. Podemos encontrar, en casi todos los lenguajes de programación funcional, un núcleo común de conceptos y técnicas asentado sobre bases firmemente establecidas. En esta asignatura estudiamos los fundamentos de la programación funcional y su utilización en la definición de implementaciones correctas y eficientes de los lenguajes de programación que se enmarcan en este paradigma.
LENGUAJES DE PROGRAMACIÓN ¿POR QUÉ HAY TANTOS Y APARECEN NUEVOS?
Por: Hanna Oktaba
La computadora, a diferencia de otras herramientas que en general apoyan el esfuerzo físico de los humanos, fue inventada para facilitar el trabajo intelectual. Si el hombre tiene algún problema, por ejemplo "sumar dos y dos", el diseñador define el algoritmo que resuelve el problema, el programador lo codifica en un lenguaje de programación, el cual la computadora es capaz de "entender", luego la computadora ejecuta el algorítmo expresado como programa en el lenguaje de programación en cuestión, y listo. La máquina le entrega al hombre la respuesta "4", sin que éste tuviera que esforzar sus neuronas.
¿Cuál es el papel del lenguaje de programación en este proceso? Es muy importante, el lenguaje de programación es el medio de comunicación entre el hombre y la máquina. El modelo general de las computadoras, desde que fue esbozado por von Neumann, no ha cambiado mucho, mientras que la invención humana para proponerse nuevos problemas a resolver, usando la computadora, parece no tener límites. En consecuencia, los lenguajes de programación tienen que adaptarse a éstas crecientes necesidades y aumentar la expresividad para poder resolver problemas muy diversos y cada vez más complejos. Además, tienen que ofrecer cierta eficiencia en la ejecución. Es un logro difícil de alcanzar y por lo tanto, se requiere una búsqueda constante de nuevos lenguajes para ello.
Permítanme exponer un breve panorama de los más importantes tipos de lenguajes de programación.
Lenguajes Imperativos Comenzaré por los llamados lenguajes imperativos. En este tipo de lenguajes, cuyo origen está ligado a la propia arquitectura de von Neumann, la arquitectura consta de una secuencia de celdas, llamadas memoria, en la cual se pueden guardar en forma codificada, lo mismo datos que instrucciones; y de un procesador, el cual es capaz de ejecutar de manera secuencial una serie de operaciones, principalmente aritméticas y booleanas, llamadas comandos. En general, un lenguaje imperativo ofrece al programador conceptos que se traducen de forma natural al modelo de la máquina.
Los lenguajes imperativos más destacados de la historia han sido: FORTRAN, Algol, Pascal, C, Modula-2, Ada. Seguramente, los lectores conocen por lo menos uno de ellos.
El programador, al utilizar un lenguaje imperativo, por lo general tiene que traducir la solución abstracta del problema a términos muy primitivos, cercanos a la máquina. La distancia entre el nivel del razonamiento humano y lo expresable por los lenguajes imperativos causa que sus programas sean más "comprensibles" para la máquina que para el hombre. Esta desventaja para nosotros, reflejada en la dificultad que tenemos al construir programas en un lenguaje imperativo, se vuelve una ventaja en el momento de la generación del código. El programa está expresado en términos tan cercanos a la máquina, que el código generado es relativamente parecido al programa original, lo que permite cierta eficiencia en la ejecución.
Lenguajes Funcionales Los matemáticos desde hace un buen tiempo están resolviendo problemas usando el concepto de función. Una función convierte ciertos datos en resultados. Si supiéramos cómo evaluar una función, usando la computadora, podríamos resolver automáticamente muchos problemas. Así pensaron algunos matemáticos, que no le tenían miedo a la máquina, e inventaron los lenguajes de programación funcionales. Además, aprovecharon la posibilidad que tienen las funciones para manipular datos simbólicos, y no solamente numéricos, y la propiedad de las funciones que les permite componer, creando de esta manera, la oportunidad para resolver problemas complejos a partir de las soluciones a otros más sencillos. También se incluyó la posibilidad de definir funciones recursivamente.
Un lenguaje funcional ofrece conceptos que son muy entendibles y relativamente fáciles de manejar para todos los que no se durmieron en las clases de matemáticas. El lenguaje funcional más antiguo, y seguramente el más popular hasta la fecha, es LISP, diseñado por McCarthy [1] en la segunda mitad de los años 50. Su área de aplicación es principalmente la Inteligencia Artificial. En la década de los 80 hubo una nueva ola de interés por los lenguajes funcionales, añadiendo la tipificación y algunos conceptos modernos de modularización y polimorfismo, como es el caso del lenguaje ML.
Programar en un lenguaje funcional significa construir funciones a partir de las ya existentes. Por lo tanto es importante conocer y comprender bien las funciones que conforman la base del lenguaje, así como las que ya fueron definidas previamente. De esta manera se pueden ir construyendo aplicaciones cada vez más complejas. La desventaja de este modelo es que resulta bastante alejado del modelo de la máquina de von Neumann y, por lo tanto, la eficiencia de ejecución de los intérpretes de lenguajes funcionales no es comparable con la ejecución de los programas imperativos precompilados. Para remediar la deficiencia, se está buscando utilizar arquitecturas paralelas que mejoren el desempeño de los programas funcionales, sin que hasta la fecha estos intentos tengan un impacto real importante.
Lenguajes Lógicos Otra forma de razonar para resolver problemas en matemáticas se fundamenta en la lógica de primer orden. El conocimiento básico de las matemáticas se puede representar en la lógica en forma de axiomas, a los cuales se añaden reglas formales para deducir cosas verdaderas (teoremas) a partir de los axiomas. Gracias al trabajo de algunos matemáticos, de finales de siglo pasado y principios de éste, se encontró la manera de automatizar computacionalmente el razonamiento lógico –particularmente para un subconjunto significativo de la lógica de primer orden– que permitió que la lógica matemática diera origen a otro tipo de lenguajes de programación, conocidos como lenguajes lógicos. También se conoce a estos lenguajes, y a los funcionales, como lenguajes declarativos, porque el programador, parar solucionar un problema, todo lo que tiene que hacer es describirlo vía axiomas y reglas de deducción en el caso de la programación lógica y vía funciones en el caso de la programación funcional.
En los lenguajes lógicos se utiliza el formalismo de la lógica para representar el conocimiento sobre un problema y para hacer preguntas que, si se demuestra que se pueden deducir a partir del conocimiento dado en forma de axiomas y de las reglas de deducción estipuladas, se vuelven teoremas. Así se encuentran soluciones a problemas formulados como preguntas. Con base en la información expresada dentro de la lógica de primer orden, se formulan las preguntas sobre el dominio del problema y el intérprete del lenguaje lógico trata de encontrar la respuesta automáticamente. El conocimiento sobre el problema se expresa en forma de predicados (axiomas) que establecen relaciones sobre los símbolos que representan los datos del dominio del problema.
El PROLOG es el primer lenguaje lógico y el más conocido y utilizado. Sus orígenes se remotan a los inicios de la década de los 70 con los trabajos del grupo de A. Colmerauer [2] en Marsella, Francia. También en este caso, las aplicaciones a la Inteligencia Artificial mantienen el lenguaje vivo y útil.
En el caso de la programación lógica, el trabajo del programador se restringe a la buena descripción del problema en forma de hechos y reglas. A partir de ésta se pueden encontrar muchas soluciones dependiendo de como se formulen las preguntas (metas), que tienen sentido para el problema. Si el programa está bien definido, el sistema encuentra automáticamente las respuestas a las preguntas formuladas. En este caso ya no es necesario definir el algoritmo de solución, como en la programación imperativa, en cambio, lo fundamental aquí es expresar bien el conocimiento sobre el problema mismo. En programación lógica, al igual que en programación funcional, el programa, en este caso los hechos y las reglas, están muy alejados del modelo von Neumann que posee la máquina en la que tienen que ser interpretados; por lo tanto, la eficiencia de la ejecución no puede ser comparable con la de un programa equivalente escrito en un lenguaje imperativo. Sin embargo, para cierto tipo de problemas, la formulación del programa mismo puede ser mucho más sencilla y natural (para un programador experimentado, por supuesto).
Lenguajes Orientados a Objetos A mediados de los años 60 se empezó a vislumbrar el uso de las computadoras para la simulación de problemas del mundo real. Pero el mundo real está lleno de objetos, en la mayoría de los casos complejos, los cuales difícilmente se traducen a los tipos de datos primitivos de los lenguajes imperativos. Así es que a dos noruegos, Dahl y Nygaard [3], se les ocurrió el concepto de objeto y sus colecciones, llamadas clases de objetos, que permitieron introducir abstracciones de datos a los lenguajes de programación. La posibilidad de reutilización del código y sus indispensables modificaciones, se reflejaron en la idea de las jerarquías de herencia de clases. A ellos también les debemos el concepto de polimorfismo introducido vía procedimientos virtuales. Todos estos conceptos fueron presentados en el lenguaje Simula 67, desde el año 1967. Aunque pensado como lenguaje de propósito general, Simula tuvo su mayor éxito en las aplicaciones de simulación discreta, gracias a la clase SIMULATION que facilitaba considerablemente la programación.
La comunidad informática ha tardado demasiado en entender la utilidad de los conceptos básicos de Simula 67, que hoy identificamos como conceptos del modelo de objetos. Tuvimos que esperar hasta los años 80 para vivir una verdadera ola de propuestas de lenguajes de programación con conceptos de objetos encabezada por Smalltalk, C++, Eiffel, Modula-3, Ada 95 y terminando con Java. La moda de objetos se ha extendido de los lenguajes de programación a la Ingeniería de Software. Si alguien desarrolla sistemas y todavía no sabe qué es un objeto, mejor que no lo confiese en público y lea cuanto antes los números 3, 4 y 20 de la revista Soluciones Avanzadas.
El modelo de objetos, y los lenguajes que lo usan, parecen facilitar la construcción de sistemas o programas en forma modular. Los objetos ayudan a expresar programas en términos de abstracciones del mundo real, lo que aumenta su comprensión. La clase ofrece cierto tipo de modularización que facilita las modificaciones al sistema. La reutilización de clases previamente probadas en distintos sistemas también es otro punto a favor. Sin embargo, el modelo de objetos, a la hora de ser interpretado en la arquitectura von Neumann conlleva un excesivo manejo dinámico de memoria debido a la constante creación de objetos, así como a una carga de código fuerte causada por la constante invocación de métodos. Por lo tanto, los programas en lenguajes orientados a objetos siempre pierden en eficiencia, en tiempo y memoria, contra los programas equivalentes en lenguajes imperativos. Para consolarnos, los expertos dicen que les ganan en la comprensión de código.
Lenguajes Concurrentes, Paralelos y Distribuidos La necesidad de ofrecer concurrencia en el acceso a los recursos computacionales se remonta a los primeros sistemas operativos. Mientras que un programa realizaba una operación de entrada o salida otro podría gozar del tiempo del procesador para sumar dos números, por ejemplo. Aprovechar al máximo los recursos computacionales fue una necesidad apremiante, sobre todo en la época en que las computadoras eran caras y escasas; el sistema operativo tenía que ofrecer la ejecución concurrente y segura de programas de varios usuarios, que desde distintas terminales utilizaban un solo procesador, y así surgió la necesidad de introducir algunos conceptos de programación concurrente para programar los sistemas operativos.
Posteriormente, cuando los procesadores cambiaron de tamaño y de precio, se abrió la posibilidad de contar con varios procesadores en una máquina y ofrecer el procesamiento en paralelo, es decir, procesar varios programas al mismo tiempo. Esto dio el impulso a la creación de lenguajes que permitían expresar el paralelismo. Finalmente, llegaron las redes de computadoras, que también ofrecen la posibilidad de ejecución en paralelo, pero con procesadores distantes, lo cual conocemos como la programación distribuida.
En resumen, el origen de los conceptos para el manejo de concurrencia, paralelismo y distribución está en el deseo de aprovechar al máximo la arquitectura von Neumann y sus modalidades reflejadas en conexiones paralelas y distribuidas.
Históricamente encontramos en la literatura soluciones conceptuales y mecanismos tales como: semáforos, regiones críticas, monitores, envío de mensajes (CSP), llamadas a procedimientos remotos (RPC), que posteriormente se incluyeron como partes de los lenguajes de programación en Concurrent Pascal, Modula, Ada, OCCAM, y últimamente en Java.
Uno de los ejemplos más importantes es el modelo de envío de mensajes de CSP de Hoare [4], para las arquitecturas paralelas y distribuidas, el cual no solamente fructificó en una propuesta del lenguaje de programación OCCAM, sino dio origen a una nueva familia de procesadores, llamados "transputers", que fácilmente se componen en una arquitectura paralela.
Es difícil evaluar las propuestas existentes de lenguajes para la programación concurrente, paralela y distribuida. Primero, porque los programadores están acostumbrados a la programación secuencial y cualquier uso de estos mecanismos les dificulta la construcción y el análisis de programas. Por otro lado, este tipo de conceptos en el pasado fue manejado principalmente a nivel de sistemas operativos, protocolos de comunicación, etcétera, donde la eficiencia era crucial, y por lo tanto no se utilizaban lenguajes de alto nivel para la programación. Hoy en día, la programación de sistemas complejos tiene que incluir las partes de comunicaciones, programación distribuida y concurrencia. Esto lo saben los creadores de los lenguajes más recientes, que integran conceptos para manejar: los hilos de control, comunicación, sincronización y no determinismo; el hardware y las aplicaciones se los exigen.
A manera de conclusiones A quien pensaba que en la materia de los lenguajes de programación ya se había dicho todo, espero haberlo convencido de que estaba equivocado. Como dice Hoare [4]: "El diseño de un lenguaje de programación es siempre un compromiso, en el cual un buen diseñador tiene que tomar en cuenta: el nivel de abstracciones deseado, la arquitectura del hardware, y el rango propuesto de las aplicaciones".
Lo malo, o lo bueno, según lo veamos, es que queremos construir sistemas a niveles cada vez más abstractos, con hardware cada vez más complicado y con aplicaciones cada vez más ambiciosas (véase la reciente efervescencia con Java y sus aplicaciones en Internet). Por lo tanto, el trabajo para los diseñadores de lenguajes existe para un buen rato.
Una versión completa de este artículo aparecerá en un número próximo a salir de la revista Soluciones Avanzadas, dedicado a los lenguajes de programación.
EVOLUCIÓN DE LOS LENGUAJES FUNCIONALES
Introducción
¿Por qué surge el modelo funcional?
Programación Funcional |
En programación funcional, un programa consta de:
- un conjunto de operaciones primitivas cuyo significado está predeterminado en el sistema. Por ejemplo, la suma de números enteros, la resta, el producto, etc.
- un conjunto de definiciones de función, establecidas por el programador, que eventualmente emplearán las operaciones primitivas. Por ejemplo, la función factorial.
- un dato de entrada (entendido como la aplicación de una de las funciones definidas sobre otros datos). Por ejemplo: fact(2*fact(2)).
La ejecución del programa funcional consiste en el cálculo del valor asociado al dato de entrada de acuerdo con las definiciones dadas para las funciones en el programa. El proceso de cálculo de dicho valor se conoce como evaluación del dato de entrada. Dicha evaluación puede realizarse de muchas formas, pero hay dos estrategias fundamentales para llevarla a cabo: la estrategia voraz (eager) y la estrategia perezosa (lazy). La elección de una u otra tiene importantes repercusiones en la implementación y en el comportamiento operacional del proceso de evaluación.
Problemas del modelo imperativo
Los programas escritos en lenguajes de programación tradicionales como Pascal, C, ADA, etc. forman una abstracción de la máquina de Von-Neumann caracterizada por:
- Memoria Principal para almacenamiento de datos y código máquina.
- Unidad Central de Proceso con una serie de registros de almacenamiento temporal y un conjunto instrucciones de cálculo aritmético, modificación de registros y acceso a la Memoria Principal.
Los programas imperativos están formados por una serie de datos globales y un conjunto de instrucciones ó código. Estos dos elementos forman una abstracción de los datos y código de la memoria principal.
El programador trabaja en un nivel cercano a la máquina lo que le permite generar programas eficientes. Con esta proximidad aparece, sin embargo, una dependencia entre el algoritmo y la arquitectura que impide, por ejemplo, utilizar algoritmos programados para arquitecturas secuenciales en arquitecturas paralelas.
Los algoritmos escritos en lenguajes imperativos se expresan mediante una secuencia de instrucciones que modifican el estado de un programa accediendo a los datos globales de la memoria. En este punto es donde empiezan los problemas:
Ejemplo
Program prueba;
var flag:boolean;
function f (n: integer):integer;
begin
flag:=not flag;
if flag then f:=n;
else f:=2*n;
end;
……..
–Programa principal
begin
flag:=true;
……
write(f(1)); ß retorna 2
write(f(1)); ß retorna 1
…….
write(f(1) + f(2)); ß retorna 4
write(f(2) + f(1)); ß retorna 5
En el primer caso la expresión f(1) retorna valores diferentes dependiendo de la secuencia de ejecución y en el segundo no se cumplen propiedades matemáticas simples tales como la conmutatividad. Estos ejemplos ponen en evidencia que ciertas características de los lenguajes imperativos tales como la asignación pueden traer consigo efectos laterales inesperados que oscurecen la semántica del programa; en consecuencia se hace difícil demostrar que los programas cumplen con los requerimientos especificados y que estén libres de errores.
Este y otros problemas son inherentes al modelo computacional utilizado, por ende una solución factible de ser aplicada puede ser cambiar el modelo computacional. Entre otras alternativas se encuentran el modelo funcional o aplicativo cuyo objetivo es describir los problemas mediante funciones matemáticas sin efectos laterales, y el modelo lógico o declarativo que describe los problemas mediante relaciones entre objetos o entidades.
Evolución del paradigma funcional
Lambda calculo
Los orígenes teóricos del modelo funcional se remontan a la década del 30, mas precisamente al año 1934, cuando Alonzo Church introdujo un modelo matemático de computación llamado lambda calculo. A pesar de que en esta época las computadoras aun no existían el lambda calculo se puede considerar como el primer lenguaje funcional de la historia y sus fundamentos fueron la base de toda la teoría de la programación funcional y de los lenguajes funcionales desarrollados posteriormente. Se puede decir que los lenguajes funcionales modernos son versiones de lambda calculo con numerosas ayudas sintácticas.
La principal característica de lambda calculo es su simplicidad ya que permite efectuar solo dos operaciones:
- Definir funciones de un solo argumento y con un cuerpo especifico, denotado por la siguiente terminología: l x.B , en donde x determina el parámetro o argumento formal y B representa el cuerpo de la función, es decir f(x) = B.
- Aplicar alguna de las funciones definidas sobre un argumento real (A); lo que es conocido también con el nombre de reducción, y que no es otra cosa que sustituir las ocurrencias del argumento formal (x), que aparezcan en el cuerpo (B) de la función, con el argumento real(A), es decir: ( l x.B ) A.
Ejemplo:
( l x. (x+5) ) 3 , lo que indica que en la expresión x+5, se debe sustituir el valor de x por 3.
Cuando ya no es posible reducir una función, se dice que ésta se encuentra en estado normal, es decir se obtiene el valor de la función. Este último, siempre dependerá únicamente de los argumentos y siempre tendrá la consistencia de retornar el mismo valor para los mismos argumentos. Por lo tanto se tiene transparencia referencial.
Los dos mecanismos básicos presentados anteriormente se corresponden con los conceptos de abstracción funcional y aplicación de función; si le agregamos un conjunto de identificadores para representar variables se obtiene lo mínimo necesario para tener un lenguaje de programación funcional. Lambda calculo tiene el mismo poder computacional que cualquier lenguaje imperativo tradicional.
Las ventajas de tener un lenguaje tan simple son:
- Permite definiciones simples.
- Facilita el estudio de aspectos computacionales.
- Su carácter formal facilita la demostración de propiedades.
Aplicaciones
- Compilación de lenguajes funcionales.
- Especificar semántica a lenguajes imperativos.
- Formalismo para definir otras teorías.
Fue creado por Turner en 1986. Es similar a ML dado que utiliza una sintaxis similar, el alcance es estático, es fuertemente para prototipado, y usa el mismo método de inferencia de tipos.
Objetivo: Facilitar la tarea del programador incorporando facilidades sintácticas como las guardas, pattern matching y las listas por comprensión.
Características:
- Todos los lenguajes de la familia de Miranda se caracterizan porque los argumentos se pasan a las funciones sin evaluar (lazy evaluation).
- Recursión.
Miranda es un sistema de la programación funcional avanzado bajo que corre el sistema operativo de UNIX (*). El objetivo del sistema de Miranda es a proporcione un idioma funcional moderno, incluido en un ` industrial la calidad' programando el ambiente. Está usándose ahora a un crecimiento
el número de sitios por enseñar la programación funcional y como un vehículo para el prototyping rápido de software. El propósito de este artículo corto es para dar una apreciación global breve de los rasgos principales de Miranda. Los temas nosotros discutirá, en el orden, es:
Las ideas básicas
El Miranda que programa el ambiente
Las ecuaciones defendidas y estructura del bloque
El modelo emparejando
Zurrando y las funciones del orden más altas
Liste las comprensiones
La evaluación perezosa y listas del infinito
Polymorphic muy bien la mecanografía
El usuario definió los tipos
Teclee los sinónimos
Los tipos de los datos abstractos
La recopilación separada y uniéndose
El estado de aplicación actual
(*) La nota: UNIX es una marca de fábrica de AT&T Campanilla Laboratorios, Miranda es un
la marca de fábrica de Software de la Investigación S.A..
Las Ideas básicas
El Miranda que programa el idioma es completamente funcional – hay no efectos del lado o los rasgos indispensables de cualquier amable. Un programa (realmente nosotros no lo llame un programa, nosotros lo llamamos una "escritura") es una colección de ecuaciones que definen las varias funciones y el datos estructura que nosotros somos interesado computando. El orden en que las ecuaciones se dan es no en general significante. No hay ninguna obligación por ejemplo para el la definición de una entidad para preceder su primer uso. Aquí es un muy simple el ejemplo de una escritura de Miranda:
z = el sq x / el sq y
el sq n = n * n
x = un + b
y = un – b
un = 10
b = 5
Note la ausencia de equipaje sintáctico – Miranda es, por el plan, más bien, conciso. No hay ninguna declaración del tipo obligatoria, aunque (vea después) el idioma se teclea fuertemente. No hay ningún punto y coma al final de las definiciones – el algoritmo del análisis gramatical hace uso inteligente de diseño.
La nota que la anotación para la aplicación de la función simplemente es la yuxtaposición, como en el "sq x". En la definición de la función del sq, "n" está un formal el parámetro – su alcance se limita a la ecuación en que ocurre (considerando que los otros nombres introdujeron sobre tiene la escritura entera para su alcance).
La estructura de los datos normalmente usada es la lista que en Miranda es escrito con los anaqueles del cuadrado y comas, por ejemplo:
el week_days = ["Mon", "Tue", se "Casan", "Thur", "Fri"]
días = el week_days ++ [se "Sentaba", Sol]
Las listas pueden añadirse por el "++" operador. Otros funcionamientos útiles en las listas incluyen el infijo ": " qué prefijos un elemento al frente de un liste, "#" qué toma la longitud de una lista, y clava "! " qué hace
el subscripting. Así por ejemplo 0:[1,2,3] tiene el valor [0,1,2,3], #días es 7, y el days!0 es "Mon."
Hay también un operador "–" qué lista la substracción. Por ejemplo
[1,2,3,4,5]–[2,4] es [1,3,5].
Hay una anotación de la taquigrafía que usa ".. " para las listas cuyo el formulario de los elementos
una serie aritmética. Por ejemplo aquí son las definiciones del factorial funcione, y de un resultado del número que es la suma de los números impares entre 1 y 100 (la suma y producto son la biblioteca funciona):
el fac n = el producto [1.. n]
el resultado = la suma [1,3 ..100]
Los elementos de una lista deben todos sea del mismo tipo. Una sucesión de se llaman elementos de tipo mixto un tuple, y es el usando escrito los paréntesis en lugar de los anaqueles del cuadrado. El ejemplo
el empleado = ("Jones",True,False,39)
Tuples son análogos a los archivos en Pascal (considerando que las listas son análogas a las series). Tuples no puede ser los subscripted – sus elementos se extraen por modelo que empareja (vea después).
El Ambiente de Miranda
El sistema de Miranda es interactivo y corre bajo UNIX como un ego el subsistema contenido. La acción básica es evaluar las expresiones, proporcionado por el usuario en el término, en el ambiente establecido por,
la escritura actual. "Z" por ejemplo evaluando en el contexto del primero escritura dada sobre produciría el resultado "9."
El recopilador de Miranda trabaja junto con un editor (normalmente esto es el "vi" pero puede ponerse a cualquier editor de la opción de los usuarios) y escrituras es automáticamente los recompiled después revisa, y cualquier sintaxis o errores del tipo el signaled inmediatamente. Los polymorphic teclean el sistema permite un alto la proporción de errores lógicos ser descubierto a compila tiempo.
Hay una biblioteca grande real de funciones normales. Hay un manual de la referencia en línea que documenta todos los aspectos del sistema. Hay una interfaz buena a UNIX, permitiendo Miranda programa para tomar los datos de, y envía los datos a, UNIX arbitrario archiva, y también es posible a invoque Miranda programa directamente de la cáscara de UNIX, y para combinar ellos, vía las cañerías de UNIX, con procesos escritos en otros idiomas.
Las Ecuaciones defendidas y Estructura del Bloque
Una ecuación puede tener varios lados de la mano derecha alternativos distinguidos por guardias – el guardia es escrito en lo siguiente correcto una coma. Para ejemplo que la más gran función del divisor común puede escribirse:
el gcd un b = el gcd (un-b) b, si el a>b
= el gcd un (b-un), si un <b
= un, si el a=b
El último guardia en tal una serie de alternativas puede escribirse
"Por otra parte", en lugar de "Si la condición", para indicar un caso predefinido (*).
También se permitía introducir las definiciones locales en la mano derecha el lado de una definición, por medio de un "donde" la cláusula. Considere para el ejemplo la definición siguiente de una función por resolver cuadrático las ecuaciones (o falla o ingresos una lista de una o dos raíces reales):
el quadsolve un b c = el error las raíces" "complejas, si el delta <0
= [- el b/(2*a)], si el delta=0
= [- el b/(2*a) + el radix/(2*a),
– el b/(2*a) – el radix/(2*a)], si el delta>0
donde
el delta = el b*b – 4*a*c
el radix = el delta del sqrt
Donde las cláusulas pueden ocurrir anidado, a la profundidad arbitraria, permitiendo Miranda los programas ser organizado con una estructura del bloque anidada. El sangrado de los bloques internos son compulsivos, cuando la información del diseño es usada por el parser.
(*) La nota: Para la compatibilidad con las versiones más tempranas de Miranda el uso de
la palabra "si" en guardias es optativo.
El modelo Emparejando
Se permitía definir una función dando varios alternativa las ecuaciones, distinguió por el uso de modelos diferentes en el formal los parámetros. Esto proporciona otro método de embale el análisis que
es a menudo más elegante que el uso de guardias. Nosotros aquí damos algún simple los ejemplos de modelo que empareja en los números naturales, listas, y tuples.
Aquí es (otro) la definición de la función factorial, y una definición de la función de Ackermann:
fac 0 = 1
el fac (el n+1) = (el n+1)*fac n
el ack 0 n = el n+1
el ack (el m+1) 0 = el ack m 1
el ack (el m+1) (el n+1) = el m(ack del ack (el m+1) n)
Aquí es un (ingenuo) la definición de una función por computar el no Fibonacci numeran:
mienta 0 = 0
mienta 1 = 1
la mentirilla (el n+2) = la mentirilla (el n+1) + la mentirilla n
Aquí son algunos ejemplos simples de funciones definidas el modelo emparejando en las listas:
la suma [] = 0
la suma (el a:x) = un + la suma x
el producto [] = 1
el producto (el a:x) = un * el producto x
la marcha atrás [] = []
la marcha atrás (el a:x) = x inverso ++ [un]
Accediendo los elementos de un tuple también se hace el modelo emparejando. Para
ejemplo que la selección funciona en 2-tuples puede definirse así
el fst (el a,b) = un
el snd (el a,b) = b
Como último ejemplos nosotros damos las definiciones de dos biblioteca de Miranda las funciones, toma y gota que el retorno los primeros miembros de n de una lista, y el resto de la lista sin los primeros miembros de n, respectivamente,
tome 0 x = []
tome (el n+1) [] = []
tome (el n+1) (el a:x) = un: tome n x
deje caer 0 x = x
la gota (el n+1) [] = []
la gota (el n+1) (el a:x) = la gota n x
Aviso que las dos funciones se definen de tal una manera que que el la identidad siguiente siempre sostiene – "tome n x ++ la gota n x = x" – incluyendo en el caso patológico que la longitud de x está menos de n.
Zurrando y las Funciones del Orden más Altas
Miranda es un idioma del orden totalmente más alto – las funciones son primero la clase los ciudadanos y puede ser los dos pasados como los parámetros y puede volver como los resultados.
La aplicación de la función queda asociativo, para que cuando nosotros le escribimos x y" a "f que es analizado como "(f x) y", significando que el resultado de aplicar f a x es un funcione que se aplica entonces a y. El lector puede probar fuera suyo entendiendo de funciones del orden más altas funcionando lo que es el valor de respuesta en la escritura siguiente:
la respuesta = dos veces dos veces dos veces suc 0
dos veces f x = f (f x)
el suc x = x + 1
Note eso en Miranda que cada función de dos o más argumentos realmente es una función del orden más alta. Esto es muy útil como él permite parcial la aplicación. Por ejemplo el "miembro" es una función de la biblioteca tal que el "miembro x un" pruebas si la lista x contiene el elemento un (volviendo Verdadero
o Falso como apropiado). aplicando al miembro parcialmente nosotros podemos derivar muchos predicados útiles, como
la vocal = el miembro ['un', 'e', 'i', 'o', 'u']
el dedo = el miembro [0', 1', 2', 3', 4', 5', 6', 7', 8', 9']
mes = el miembro [el "Ene", el "Feb", "Mar", el "Abr", el "Jun", "jul", "ago", el "Sep",,
"Oct", "Nov", el "Dic"]
Como otro ejemplo de programación del orden más alta considere la función
el foldr, definió
el op del foldr k [] = k
el op del foldr k (el a:x) = el op un (el op del foldr k x)
Todas las funciones de proceso de lista normales pueden obtenerse parcialmente por el foldr aplicando. Los ejemplos
la suma = el foldr (+) 0
el producto = el foldr (*) 1
la marcha atrás = el postfix del foldr []
donde el postfix un x = x ++ [un]
Liste las Comprensiones
Las comprensiones de la lista dan una sintaxis concisa para una clase bastante general de las iteraciones encima de las listas. La sintaxis se adapta de una anotación análoga usado en la teoría fija (llamó la comprensión" fija). UN ejemplo simple de un la comprensión de la lista es:
[el n*n | n <- [1 ..100]]
Ésta es una lista que contiene (en el orden) los cuadrados de todos los números de 1 a 100. La expresión anterior se leería alto como la lista de todo el n*n tal ese n deducido de la lista 1 a 100". Nota que "n" es un local inconstante de la expresión anterior. La estructura inconstante-obligatoria al el derecho de la barra se llama un "generador" – el "<- " la señal denota eso la variable introdujo en sus rangos izquierdos encima de todos los elementos del liste en su derecho. El formulario general de una comprensión de la lista en Miranda
es:
[el cuerpo | los calificadores]
donde cada calificador o es un generador, del var del formulario <- el exp, o el resto un filtro que es una expresión del boolean restringía los rangos de las variables introducidas por los generadores. Cuando dos o más los calificadores están presentes que ellos están separados por los puntos y comas. Un ejemplo de
una comprensión de la lista con dos generadores es dada por lo siguiente la definición de una función por devolver una lista de todas las permutaciones de una lista dada,
los permanente [] = [[]]
los permanente x = [el a:y | un <- x; y <- los permanente (x–[un])]
El uso de un filtro se muestra por la definición siguiente de una función qué toma un número e ingresos una lista de todos sus factores,
los factores n = [i | i <- [1.. n div 2]; el mod de n i = 0]
Liste a menudo que las comprensiones permiten concisión notable de expresión. Nosotros damos dos ejemplos. Aquí es una declaración de Miranda de Hoare
El algoritmo de "Quicksort", como un método de ordenar una lista,
la clase [] = []
la clase (el a:x) = la clase [b | b <- x; b <= un]
++ [un] ++
la clase [b | b <- x; el b>a]
Luego es una solución de Miranda al ocho problema de las reinas. Nosotros tenemos a ponga a ocho reinas en los ajedreces que aborda para que ninguna reina dé el cheque a cualquiera otro. Desde que cualquier solución debe tener una reina exactamente en cada columna, un la representación conveniente para una tabla es una lista de enteros que dan la fila el número de la reina en cada columna sucesiva. En la escritura siguiente
la función "corona n" devuelve todas las maneras seguras de poner a reinas adelante el primero las columnas de n. Una lista de todas las soluciones al ocho problema de las reinas es por consiguiente obtenido imprimiendo el valor de (corona 8)
reinas 0 = [[]]
reinas (el n+1) = [el q:b | b <- corona n; q <- [0 ..7]; q b seguro]
q b seguro = y [~ verifica q b i | i <- [0.. #el b-1]]
los cheques q b i = el q=b!i / el abs(q – el b!i)=i+1
La Evaluación perezosa y Listas del Infinito
El mecanismo de la evaluación de Miranda es "perezoso", en el sentido que no el subexpression se evalúa hasta que su valor se conozca para ser requerido. Uno la consecuencia de esto es que eso es posible definir funciones que son non-estricto (significando que ellos son capaces de devolver una respuesta aun cuando
uno de sus argumentos es indefinido). por ejemplo nosotros podemos definir un la función condicional como sigue,
el cond Verdadero x y = x
el cond x y Falso = y
y entonces lo usa en las tales situaciones como el "cond (el x=0) 0 (1/x) ".
La otra consecuencia principal de evaluación perezosa es que lo hace posible apuntar definiciones de estructuras de los datos infinitas. Aquí es algunos ejemplos de definiciones de Miranda de listas infinitas (la nota que hay un formulario modificado del ".. " la anotación para la aritmética interminable las progresiones)
unos = 1: unos
repita un = x
donde x = un: x
el nats = [0..]
las desigualdades = [1,3..]
los cuadrados = [el n*n | n <- [0..]]
los perfecto = [n | n <- [1..]; el sum(factors n) = n]
imprima = el cedazo [2..]
donde
el cedazo (el p:x) = p: el cedazo [n | n <- x; el mod de n p> 0]
Una aplicación interesante de listas infinitas es actuar como las mesas del lookup por esconder los valores de una función. Por ejemplo nuestro más temprano ingenuo La definición de mentirilla puede mejorarse de exponencial a lineal la complejidad cambiando el recursion para usar una mesa del lookup, así,
mienta 0 = 1
mienta 1 = 1
la mentirilla (el n+2) = el flist!(n+1) + el flist!n
donde
el flist = la mentirilla del mapa [0..]
Otro uso importante de listas infinitas es que ellos nos permiten que escribamos programas funcionales que representan redes de comunicar los procesos. Considere por ejemplo el Hamming numera el problema – nosotros tenemos que imprimir en el orden ascendente todos los números del formulario 2^a*3^b*5^c, para el a,b,c>=0.
Hay una solución buena a este problema por lo que se refiere a comunicar procesos como que pueden expresarse en Miranda siguen el hamming = 1: una (f 2) (una (f 3) (f 5))
donde
f un = [el n*a | n <- el hamming]
una (el a:x) (el b:y) = un: fusione x (el b:y), si un <b
= b: una (el a:x) y, si el a>b
= un: fusione x y, por otra parte,
Polimorfismo Strong la Mecanografía
Miranda se teclea fuertemente. Es decir, cada expresión y cada el subexpression tiene un tipo a que puede deducirse compilar tiempo, y cualquiera la inconsistencia en la estructura del tipo de una escritura resulta en un compile cronometre el mensaje del error. Nosotros aquí resumimos la anotación de Miranda brevemente para
sus tipos.
Hay tres tipos primitivos, num llamados, bool, y trabajo por horas. El tipo el num comprende entero y los números del punto flotantes (la distinción entre los enteros y se ocupan de números del punto flotantes en momento de la carrera – esto no se considera como ser una distinción del tipo). hay dos valores de bool del tipo, llamado Verdadero y Falso. El trabajo por horas del tipo comprende el ascii el carácter puso – las constantes del carácter son escrito en las solas citas, mientras usando Las convenciones de escape de C, por ejemplo 'un', '$', ' n' etc.
Si T es el tipo, entonces [T] es el tipo de listas cuyos elementos son de tipo T. por ejemplo [[1,2],[2,3],[4,5]] es de tipo [[el num]], eso es es un la lista de listas de números. Las constantes del cordón son de tipo [el trabajo por horas], de hecho un cordón como "hola" simplemente es una manera de la taquigrafía de escribir ['h', 'e', 'l', 'l', 'o'].
Si T1 a Tn son los tipos, entonces (T1,…, Tn) es el tipo de tuplas con los objetos de estos tipos como los componentes. Por ejemplo (Verdadero, "hello",36) es de tipo (el bool,[char],num).
Si T1 y T2 son los tipos, entonces T1->T2 es el tipo de una función con los argumentos en T1 y resultados en T2. Por ejemplo la suma de la función es de el tipo [el num]->num. El quadsolve de la función, dado antes, es de tipo num->num->num->[num]. la Nota que "-> " es correcto asociativo.
Las escrituras de Miranda pueden incluir las declaraciones del tipo. Éstos son los usando escrito ":: " significar es de tipo. El ejemplo
el sq:: el num -> el num
el sq n = n * n
La declaración del tipo no es necesaria, sin embargo. El recopilador siempre es capaz para deducir el tipo de un identificador de su ecuación definiendo. Las escrituras de Miranda contienen a menudo las declaraciones del tipo como éstos es útil para la documentación (y ellos proporcionan un cheque extra, desde el typechecker,
quéjese si el tipo declarado es incoherente con los inferimos uno).
Los tipos pueden ser los polymorphic, en el sentido de Milner [Milner 78]. Esto es indicado usando los símbolos * * * * * * el etc como un alfabeto de genérico teclee las variables. Por ejemplo, la función de identidad, definió en el La biblioteca de Miranda como
el id x = x
tiene el tipo siguiente
el id:: * -> *
esto significa que la función de identidad tiene muchos tipos. A saber todos aquéllos qué puede obtenerse sustituyendo un tipo arbitrario para el genérico el tipo inconstante, eg "num->num", "bool->bool", "(* -> * *) -> (* -> * *) " y para que en.
Nosotros ilustramos los Miranda teclean el sistema dando los tipos para algunos del las funciones definieron hasta ahora en este artículo
el fac:: el num -> el num
el ack:: el num -> el num -> el num
la suma:: [el num] -> el num
mes:: [el trabajo por horas] -> el bool
la marcha atrás:: [*] -> [*]
el fst:: (*, * *) -> *
el snd:: (*, * *) -> * *
el foldr:: (* -> * * -> * *) -> * * -> [*] -> * *
los permanente:: [*] -> [[*]]
El usuario Definió los Tipos
El usuario puede introducir los nuevos tipos. Esto se hace por una ecuación en ": := ". Por ejemplo un tipo de árboles binarios etiquetados (con numérico las etiquetas) se introduciría como sigue,
el árbol: := Nilt | los num del Nodo obligan a refugiarse en un árbol el árbol
Esto introduce tres nuevos identificadores – árbol de que es el nombre el teclee, y "Nilt" y "Nodo" que son los constructores para los árboles. Nilt es un constructor atómico, mientras el Nodo toma tres argumentos, de los tipos, mostrado. Aquí es un ejemplo de un árbol construyó usando a estos constructores
el t1 = Nodo 7 (el Nodo 3 Nilt Nilt) (el Nodo 4 Nilt Nilt)
Para analizar un objeto de usuario definido el tipo, nosotros usamos el modelo emparejando. Para el ejemplo aquí es una definición de una función por tomar la imagen del espejo de un árbol
refleje Nilt = Nilt
el espejo (el Nodo un x y) = el Nodo un (el espejo y) (el espejo x)
El usuario definió los tipos pueden ser los polymorphic – esto se muestra introduciendo uno o variables del tipo más genéricas como los parámetros del ": := " la ecuación. Para el ejemplo nosotros podemos generalizar la definición de árbol para permitir arbitrario las etiquetas, así,
el árbol *: := Nilt | el Nodo * (el árbol *) (el árbol *)
esto introduce a una familia de tipos del árbol, incluso el num del árbol, el bool del árbol, el tree(char->char), y así sucesivamente. Los tipos introducidos por ": := " se llaman las definiciones los tipos" "algebraicos.
Los tipos algebraicos son una idea muy general. Ellos incluyen el scalar la enumeración teclea, eg
el color: := Rojo | la Naranja | Amarillo | Green | Azul | el Índigo | la Violeta
y también nos da una manera de hacer la unión teclea, por ejemplo
el bool_or_num: := el bool Izquierdo | el num Correcto
Es interesante a nota que todo el datos básico teclea de Miranda pudo se defina de primeros principios, mientras usando ": := " las ecuaciones. Por ejemplo aquí son las definiciones del tipo para el bool, (natural) los números y listas,
el bool: := Verdadero | Falso
el nat: := Ceros | el nat de Suc
la lista *: := el Nada | Hace trampas * (la lista *)
Se hacen tipos teniendo como "num" construido en por las razones de eficacia – no es lógicamente necesario.
También es posible asociar las "leyes" con los constructores de un tipo algebraico que es aplicado siempre que un objeto del tipo sea construido. Por ejemplo nosotros podemos asociar las leyes con el constructor del Nodo de el tipo del árbol anteriormente, para que los árboles siempre sean equilibrados. Nosotros omitimos la discusión de este rasgo de Miranda aquí para la falta de espacio – Los lectores interesados encontrarán más detalles en las referencias [Thompson 86, tornero 85].
Teclee los Sinónimos
El programador de Miranda puede introducir un nuevo nombre por un ya haber existido el tipo. Nosotros usamos "== " para estas definiciones, para distinguirlos de las definiciones de valor ordinarias. Los ejemplos
el cordón == [el trabajo por horas]
la matriz == [[el num]]
Los sinónimos del tipo son completamente transparentes al typechecker – es mejor para pensar en ellos como los macros. También es posible introducir los sinónimos para las familias de tipos. Esto se hace usando los símbolos del tipo genéricos como los parámetros formales, como en
la serie * == [[*]]
así ahora el eg ` el num de la serie' es el mismo tipo como ` la matriz.'
Los Tipos de los Datos abstractos
Además de los tipos de hormigón, introdujo por ": := " o "== " las ecuaciones, Miranda permite la definición de tipos abstractos cuyo la aplicación está oculto del resto del programa. Para mostrar cómo esto trabaja nosotros
dé el ejemplo normal de definir la pila como un tipo del datos abstracto (aquí basó en las listas):
los abstype apilan *
con vacío:: la pila *
el isempty:: la pila * -> el bool
el empujón:: * -> la pila * -> la pila *
el estallido:: la pila * -> la pila *
la cima:: la pila * -> *
la pila * == [*]
vacío = []
el isempty x = (x = [])
empuje un x = (el a:x)
el estallido (el a:x) = x
la cima (el a:x) = un
Nosotros vemos que la definición de un tipo del datos abstracto consiste en dos las partes. Primero una declaración del formulario el "abstype… con… ", dónde lo siguiente de los nombres el "con" se llama la firma del lo abstracto los datos teclean. Estos nombres son la interfaz entre el tipo de los datos abstracto
y el resto del programa. Entonces un juego de ecuaciones que dan las encuadernaciones para los nombres introducidos en la declaración del abstype. Éstos se llaman las ecuaciones de aplicación.
La abstracción del tipo se da fuerza a por el typechecker. El mecanismo los trabajos como sigue. Cuando el typechecking las ecuaciones de aplicación el se tratan tipo abstracto y su representación como ser el mismo tipo. En el todo del resto de la escritura el tipo abstracto y su la representación se trata como dos separado y completamente el unrelated los tipos. Esto es algo diferente del mecanismo usual para llevando a cabo los datos abstractos teclea, pero tiene varios ventajas. Es discutido a la longitud algo mayor en [Tornero 85].
La Recopilación separada y Uniéndose
Los mecanismos básicos para la recopilación separada y unirse son sumamente simple. Cualquier escritura de Miranda puede contener uno o más directives del el formulario
% incluya el "pathname"
donde el "pathname" es el nombre de otro Miranda escritura archivo (qué poderío contiene incluya el directives, y así sucesivamente el recursively – ciclos en el incluya la estructura no se permite sin embargo). La visibilidad de nombres a una escritura incluyendo se controla por un director en el incluido la escritura, del formulario,
% los nombres de la exportación
Se permitía exportar los tipos así como los valores. No se permite para exportar un valor a un lugar dónde su tipo es desconocido, para que si usted exporta un objeto de un tipo localmente definido, los typename también deben exportarse. Exportando el nombre de un ": := " teclee automáticamente exporta todos su constructores. Si una escritura no contiene una exportación director, entonces, el valor predeterminado es que todos los nombres (y typenames) define será exportado (pero no aquéllos por que adquirió% incluya las declaraciones).
También se permitía escribir una escritura del paramaterized en que cierto los nombres y/o typenames se declaran como libre. Un ejemplo es que nosotros podría desear escribir un paquete por hacer el álgebra de la matriz sin saber lo que el tipo de los elementos de la matriz va a ser. Un título para tal un paquete podría parecerse:
% libre {el elemento:: el tipo
ponga a cero, unidad:: el elemento
el mult, agregue, substraiga, divida:: elemento->element->element
}
% el matmult de la exportación el eigenvectors del eigenvalues determinante…
|| aquí seguiría las definiciones de matmult, determinante,
|| el eigenvalues, etc. por lo que se refiere al cero de los identificadores libre,
|| la unidad, el mult, agrega, substraiga, divida
En la escritura usando, el correspondiendo% incluya la declaración debe dar un ponga de encuadernaciones para las variables libres de la escritura incluido. Para el ejemplo aquí es un instantiation del paquete de la matriz esbozado sobre, con los números reales como el tipo del elemento escogido:
% incluya el "matrix_pack"
{el elemento == el num; ceros = 0; la unidad = 1
el mult = *; agregue = +; substraiga = -; divida = /
}
Los tres directivas incluyen la exportación, y libre proporcione el Miranda programador con un flexible y tipo el mecanismo seguro por estructurar los pedazos más grandes de software de las bibliotecas de componentes más pequeños.
La recopilación separada se administra sin la intervención del usuario. Cada archivo que contiene una escritura de Miranda se sombrea por un archivo de código de objeto creado por el sistema, y se recrean los archivos de código de objeto automáticamente y relinked si ellos se vuelven fuera de fecha con respecto a cualquier pertinente la fuente. (Esta conducta es fuertemente análoga a eso logrado por el La UNIX programa hechura, sólo que aquí el usuario no se exige escribir un makefile – la información de la dependencia necesaria se infiere del % incluya el directives en la fuente de Miranda.)
El Estado de Aplicación actual
Una aplicación de Miranda está disponible para VAX, SOL-3, SOL-4, DECstation, MIPS, Apolo, y varias otras máquinas Berkeley corriente UNIX, y también para la serie de HP9000 bajo sistema 5. Esto es un
aplicación interpretiva que trabaja compilando las escrituras de Miranda a un código del intermedio basó en el combinators. Está corriendo actualmente a 400 sitios (a partir del 1990 de enero). Autorizando la información pueden obtenerse de la dirección neta
Aportes
- Tratamiento sistemático de la sobrecarga a través de las clases de tipos.
- Entrada/salida puramente funcional a través de Monadas para entrada/salida: permite realizar las tareas de Entrada/Salida de una forma puramente funcional, manteniendo transparencia referencial y sin tener efectos laterales. Desde el punto de vista del programador la monada es un tipo abstracto de dato, por lo tanto una monada de entrada salida provee operaciones primitivas de entrada/salida.
- Definición y utilización de módulos.
- Se pueden nombrar componentes de tipos de datos algebraicos.
- Clases de constructores de tipos.
- Currificación: Las funciones en Haskell tienen un único argumento y devuelven un único resultado. Si f toma un argumento de tipo T1 y devuelve un resultado de tipo T2 , f es de tipo T1 ® T2 , expresado como f :: T1 ® T2. Para simular funciones con más de un argumento, simplemente se entiende que la función definida se aplica al primero de ellos y el resultado es otra función que se aplica al segundo, y así sucesivamente. Es decir, la expresión f x1 x2 … xn se interpreta como (…((fx1)x2)…) xn . Si T1 es el tipo de x1 , T2 el de x2 ,. . . ,Tn es el tipo de xn y T es el tipo del resultado, el tipo de f es: T1 ® (T2 ® (…(Tn ® T ))) abreviado como T1 ® T2 ® … ® Tn ® T
Este proceso conocido como currificación en honor al matemático Haskell B. Curry, permite la creación de varias funciones mediante una sola definición. además permite reducir el
número de paréntesis necesarios para escribir expresiones.
Considérese la siguiente ecuación:
sumar x y = x + y
Suponiendo que el operador + suma enteros, la ecuación define una función sumar::Int® (Int® Int) que, abreviadamente, puede expresarse como sumar::Int ® Int ® Int. La expresión sumar 3 4 devuelve obviamente 7, pero en su evaluación intervienen dos funciones, primeramente la función sumar que, aplicada a 3, devuelve una función sin nombre que podemos llamar "sumar 3", y después esta segunda función que, aplicada a 4, devuelve 7. De hecho, sumar 3 es una expresión válida, cuyo tipo es Int -> Int.
Página siguiente |