- 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 |
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 |