¿Qué es Qriollo?
Qriollo es un lenguaje de programación funcional, impuro, estricto, rioplatense y en joda.
El compilador de Qriollo está desarrollado en Haskell, tiene múltiples backends, y puede generar código C, código Python, y bytecode para la JVM. La implementación se encuentra en fase inestable/experimental y cuenta con una interfaz de funciones gringas.
Qriollo es un lenguaje estáticamente tipado, con inferencia de tipos, polimorfismo paramétrico y clases de tipos. Incorpora un sistema de módulos simple. Tiene las funcionalidades usuales de un lenguaje de programación de la familia de ML: tipos de datos algebraicos, análisis de casos por comparación de patrones, clausuras léxicas, optimización de invocaciones a la cola, referencias mutables y call/cc. El backend para C cuenta con un algoritmo de recolección de basura exacto y stop-and-copy.
el programa es
escupir "Hola mundo\n"
Ejemplos
Elementos pares de una lista (para ejecutar interactivamente)
filtrar (la que dado x da x % 2# == 0#) [1#, 2#, 3#, 4#]
Calcular números primos (para compilar)
el esPrimo
dado p
da nadie (esDivisor p) (2..p - 1)
el esDivisor
dados p d
da p % d == 0#
los primos
dado n
da filtrar esPrimo (2..n)
el programa es
escupir . mostrar . primos $ 1000#
$ qr Primos.q --c -o Primos.c $ gcc -o Primos Primos.c -Wall $ ./Primos [2#, 3#, 5#, 7#, 11#, 13#, 17#, 19#, 23#, 29#, 31#, ...
Altura de un árbol binario
un Árbol de a es
bien Hoja a
bien Nodo a (Árbol de a) (Árbol de a)
el máximo
dados n m
si n > m da n
si no da m
la altura de Árbol de a en Numerito
dado (Hoja _) da 1
dado (Nodo _ a b) da 1 + máximo (altura a)
(altura b)
el programa es escupir . mostrar . altura $
Nodo 'a'
(Nodo 'b'
(Hoja 'c')
(Hoja 'd'))
(Nodo 'e'
(Hoja 'f')
(Hoja 'g'))
$ qr AB.q
3#
Clases de tipos
cualidad Escalable para coso
el escalar de Numerito en coso en coso
boludo
encarnar Escalable para Numerito
el escalar dados n m da n * m
boludo
encarnar Escalable para [coso] con Escalable para coso
el escalar es fap . escalar
boludo
el programa es escupir . mostrar $
escalar 2 [[1#, 2#, 3#],
[4#, 5#, 6#],
[7#, 8#, 9#]]
$ qr ClasesDeTipos.q
[[2#, 4#, 6#], [8#, 10#, 12#], [14#, 16#, 18#]]
Bajarlo
Prerrequisitos
Se requiere ghc (Glasgow Haskell Compiler) para compilar el compilador de Qriollo.
Instalación
- Manera 1: desde el AUR
- Instalar el paquete AUR de Qriollo:
$ yaourt -S qriollo
- El instalador le da un valor a la variable de entorno RUTA_QRIOLLO para que incluya el directorio actual (.) y el directorio donde se encuentra el Chamuyo.q (normalmente /usr/share/qriollo). Si todavía no volviste a loguearte, o en caso de que lo anterior no funque, podés agregar manualmente la línea siguiente a tu perfil:
$ export RUTA_QRIOLLO=.:/usr/share/qriollo
- Ponele:
$ echo enchufar Chamuyo el programa es escupir \"Hola mundo\\n\" > HolaMundo.q $ qr HolaMundo.q hola
- Instalar el paquete AUR de Qriollo:
- Manera 2: desde github
- Clonar el repositorio github:
$ git clone https://github.com/qriollo/qriollo.git $ cd qriollo/
- Compilar:
$ make
- Probarlo:
$ ./qr HolaMundo.q Hola mundo
- Clonar el repositorio github:
- Manera 3: a manopla
- Descargar los fuentes de Qriollo:
$ tar xzf Qriollo-0.91.tar.gz $ cd Qriollo-0.91/
- Compilar:
$ make
- Probarlo:
$ ./qr HolaMundo.q Hola mundo
Compilación a C
$ ./qr HolaMundo.q --c -o HolaMundo.c $ gcc -o HolaMundo HolaMundo.c -Wall $ ./HolaMundo Hola mundo
Compilación a Python (versión 2)
$ ./qr HolaMundo.q --py -o HolaMundo.py $ python2 HolaMundo.py Hola mundo
Compilación a la JVM
$ ./qr HolaMundo.q --jvm $ java -jar HolaMundo.jar Hola mundo
- Descargar los fuentes de Qriollo:
Manual
Índice
- Instalación
- Uso del intérprete y definición de valores simples (el...es...)
- Punto de entrada del programa (el programa...)
- Declaración y uso de funciones (el...dado...da...)
- Definición de los tipos de datos algebraicos (un...es...bien...)
- Declaración de funciones por análisis de casos (el...dado...da...dado...da...)
- Coincidencia de patrones (mirar...si...boludo)
- Guardas condicionales (dado...si...da...si no da...)
- Funciones anónimas (la que dado...da)
- Declaraciones locales (donde...boludo)
- Declaraciones locales (ponele que...en)
- Tipos y anotaciones de tipos (...de...)
- Polimorfismo
- Condiciones (si...da...si no da...)
- Declaración de operadores (chirimbolos)
- Clases de tipos (cualidad...para...boludo, encarnar...para...boludo)
- Continuaciones y call/cc (ccc, invocar)
- Mónadas y notación che (che...es...)
- Interfaz de funciones gringas (gringo ... y GRINGO...)
- Compilación condicional (pragma SI...BOLUDO)
- Módulos (entregar..., enchufar...)
- Caso de estudio: intérprete de Lizp
- Convenciones léxicas
- Recomendaciones de estilo
- Bichos y limitaciones conocidas
- Licencia
- Reconocimientos
Instalación
Ir a la sección Bajarlo.Uso del intérprete y definición de valores simples (el...es...)
Normalmente todos los programas en Qriollo los empezás importando el módulo Chamuyo, con la declaración "enchufar Chamuyo". El módulo Chamuyo es responsable de definir las primitivas del lenguaje y algunas funciones básicas.
Un programa Qriollo es una lista de declaraciones. La declaración más simple es la declaración de un valor, que vincula un nombre a un valor. Tiene la estructura "el <nombre> es <valor>". Una regla general es que las palabras reservadas te las acepta indistintamente en cualquier número y género: cualesquiera de las formas el, la, los o las le dan lo mismo, e igualmente le da lo mismo usar es o son:
la fruta es "Manzana"
el tres es 1# + 2#
los numeritos son [1#, 2#, 3#, 4#]
Podés cargar las definiciones en modo interactivo pasando la opción -i al intérprete Qriollo:
$ qr DeclaracionValor.q -i qriollo> fruta "Manzana" de Texto qriollo> tres 3# de Numerito
El intérprete te indica el resultado, y también te indica su tipo (Texto en el caso de "Manzana" y Numerito en el caso de 3#).
Punto de entrada del programa (el programa...)
Para que podás ejecutar un programa independiente, o compilarlo, tenés que incluir un punto de entrada, que es la definición del valor programa. Verbigracia:
el programa es
escupir (mostrar (númerosHasta 10))
los númerosHasta
dado 0# da []
dado n da númerosHasta (n - 1#) ++ [n]
$ qr NumerosHasta.q
[1#, 2#, 3#, 4#, 5#, 6#, 7#, 8#, 9#, 10#]
Declaración y uso de funciones (el...dado...da...)
Podés definir funciones directamente utilizando otra forma de definición: "la <función> dado <parámetros> da <resultado>". Igual que antes, podés escribir indistintamente dado, dada, dados o dadas, siempre prefiriendo la forma eufónica en contexto. Como es habitual en los lenguajes funcionales gringos, las funciones las definís al chimichurri y la aplicación de funciones la denotás con la yuxtaposición. La aplicación es asociativa a izquierda.
el doble dado x da 2# * x
el dosVeces dados f x da f (f x)
el cuádruple es dosVeces doble
$ qr Doble.q -i qriollo> cuádruple 10# 40# de Numerito
Definición de los tipos de datos algebraicos (un...es...bien...)
Podés definir un tipo algebraico con la sintaxis:
bien <constructor> <tipos_parámetros>
...
bien <constructor> <tipos_parámetros>
Como siempre, podés usar cualquiera de los artículos un, una, unos o unas. Los tipos los podés poner parámetricos (por ejemplo "Árbol de Numerito"), en cuyo caso la sintaxis para que los declarés es:
bien <constructor> <tipos_parámetros>
...
bien <constructor> <tipos_parámetros>
Un ejemplo de un tipo algebraico que no tiene parámetros es:
una Expresión es
bien Constante Numerito
bien Sumar Expresión Expresión
bien Multiplicar Expresión Expresión
Si levantás esta definición y evaluás la expresión Constante 10#, vas a obtener un error que te indica que el tipo Expresión no tiene la cualidad Mostrable, que le permite a Qriollo convertirlo en un texto para escupir:
$ qr DefExpresion.q -i qriollo> Constante 10# Se te escapó la tortuga. Esto no tipa ni en pedo. El tipo del argumento no coincide con el tipo esperado por la función. No hay una instancia declarada para Mostrable para Expresión
Pero podés cargar el archivo y preguntarle cuál es el tipo de una expresión, anteponiendo :t como sigue:
$ qr DefExpresion.q -i qriollo> :t Constante de Numerito en Expresión qriollo> :t Constante 10# de Expresión qriollo> :t Sumar de Expresión en Expresión en Expresión qriollo> :t Sumar (Constante 10#) de Expresión en Expresión qriollo> :t Sumar (Constante 10#) (Constante 10#) de Expresión
Un ejemplo de tipo algebraico con parámetros es:
un Árbol de tipoNodo tipoHoja es
bien Hoja tipoHoja
bien Nodo tipoNodo
(Árbol de tipoNodo tipoHoja)
(Árbol de tipoNodo tipoHoja)
Las variables de tipo que Qriollo no instancia durante el proceso de inferencia les da un nombre anónimo.
$ qr DefArbol.q -i qriollo> :t Hoja de sarasa66 en Árbol de coso67 sarasa66 qriollo> :t Hoja 10# de Árbol de coso67 Numerito qriollo> :t Nodo 'a' (Hoja 10#) de (Árbol de Letra Numerito) en Árbol de Letra Numerito qriollo> :t Nodo 'a' (Hoja 10#) (Hoja 10#) de Árbol de Letra Numerito
Declaración de funciones por análisis de casos (el...dado...da...dado...da...)
Las funciones las podés definir por casos haciendo comparación de patrones, utilizando la sintaxis:
dados <patrones> da <resultado>
...
dados <patrones> da <resultado>
<patrones> debe ser una lista de patrones, uno por cada parámetro que la función espera. Cada patrón puede ser un comodín (que lo escribís con un guión bajo "_"), una variable (un nombre que lo podés vincular a cualquier valor), o una aplicación de un constructor a una lista de patrones.
Ponele, la suma de los valores que están en los nodos de un árbol como el de arriba la podés definir como sigue:
un Árbol de tipoNodo tipoHoja es
bien Hoja tipoHoja
bien Nodo tipoNodo
(Árbol de tipoNodo tipoHoja)
(Árbol de tipoNodo tipoHoja)
la suma_de_los_nodos
dada (Hoja n) da n
dado (Nodo n izq der) da
n + suma_de_los_nodos izq
+ suma_de_los_nodos der
$ qr SumaArbol -i qriollo> suma_de_los_nodos (Nodo 1# (Hoja 2#) (Hoja 3#)) 6# de Numerito
Podés hacer comparación de patrones con constantes (letras, numeritos), tipos algebraicos nativos qriollos (tuplas, listas), y constructores de tipos de datos algebraicos. El patrón "_" (guión bajo) representa un comodín que puede coincidir con cualquier estructura que recibás, y no le da valor a ningún nombre. El mismo nombre no lo podés meter dos veces en un patrón. El análisis de casos tiene que ser exhaustivo. Los patrones los podés anidar a una profundidad arbitraria. Si el patrón no es atómico lo tenés que encerrar entre paréntesis.
Ejemplo de tipos de datos algebraicos existentes:
el neg
dado Sí da No
dado No da Sí
la conj
dados Sí Sí da Sí
dados _ _ da No
$ qr PatPosta -i qriollo> conj (neg No) (neg No) Sí de Posta
Ejemplo de patrones anidados con tuplas y numeritos:
el máximo_común_divisor
dados (a, 0#) da a
dados (a, b) da máximo_común_divisor (b, a % b)
$ qr PatTupla -i qriollo> máximo_común_divisor (100#, 60#) 20# de Numerito
Tené re en cuenta que si querés definir esta función de manera bien qriolla tenés que usar su variante al chimichurri, recibiendo dos parámetros de tipo Numerito en vez de un solo parámetro de tipo (Numerito, Numerito).
Ejemplo de patrones con dos listas:
el emparejar
dadas [] _ da []
dadas (_ : _) [] da []
dadas (x : xs) (y : ys) da
(x, y) : emparejar xs ys
$ qr PatLista -i qriollo> emparejar [1#, 2#, 3#] ['a', 'b', 'c'] [(1#, 'a'), (2#, 'b'), (3#, 'c')] de [(Numerito, Letra)]
Coincidencia de patrones (mirar...si...boludo)
Y si no, podés usar una expresión mirar...boludo para hacer análisis de casos por comparación de patrones. A diferencia de la construcción anterior, en la que la comparación de patrones es parte de la sintaxis de una definición de función, mirar...boludo es una expresión que (como cualquier expresión) la podés usar para formar expresiones más grandes, y no hace falta que introduzcás una definición:
si <patrón> da <resultado>
...
si <patrón> da <resultado>
[si no da <resultado>]
boludo
La cláusula "si no" la podés meter si querés, y si no querés no, y representa el resultado predeterminado por omisión de analizar los casos. Igual que cuando definís funciones por análisis de casos, la cobertura de los casos la tenés que hacer exhaustiva.
En lo que refiere al resaltado de sintaxis, todas las palabras clave que te las resalta en verde sirven para marcar que empieza una estructura que la tenés que cerrar con un correspondiente boludo (o boluda). Excepto, claro está, en el caso de la palabra clave boludo, que no requiere que pongás un boludo posterior.
Ejemplo de análisis de casos con constantes:
el escupir_dígito
dado n da
escupir (
mirar n
si 0# da "cero"
si 1# da "uno"
si 2# da "dos"
si 3# da "tres"
si 4# da "cuatro"
si 5# da "cinco"
si 6# da "seis"
si 7# da "siete"
si 8# da "ocho"
si 9# da "nueve"
si no da "dígito incorrecto"
boludo
)
$ qr MirarDigitos -i qriollo> escupir_dígito 3# tres () de ()
Fijate que la función escupir_dígito definida arriba te devuelve el valor () de tipo (), que corresponde a la tupla 0-aria o al valor unit tradicional en lenguajes funcionales gringos, pero además tiene el efecto colateral de escupirte el texto "tres" en la salida estándar.
Ejemplo de análisis de casos con patrones anidados:
Determinar el mínimo de una lista.
el mínimo dada lista da
mirar lista
si [] da
escupir "La lista vacía no tiene mínimo.\n";
espichar 1
si [x] da x
si x : y : xs da
mirar x < y
si Sí da mínimo (x : xs)
si no da mínimo (y : xs)
boludo
boludo
$ qr MirarMinimo -i qriollo> mínimo [10#, 9#, 8#, 7#, 11#, 12#] 7# de Numerito
Una manera más qriolla de definir esta función te la mostramos en los párrafos que siguen más abajo, usando guardas condicionales.
Guardas condicionales (dado...si...da...si no da...)
Cuando usás una cláusula dado...da... de las que forman parte de las definiciones de funciones por análisis de casos, podés meter una secuencia de guardas condicionales:
si <condición> da <resultado>
...
si <condición> da <resultado>
si no da <resultado>
Fijate que en este caso el "si no" lo tenés que poner sí o sí.
Ponele, si querés determinar el mínimo de una lista:
el mínimo
dada [] da
escupir "La lista vacía no tiene mínimo.\n";
espichar 1
dada [x] da x
dada (x : y : xs)
si x < y da mínimo (x : xs)
si no da mínimo (y : xs)
Nota: una vez que la expresión a evaluar la comparó exitosamente con el patrón, Qriollo agarra esa rama, a diferencia de lo que pasa en otros lenguajes gringos como Haskell. Las condiciones adentro de una cláusula dado...si...da...si no...da las evalúa secuencialmente.
Funciones anónimas (la que dado...da)
Podés declarar funciones anónimas mediante la expresión:
La construcción te acepta varios parámetros:
Y más en general te acepta que pongás un análisis de casos con guardas condicionales como cualquier función:
dados <parámetros> da <resultado>
...
dados <parámetros> da <resultado>
Ponele:
el aplicar123
dada f da [f 1#, f 2#, f 3#]
el aplicarL
dados m b
da aplicar123 (la que dado x da m * x + b)
$ qr Anonimas1 -i qriollo> aplicarL 2# 1# [3#, 5#, 7#] de [Numerito]
el it
dados z p f
si p z da [z]
si no da z : it (f z) p f
la conjetura_de_Collatz dado n da
it n
(la que dado n da n == 1#)
(la que dado n
si n % 2# == 0# da n / 2#
si no da 3# * n + 1#)
$ qr Anonimas2 -i qriollo> conjetura_de_Collatz 3# [3#, 10#, 5#, 16#, 8#, 4#, 2#, 1#] de [Numerito]
Declaraciones locales (donde...boludo)
Cada cláusula dado...da..., igual que su variante con guardas condicionales, te acepta que metás una secuencia de declaraciones locales:
donde
<declaración>
...
<declaración>
boludo
Cada una de ellas es una declaración de un valor simple (el <nombre> es <valor>) o de una función (la <función> dados <patrones> da <resultado>), posiblemente con guardas condicionales, y posiblemente también con declaraciones locales anidadas. Como Qriollo usa evaluación estricta, los valores que le diste a cada una de las declaraciones locales las evalúa antes de evaluar el cuerpo (resultado).
Ejemplo:
Escribir un número natural n en base b:
el en_base
dados b n
si n == 0# da []
si no da r () ++ [d]
donde
el d es n % b
el r dado _ da en_base b (n / b)
boludo
$ qr Base -i qriollo> en_base 2# 999999# [1#, 1#, 1#, 1#, 0#, 1#, 0#, 0#, 0#, 0#, 1#, 0#, 0#, 0#, 1#, 1#, 1#, 1#, 1#, 1#] de [Numerito]
Las declaraciones que las introducís con una cláusula donde...boludo las podés hacer recursivas y mutuamente recursivas.
Declaraciones locales (ponele que...en)
También podés introducir declaraciones locales como expresiones, igual que como en el letrec gringo:
<declaración>
...
<declaración>
en
<cuerpo>
Ponele, el algoritmo para incorpordenar una lista:
el incorpordenar
dada [] da []
dada [x] da [x]
dada lista da
ponele que
el tamaño es longitud lista
la mitá es tamaño / 2
la primera_mitá es agarrar mitá lista
la segunda_mitá es tirar mitá lista
en
incorporar (incorpordenar primera_mitá)
(incorpordenar segunda_mitá)
el incorporar
dadas [] ys da ys
dadas (x : xs) [] da (x : xs)
dadas (x : xs) (y : ys)
si x < y da x : incorporar xs (y : ys)
si no da y : incorporar (x : xs) ys
la longitud
dada [] da 0#
dada (_:xs) da 1# + longitud xs
el agarrar
dados 0# _ da []
dados n [] da []
dados n (x:xs) da x : agarrar (n - 1) xs
el tirar
dados 0# xs da xs
dados _ [] da []
dados n (_:xs) da tirar (n - 1) xs
el programa es
escupir . mostrar . incorpordenar $
[70#, 78#, 24#, 47#, 63#, 36#, 5#,
96#, 54#, 86#, 30#, 95#, 0#, 25#,
18#, 18#, 73#, 96#, 1#, 1#]
$ qr Incorpordenar -i
[0#, 1#, 1#, 5#, 18#, 18#, 24#, 25#, 30#, 36#, 47#,
54#, 63#, 70#, 73#, 78#, 86#, 95#, 96#, 96#]
Tipos y anotaciones de tipos (...de...)
Qriollo tiene tipos básicos ya predefinidos en el lenguaje (Posta, Numerito, Letra, etc.), y tipos definidos por vos. Por ahora no tiene un mecanismo implementado para definir alias (o renombres) de tipos.
En todas las definiciones de valores ("el <nombre> es ...") y de funciones ("la <función> dado ...") podés declarar explícitamente sus tipos, escribiendo, respectivamente "el <nombre> de <tipo> es ..." y "la <función> de <tipo> dado ...".
El tipo de las funciones
El tipo de las funciones es el tipo más importante en un lenguaje funcional como Qriollo. Si fulano es un tipo y mengano es otro tipo, entonces (fulano en mengano) es el tipo de las funciones que reciben fulanos y devuelven menganos. El constructor infijo binario en es asociativo a derecha, como siempre pasa en los lenguajes funcionales gringos, y de esta manera "coso en coso en coso en coso" es el mismo tipo que "coso en (coso en (coso en coso))".
El tipo Posta
Tiene dos constructores: Sí y No. La manera de usarlos es que les hagás análisis de casos por comparación de patrones, o que usés una estructura condicional, como vas a ver después.
Ponele:
el y de Posta en Posta en Posta
dados Sí Sí da Sí
dados Sí No da No
dados No Sí da No
dados No No da No
el o de Posta en Posta en Posta
dados Sí Sí da Sí
dados Sí No da Sí
dados No Sí da Sí
dados No No da No
el neg de Posta en Posta
dado Sí da No
dado No da Sí
$ qr Logica1 -i qriollo> o (neg Sí) (neg (y No Sí)) Sí de Posta
El tipo Numerito
Tiene 232 constructores, que los notás 0#, 1#, 2#, ..., 4294967295#. Ojo, la sintaxis sin el cardinal (0, 1, 2, ...) está reservada para representar valores genéricos de tipos de datos que encarnen la cualidad Digital, o sea que tengan una operación para transformar un número natural cualquiera en un elemento del tipo. Como caso particular, el tipo de datos Numerito encarna la cualidad Digital, lo que permite que escribás 0 para representar el numerito 0#, 1 para representar el numerito 1#, y así sucesivamente.
Qriollo por el momento no tiene soporte léxico para que escribás constantes numéricas de ninguna manera alternativa, como por ejemplo, constantes numéricas en distintas bases de numeración.
Los numeritos los podés usar haciendo análisis de casos con sus valores comparandolós con patrones, o mediante los operadores aritméticos y relacionales:
-Expresión- | -Resultado- | -Significado- |
10# + 3# | => 13# | suma |
10# - 3# | => 7# | resta |
10# * 3# | => 30# | producto |
10# / 3# | => 3# | división entera |
10# % 3# | => 1# | resto en la división |
10# ** 3# | => 1000# | potencia |
10# & 3# | => 2# | "y" bit a bit |
10# | 3# | => 11# | "o" bit a bit |
10# ^ 3# | => 9# | "xor" bit a bit |
10# == 3# | => No | comparación por igual |
10# != 3# | => Sí | comparación por distinto |
10# < 3# | => No | comparación por menor |
10# > 3# | => Sí | comparación por mayor |
10# <= 3# | => No | comparación por menor o igual |
10# >= 3# | => Sí | comparación por mayor o igual |
~10# | => 4294967285# | complemento bit a bit |
Ponele:
el factorial de Numerito en Numerito
dado 0# da 1#
dado n da n * factorial (n - 1#)
el es_par de Numerito en Posta
dado n da n & 1# == 0#
$ qr Aritmetica -i qriollo> factorial 7# 5040# de Numerito qriollo> es_par (factorial 1#) No de Posta
El tipo Letra
Tiene un montón de constructores, tantos como códigos de punto Unicode. Los valores de tipo Letra son 'a', 'b', 'c', etcétera, donde el caracter Unicode lo metés encerrado entre dos comillas simples. Por ahora la única sintaxis léxica permitida para que ingresés constantes de tipo Letra es que las escribás literalmente en el archivo fuente de Qriollo, que Qriollo te lo presume codificado en UTF-8. Además, te soporta algunas secuencias de escape:
-Escape- | -Código- | -Letra- |
'\a' | 7 | campanita |
'\b' | 8 | backspace |
'\f' | 12 | salto de página |
'\t' | 9 | tab |
'\v' | 11 | tab vertical |
'\n' | 10 | salto de línea |
'\r' | 13 | retorno de carro |
'\"' | 34 | comilla doble (") |
'\'' | 39 | comilla simple (') |
'\\' | 92 | contrabarra (\) |
El soporte para Unicode por ahora tiene algunos caprichos y limitaciones. Los caracteres los podés utilizar haciendo análisis de casos por comparación de patrones, y también mediante las funciones letra_a_numerito que convierte una letra en su código de punto Unicode, y numerito_a_letra que es la operación inversa.
Ponele:
la siguiente_letra de Letra en Letra
dada x da numerito_a_letra (letra_a_numerito x + 1)
$ qr Letras -i qriollo> siguiente_letra (siguiente_letra 'a') 'c' de Letra
El tipo Lista
Si fulano es un tipo, entonces [fulano] es el tipo de las listas que contienen fulanos. La sintaxis para literales de tipo lista es: [elemento1, elemento2, ..., elementon] . Podés no poner ningún elemento, y en ese caso te lo entiende como la lista vacía ( [] ). También te admite omitir o escribir la coma final. Ponele, [1#, 2#, 3#] y [1#, 2#, 3#,] son dos expresiones que representan la misma lista de numeritos. De hecho son dos maneras que tenés de escribir la misma expresión. Ponele:
el invertir
dada [] da []
dada (x : xs) da agregar_al_final x (invertir xs)
el agregar_al_final
dados x [] da [x]
dados x (y:ys) da y : agregar_al_final x ys
$ qr InvertirLista -i qriollo> invertir [1#, 2#, 3#] [3#, 2#, 1#] de [Numerito]
Los tipos de las n-uplas
Qriollo tiene un tipo para las tuplas de n elementos, donde acá n puede ser 0 o un número natural mayor que 1. Si fulano1, fulano2, ..., y fulanon son tipos, entonces (fulano1, fulano2, ..., fulanon) es el tipo de las n-uplas cuyo i-ésimo elemento es de tipo fulanoi.
En el caso particular en el que n = 0, tenés el tipo unitario de las 0-uplas que lo escribís () y tenés un único valor también escrito (). El valor () lo podés usar de manera convencional como valor que devolvés cuando el que el resultado de un cómputo no importa, como cuando querés usar operaciones que tienen efectos colaterales. Ponele:
qriollo> escupir "Hola mundo\n"
Hola mundo
()
de ()
La operación escupir le das un texto y te lo escupe por la salida estándar. Además, devuelve el valor () que tiene tipo ().
La manera que tenés de usar las n-uplas es que hagás análisis de casos por comparación de patrones. Ponele:
el sumar_pares de (Numerito, Numerito)
en (Numerito, Numerito)
en (Numerito, Numerito)
dados (x1, y1)
(x2, y2)
da (x1 + x2, y1 + y2)
$ qr SumarPares -i qriollo> sumar_pares (1#, 2#) (10#, 20#) (11#, 22#) de (Numerito, Numerito)
El tipo Texto
El tipo Texto te sirve para representar cadenas de texto, que son tiras de letras. El tipo Texto tiene un constructor solo que también se llama Texto, que le das una lista de letras y te devuelve un texto. La manera que tenés de usar los textos es que les hagás análisis de casos por comparación de patrones para acceder a la lista de letras que hay por abajo.
También si querés podés usar la sintaxis más común en los lenguajes gringos: un texto lo delimitás con una comilla doble (") a la izquierda y con otra a la derecha, y a todas las letras del medio las yuxtaponés para formar la lista de letras en cuestión. Las secuencias de escape que te reconoce son las mismas que las que ya dijimos en la sección que le corresponde al tipo Letra. Ponele:
- "cronopio" equivale a (Texto ['c', 'r', 'o', 'n', 'o', 'p', 'i', 'o']).
- "dije \"hola\"" equivale a (Texto ['d', 'i', 'j', 'e', ' ', '"', 'h', 'o', 'l', 'a', '"']).
- "\\\"" equivale a (Texto ['\\', '"']).
- "a\n" equivale a (Texto ['a', '\n']).
- "a\\n" equivale a (Texto ['a', '\\', 'n']).
- "" equivale a (Texto [])
Ponele:
OJO. Funciona solamente para letras en ASCII.
el mayusculizar_letra de Letra en Letra
dada l da numerito_a_letra (letra_a_numerito l & ~32)
el mayusculizar_texto de Texto en Texto
dado "" da "" OJO. Caso vacío
dado (Texto (letra : letras))
da Texto (mayusculizar_letra letra : letras)
$ qr MayusculizarTexto -i qriollo> mayusculizar_texto "hola" "Hola" de Texto
El tipo Quizá
Igual que otros lenguajes gringos, Qriollo te permite que extendás cualquier tipo que querás con un valor distinguido. Si coso es un tipo, Quizá de coso es otro tipo cuyos valores son Nada, y un montón de valores que se escriben (Este chabón), donde chabón puede ser cualquier valor del tipo coso. La manera de que usés el tipo Quizá de coso es que hagás análisis de casos por comparación de patrones. Ponele:
el mín de Numerito en Numerito en Numerito
dados x y
si x < y da x
si no da y
el mínimo de [Numerito] en Quizá de Numerito
dada [] da Nada
dada (x : xs) da
mirar mínimo xs
si Nada da Este x
si Este y da Este (mín x y)
boludo
$ qr QuizaMinimo -i qriollo> mínimo [] Nada de Quizá de Numerito qriollo> mínimo [2#, 1#, 3#] (Este 1#) de Quizá de Numerito
El tipo Ref
Las variables en Qriollo no son cosas asignables: cuando una variable la asociaste a un valor, cagaste: la dejaste asociada a dicho valor para siempre. Por otro lado, los valores en Qriollo son inmutables, y eso quiere decir que no los podés modificar a lo largo del tiempo. La única excepción a esta regla es el tipo de las referencias, que te deja que introduzcás valores mutables de tal manera que vos los controlés.
Si fulano es un tipo, Ref de fulano es el tipo de las referencias mutables a valores de tipo fulano. El tipo de las referencias tiene un constructor que también se llama Ref, que le das un valor de tipo fulano y te devuelve una celda mutable que referencia ese valor. Si r es una referencia de tipo Ref de fulano, la podés usar de dos maneras: en primer lugar, si ponés la expresión desref r te devuelve el valor de tipo fulano que está siendo referenciado por la celda r. En segundo lugar, si x es cualquier valor de tipo fulano, con la expresión r := x mutás la celda r para que pase a referenciar el valor designado por x. La expresión r := x tiene un efecto colateral pero siempre te devuelve el mismo valor ().
Como una utilidad práctica que sirve para que podás definir programas con efectos colaterales, Qriollo te proporciona el operador punto y coma (";") que te sirve si querés componer dos expresiones secuencialmente: la expresión (<expresión1> ; <expresión2>) primero evalúa <expresión1> descartando su valor, y después evalúa <expresión2>.
Ejemplo básico de referencias:
qriollo> Ref 1# (Ref 1#) de Ref de Numerito qriollo> ponele que la x es Ref 1# en desref x 1# de Numerito qriollo> ponele que la x es Ref 1# en x := 5; desref x 5# de Numerito qriollo> ponele que la x es Ref 1# en x := desref x + 1#; desref x 2# de Numerito
Factorial iterativo usando referencias:
En este ejemplo definimos el factorial de un numerito n como un proceso iterativo que agarra e inicializa p e i como referencias a 1#, y en cada paso cambia sus valores para que referencien respectivamente el producto (desref p * desref i) y la suma (desref i + 1#).
el factorial_iterativo dado n da
iterar ();
desref p
donde
el i es Ref 1#
el p es Ref 1#
el iterar dado () da
si desref i > n da ()
si no da
p := desref p * desref i;
i := desref i + 1#;
iterar ()
boludo
$ qr Referencias1 -i qriollo> factorial_iterativo 5# 120# de Numerito
Evaluación diferida usando referencias:
En este ejemplo definimos dos funciones que implementan un protocolo de evaluación diferida: bancame agarra y recibe una función f de () en coso y te devuelve un valor de tipo Promesa de coso, que representa el cómputo de f (). La función dame le das una Promesa de coso y te devuelve el correspondiente coso. Una promesa p la representás con una referencia que inicialmente apunta a la función f. La primera vez que evaluás dame p te evalúa f () y se actualiza la promesa p para que apunte al resultado final de evaluar f (). Cuando te evalúa dame p las siguientes veces, Qriollo te devuelve el valor memorizado, sin volver a evaluar f () de nuevo.
una Valor de coso es
bien TodavíaNoEvaluada (() en coso)
bien YaEvaluada coso
una Promesa de coso es
bien Prometer (Ref de (Valor de coso))
el bancame de (() en coso)
en Promesa de coso
dada f
da Prometer (Ref (TodavíaNoEvaluada f))
el dame de Promesa de coso
en coso
dado (Prometer r) da
mirar desref r
si TodavíaNoEvaluada f da
ponele que la x es f () en
r := YaEvaluada x;
x
si YaEvaluada x da x
boludo
$ qr Referencias2 -i qriollo> ponele que la p es bancame (la que dado () da escupir "hola\n"; 1# + 1#) en 7# 7# de Numerito qriollo> ponele que la p es bancame (la que dado () da escupir "hola\n"; 1# + 1#) en dame p hola 2# de Numerito qriollo> ponele que la p es bancame (la que dado () da escupir "hola\n"; 1# + 1#) en (dame p, dame p) hola (2#, 2#) de (Numerito, Numerito)
Fijate que en el primer caso la promesa no la utilizás de verdad, o sea que no te escupe el "hola" en pantalla. En el segundo caso, la promesa la utilizás una vez, o sea que sí te escupe el "hola" en pantalla y además te devuelve el valor prometido. En el tercer caso, la promesa la usás dos veces: la primera vez que forzás la promesa, para calcular la primera componente del par, te evalúa la función, escupiéndote el "hola" por pantalla, pero al momento de calcular la segunda componente del par el resultado 2# ya lo tiene memorizado, y te lo devuelve pero no te lo vuelve a calcular.
Igualdad de referencias
La comparación de referencias responde a su identidad como objetos, o sea, a la coincidencia de la locación de memoria a la que se refieren. Ponele:
$ qr -i qriollo> Ref 1# == Ref 1# No de Posta qriollo> ponele que la r es Ref 1# en r == r Sí de Posta
Polimorfismo
Los tipos pueden incluir variables de tipo, que las escribís con nombres empezados en minúscula. Esto te permite que definás valores cuyo tipo es polimórfico en dichas variables. Ponele:
OJO. Composición de funciones.
la composición de (mengano en zutano)
en (fulano en mengano)
en fulano en zutano
dadas f g da
la que dado x da f (g x)
OJO. Aplicar una función a cada elemento de una lista.
el apl de (coso en cosito) en [coso] en [cosito]
dadas _ [] da []
dadas f (x : xs) da f x : apl f xs
OJO. Dar vuelta el orden de los argumentos.
el vueltargar de (a en b en c) en b en a en c
dadas f x y da f y x
$ qr Polimorfismo -i qriollo> composición numerito_a_letra letra_a_numerito 'a' 'a' de Letra qriollo> apl letra_a_numerito ['h', 'o', 'l', 'a'] [104#, 111#, 108#, 97#] de [Numerito] qriollo> vueltargar composición numerito_a_letra letra_a_numerito 97# 97# de Numerito
Cuando Qriollo encuentra un conjunto de definiciones de varios valores, que pueden ser, o no, mutuamente recursivos, ya sea en:
- una expresión: ponele que <definiciones> en <cuerpo>,
- una cláusula: <cuerpo> donde <definiciones> boludo,
- la lista de definiciones que conforman un programa,
si las definiciones no las acompañás de una declaración de tipos, se portan de manera polimórfica en el cuerpo, pero monomórfica en todos los usos que están al mismo nivel. Ponele, el uso de identidad1 falla, porque acá estás tratando de usar una función que no viene acompañada de una declaración de tipos de manera polimórfica (con un argumento de tipo Posta y otro de tipo Letra) por una definición que está al mismo nivel:
la identidad1 dado x da x
el programa es (identidad1 Sí, identidad1 'a')
$ qr IdPolimorfica1 Se te escapó la tortuga. Esto no tipa ni en pedo. El tipo del argumento no coincide con el tipo esperado por la función. No unifican los tipos. Letra en lópez64 Posta en Posta
En cambio, el uso de identidad2 funciona, porque acá lo estás usando de manera polimórfica adentro del cuerpo del donde...boludo:
el programa es
(identidad2 Sí, identidad2 'a')
donde
la identidad2 dado x da x
boludo
$ qr IdPolimorfica2 -i qriollo> programa (Sí, 'a') de (Posta, Letra)
El uso de identidad3 también es correcto, porque la función la acompañás de una declaración de tipos que la declara como polimórfica:
la identidad3 de coso en coso
dado x da x
el programa es (identidad3 Sí, identidad3 'a')
$ qr IdPolimorfica3 -i qriollo> programa (Sí, 'a') de (Posta, Letra)
Condiciones (si...da...si no da...)
Podés usar expresiones condicionales, por medio de la sintaxis:
...
si <condición> da <resultado>
si no da <resultado>
Ponele:
el crear_saludador dado () da
ponele que la primera es Ref Sí en la que dado () da
escupir (
si desref primera da primera := No; "hola\n"
si no da "chau\n"
)
el programa es
saludar1 ();
saludar1 ();
saludar2 ();
saludar2 ();
saludar1 ();
saludar1 ();
saludar2 ();
saludar2 ()
donde
el saludar1 es crear_saludador ()
el saludar2 es crear_saludador ()
boludo
$ qr Condicionales
hola
chau
hola
chau
chau
chau
chau
chau
Declaración de operadores (chirimbolos)
Podés extender la sintaxis qriollo declarando nuevos operadores. La declaración con la que incorporás un chirimbolo a la sintaxis es:
Las asociatividades posibles son tres: zurdo para un chirimbolo binario asociativo a izquierda, diestro para un chirimbolo binario asociativo a derecha y prefijo para un chirimbolo unario prefijo. La precedencia es un numerito. Los operadores de mayor precedencia Qriollo los aplica sobre sus operandos antes que los operadores de menor precedencia. Un mismo chirimbolo no lo podés declarar más de una vez, así que no es posible sobrecargar un mismo chirimbolo para que sea unario y binario. Esto es diferente de lo que sí podés hacer en otros lenguajes gringos con chirimbolos como el menos "-".
El nombre de un chirimbolo puede ser cualquier tira de letras que estén entre las siguientes:
! # $ % & * + . / < = > ? @ : ^ | - ~ \ ; `
Si el nombre de un chirimbolo termina en dos puntos (":"), Qriollo lo entiende como que se trata del nombre de un constructor. La extensión de la sintaxis con nuevos chirimbolos es independiente del valor que les des. El valor de un chirimbolo se define igual que el de cualquier otro nombre.
Los chirimbolos los podés usar como funciones usando la sintaxis (el <chirimbolo>) o equivalentemente (la <chirimbolo>). Los paréntesis son obligatorios en ese caso.
chirimbolo zurdo 10 <@>
chirimbolo diestro 20 <$>
el <@> de coso en cosito en (coso, cosito)
dados x y da (x, y)
el <$> de coso en cosito en ([coso], [cosito])
dados x y da ([x], [y])
$ qr Chirimbolo1 -i qriollo> 1# <@> 2# <@> 3# <@> 4# (((1#, 2#), 3#), 4#) de (((Numerito, Numerito), Numerito), Numerito) qriollo> 1# <$> 2# <$> 3# <$> 4# ([1#], [([2#], [([3#], [4#])])]) de ([Numerito], [([Numerito], [([Numerito], [Numerito])])]) qriollo> 1# <$> 2# <@> 3# <$> 4# (([1#], [2#]), ([3#], [4#])) de (([Numerito], [Numerito]), ([Numerito], [Numerito]))
chirimbolo prefijo 10 \
el \ de coso en (coso, coso)
dado x da (x, x)
$ qr Chirimbolo2 -i qriollo> \ \ \ 1# (((1#, 1#), (1#, 1#)), ((1#, 1#), (1#, 1#))) de (((Numerito, Numerito), (Numerito, Numerito)), ((Numerito, Numerito), (Numerito, Numerito))) qriollo> fap (el \) [1#, 2#, 3#] [(1#, 1#), (2#, 2#), (3#, 3#)] de [(Numerito, Numerito)]
OJO. Tienen que terminar con ":" para que sean constructores.
chirimbolo zurdo 10 :+:
chirimbolo prefijo 20 ::
un Árbol de coso es
bien :: coso
bien :+: (Árbol de coso) (Árbol de coso)
OJO. Para mostrar árboles.
encarnar Mostrable para Árbol de coso
con Mostrable para coso
el mostrar
dado (:: x)
da "::" ++ mostrar x
dado (x :+: y)
da "(" ++ mostrar x ++ " :+: " ++ mostrar y ++ ")"
boludo
$ qr Chirimbolo3 -i qriollo> (::'a' :+: ::'b') :+: (::'c' :+: ::'d') ((::'a' :+: ::'b') :+: (::'c' :+: ::'d')) de Árbol de Letra
Clases de tipos (cualidad...para...boludo, encarnar...para...boludo)
Qriollo te permite que definás cualidades, lo que en lenguajes gringos conocés como "clases de tipos". Una cualidad es un conjunto de declaraciones de métodos con sus tipos:
el <nombre_método> de <tipo>
...
el <nombre_método> de <tipo>
boludo
El nombre de la cualidad lo tenés que empezar con mayúscula. El tipo de cada uno de los métodos tiene que depender de la variable de tipo para la que declarás la cualidad.
Ponele, la siguiente declaración indica que un tipo coso encarna la cualidad Comparable si lo acompañás de un método comparar, que le das dos cosos y te devuelve una posta:
el comparar de coso en coso en Posta
boludo
Una vez que declaraste una cualidad, le podés decir de qué manera diferentes tipos concretos la encarnan, que en lenguajes gringos lo llamás "instancia":
<definición_método>
...
<definición_método>
boludo
Con las definiciones de los métodos le das valor a cada uno de los métodos presentes en la declaración de la cualidad correspondiente. Las cualidades solamente las podés encarnar para tipos concretos que vienen encabezados por un constructor de tipos. Si el constructor de tipos tiene parámetros, tienen que ser variables de tipos que pueden variar libremente.
Ponele, con la siguiente declaración indicás que el tipo Numerito encarna la cualidad Comparable y definís el método comparar como el operador de comparación por menor o igual ("<="):
el comparar es (el <=)
boludo
Si el tipo concreto depende de ciertas variables de tipos, podés exigir que esas variables encarnen a su vez otras cualidades para poder definir cómo el tipo encarna una cierta cualidad:
para <constructor_de_tipos> <var> ... <var>
con (<nombre_cualidad> para <var>,
<nombre_cualidad> para <var>,
...
<nombre_cualidad> para <var>)
<definición_método>
...
<definición_método>
boludo
En el caso en el que la encarnación dependa de que una sola variable encarne cierta cualidad, los paréntesis no hace falta que los pongás.
Ponele, las listas de cosas son comparables si las cosas son comparables:
con Comparable para coso
el comparar
dadas [] _ da Sí
dadas (_ : _) [] da No
dadas (x : xs) (y : ys) da
comparar x y && comparar xs ys
boludo
Todos los tipos los podés aumentar con una declaración de las cualidades que tienen que encarnar las variables de tipos que lo conforman:
con (<nombre_cualidad> para <var>,
<nombre_cualidad> para <var>,
...
<nombre_cualidad> para <var>)
En caso de que querás especificar una cualidad sola, los paréntesis no los necesitás. Ponele:
dado x da escupir (mostrar x)
Ejemplo: tipos finitos
En este ejemplo definimos la cualidad Finito para los tipos que cuentan con una constante elementos, que comprende la lista de todos sus elementos. La cualidad Finito la encarnamos para las postas, para el tipo Quizá de coso cuando coso es finito, y para los pares de tipos finitos.
chirimbolo zurdo 650 ><
el >< de [coso] en [cosito] en [(coso, cosito)]
dados [] _ da []
dados (x : xs) ys da
aplistar (la que dado y da (x, y)) ys ++ xs >< ys
cualidad Finito para tipo
los elementos de [tipo]
boludo
encarnar Finito para Posta
los elementos son [Sí, No]
boludo
encarnar Finito para Quizá de coso
con Finito para coso
los elementos son fap Este elementos
boludo
encarnar Finito
para (coso, cosito)
con (Finito para coso, Finito para cosito)
los elementos son elementos >< elementos
boludo
$ qr CualidadFinito -i qriollo> elementos de [Posta] [Sí, No] de [Posta] qriollo> elementos de [(Posta, Posta)] [(Sí, Sí), (Sí, No), (No, Sí), (No, No)] de [(Posta, Posta)] qriollo> elementos de [(Posta, (Posta, Quizá de Posta))] [(Sí, (Sí, (Este Sí))), (Sí, (Sí, (Este No))), (Sí, (No, (Este Sí))), (Sí, (No, (Este No))), (No, (Sí, (Este Sí))), (No, (Sí, (Este No))), (No, (No, (Este Sí))), (No, (No, (Este No)))] de [(Posta, (Posta, Quizá de Posta))]
Ejemplo: monoides
En este ejemplo definimos la cualidad Monoide para los tipos que tienen un elemento neutro y una operación asociativa. La cualidad Monoide la encarnamos para cuatro variantes distintas de los numeritos, cada una de las cuales la acompañamos de una estructura de monoide diferente: NumeritoMax va a ser el monoide de los numeritos con el máximo y el 0, NumeritoMul va a ser el monoide de los numeritos con el producto y el 1, y así sucesivamente.
chirimbolo zurdo 10 <*>
cualidad Monoide para coso
el neutro de coso
el <*> de coso en coso en coso
boludo
OJO. Varios envoltorios para que los numeritos provean
OJO. distintas operaciones.
un NumeritoMax es bien NumeritoMax Numerito
un NumeritoMin es bien NumeritoMin Numerito
un NumeritoSum es bien NumeritoSum Numerito
un NumeritoMul es bien NumeritoMul Numerito
el desNumeritoMax dado (NumeritoMax n) da n
el desNumeritoMin dado (NumeritoMin n) da n
el desNumeritoSum dado (NumeritoSum n) da n
el desNumeritoMul dado (NumeritoMul n) da n
encarnar Monoide para NumeritoMax
el neutro es NumeritoMax 0#
el <*> dados (NumeritoMax x) (NumeritoMax y)
si x > y da NumeritoMax x
si no da NumeritoMax y
boludo
encarnar Monoide para NumeritoMin
el neutro es NumeritoMin 4294967295#
el <*> dados (NumeritoMin x) (NumeritoMin y)
si x < y da NumeritoMin x
si no da NumeritoMin y
boludo
encarnar Monoide para NumeritoSum
el neutro es NumeritoSum 0#
el <*> dados (NumeritoSum x) (NumeritoSum y)
da NumeritoSum (x + y)
boludo
encarnar Monoide para NumeritoMul
el neutro es NumeritoMul 1#
el <*> dados (NumeritoMul x) (NumeritoMul y)
da NumeritoMul (x * y)
boludo
el colapsar de [m] en m
con Monoide para m
es plegard (el <*>) neutro
el testNumeritos es
[
desNumeritoMax . colapsar . fap NumeritoMax $ lista,
desNumeritoMin . colapsar . fap NumeritoMin $ lista,
desNumeritoSum . colapsar . fap NumeritoSum $ lista,
desNumeritoMul . colapsar . fap NumeritoMul $ lista
]
donde
la lista es [2#, 1#, 3#, 4#, 5#]
boludo
$ qr CualidadMonoide -i qriollo> testNumeritos [5#, 1#, 15#, 120#] de [Numerito]
Ejemplo: iterables
chirimbolo zurdo 250 //
chirimbolo zurdo 250 ///
cualidad Iterable para bolsa
el // de (coso en cosito)
en bolsa de coso
en ()
boludo
encarnar Iterable para Quizá
el //
dados _ Nada da ()
dados f (Este x) da f x; ()
boludo
encarnar Iterable para PRIM.Lista
el //
dados _ [] da ()
dados f (x : xs) da f x; f // xs
boludo
el /// es (el //) . (el //)
OJO. Recolectores.
un Recolector de coso es
bien Recolector (coso en ()) (() en [coso])
el crear_recolector de () en Recolector de coso
dado () da Recolector a r
donde
el resultado es Ref []
la a dado x da resultado := desref resultado ++ [x]
el r dado () da desref resultado
boludo
el acumular de Recolector de coso en coso en ()
dado (Recolector a _) x da a x
el resultado de Recolector de coso en [coso]
dado (Recolector _ r) da r ()
el ejemplo de () en [Numerito]
dado () da
acumular r /// [[1#, 2#, 3#], [4#, 5#], [6#]];
resultado r
donde
el r es crear_recolector ()
boludo
$ qr CualidadIterable -i qriollo> escupirm // [[1#, 2#, 3#], [4#, 5#], [6#]] [1#, 2#, 3#] [4#, 5#] [6#] () de () qriollo> escupirm /// [[1#, 2#, 3#], [4#, 5#], [6#]] 1# 2# 3# 4# 5# 6# () qriollo> escupirm // Este [1#, 2#, 3#] [1#, 2#, 3#] () de () qriollo> escupirm /// Este [1#, 2#, 3#] 1# 2# 3# () de () qriollo> ejemplo () [1#, 2#, 3#, 4#, 5#, 6#] de [Numerito]
Continuaciones y call/cc (ccc, invocar)
En Qriollo las continuaciones son ciudadanos de primera clase. Qriollo tiene el tipo Cont de coso que representa una continuación que le das un valor de tipo coso, y tiene también dos operaciones primitivas:
invocar de (Cont de coso) en coso en cosito
La operación ccc quiere decir "Con la Continuación Corriente" y es análoga a la operación que en lenguajes gringos la conocés como call-with-current-continuation o call/cc. La operación ccc le das una función y te devuelve la continuación actual. La operación invocar espera una continuación y un coso, y te alimenta la continuación con el coso recibido. Fijate que invocar k x le pasa el control a la continuación k y no vuelve nunca, de manera que la expresión invocar k x la podés usar en cualquier contexto.
Ejemplo: excepciones
Acá definimos la función tratar, parecida al try de los lenguajes gringos, que le das un cuerpo y un manejador de excepciones. El manejador es como la cláusula catch de los lenguajes gringos. El cuerpo le das un parámetro que corresponde al throw de los gringos. Una diferencia con respecto a las formas gringas es que try, catch y throw suelen ser estructuras de control primitivas del lenguaje, mientras que en Qriollo son funciones comunes y corrientes que las podés definir por medio de ccc e invocar.
Como ejemplo, definimos una función que le das dos numeritos y te devuelve la suma, pero muestra un mensaje de error y devuelve 666# en caso de que cualquiera de los dos numeritos sea 0#. Huelga mencionar que esta no es la manera más sucinta ni qriolla de definir la función ejemplo, pero resulta ilustrativa para ejemplificar el uso de las excepciones aquí definidas.
una Excepción es
bien Excepción Texto
el tratar de ((Excepción en coso) en coso)
en (Excepción en coso)
en coso
dados cuerpo manejador
da ccc
(la que dada continuación da
cuerpo (la que dada excepción da
invocar continuación
(manejador excepción)))
el ejemplo de Numerito en Numerito en Numerito
dados a b da
tratar
OJO. Cuerpo.
(la que
dado fallar
da controlar "a" a + controlar "b" b
donde
el controlar dados nombre valor
si valor == 0#
da fallar (Excepción nombre)
si no da valor
boludo)
OJO. Manejador.
(la que dada (Excepción nombre)
da escupir
(nombre ++ " no puede ser cero\n");
666#)
$ qr ContExcepciones -i qriollo> ejemplo 1# 1# 2# de Numerito qriollo> ejemplo 0# 1# a no puede ser cero 666# de Numerito qriollo> ejemplo 1# 0# b no puede ser cero 666# de Numerito
Ejemplo: corrutinas
Acá definimos la función componer_en_paralelo que le das dos rutinas y te las compone para alternar la ejecución de la primera y la segunda, de manera colaborativa. A cada rutina le pasan como parámetro una función que permite pasarle el control a la otra.
Podríamos definir versiones más sofisticadas del ejemplo para que las rutinas puedan intercambiar resultados, o para componer en paralelo una cantidad arbitraria de rutinas. Pero para no complicar más todavía la legibilidad del ejemplo, resumir1 y resumir2 son copias textuales la una de la otra. Obviamente podríamos reescribir el ejemplo de manera más abstracta, usando una estructura de datos para guardar la información correspondiente a cada rutina, evitando así la repetición.
el componer_en_paralelo de ((() en ()) en ())
en ((() en ()) en ())
en ()
dadas rutina1 rutina2 da resumir1 ()
donde
el siguiente1
de Ref de (Quizá de (Cont de ()))
es Ref Nada
el resumir1 de () en ()
dado () da
mirar desref siguiente1
si Nada da
rutina1 (la que dado () da
ccc (la que dada continuación da
siguiente1 := Este continuación;
resumir2 ()))
si Este continuación da
invocar continuación ()
boludo
el siguiente2
de Ref de (Quizá de (Cont de ()))
es Ref Nada
el resumir2 de () en ()
dado () da
mirar desref siguiente2
si Nada da
rutina2 (la que dado () da
ccc (la que dada continuación da
siguiente2 := Este continuación;
resumir1 ()))
si Este continuación da
invocar continuación ()
boludo
boludo
el ejemplo dado () da
componer_en_paralelo (rutina "uno\n") (rutina "dos\n")
donde
la rutina dados texto pasar_el_control da loop ()
donde
el loop dado () da
escupir texto;
pasar_el_control ();
loop ()
boludo
boludo
$ qr ContCorrutinas -i qriollo> ejemplo () uno dos uno dos uno dos uno dos uno dos ...
Ejemplo: backtracking
Acá definimos la función elegir que implementa el operador amb de McCarthy. La función elegir le das una lista de elementos y elige el primero de ellos. La función fallar recibe la 0-upla y retrocede (haciendo backtracking) hasta el último punto en el que hiciste una elección, eligiendo el elemento siguiente al que fue elegido la vez anterior. De manera más ideal, elegir representa la elección no determinística de un valor que no provoca una falla.
Ponele, la función tripla_pitagórica le das un numerito n y te devuelve tres numeritos x, y, z en el rango 1...n que cumplen x2 + y2 = z2. Para eso, agarra cada uno de los valores de manera no determinística y provoca una falla cuando la propiedad buscada no se cumplió.
las alternativas de Ref de [() en coso] son Ref []
el fallar de () en coso
dado () da
mirar desref alternativas
si [] da escupir "NO.\n";
espichar 1#
si (alt : alts) da
alternativas := alts;
alt ()
boludo
el elegir de [coso] en coso
dadas posibilidades da
ccc (la que dada continuación da
alternativas :=
aplistar (la que dadas posibilidad ()
da invocar continuación posibilidad)
posibilidades ++
desref alternativas;
fallar ()
)
el tripla_pitagórica de Numerito en [Numerito]
dado n da
ponele que
el x es elegir (1#...n)
el y es elegir (1#...n)
el z es elegir (1#...n)
en
si x * x + y * y == z * z
da [x, y, z]
si no da fallar ()
$ qr ContRetroceso -i qriollo> tripla_pitagórica 10# [3#, 4#, 5#] de [Numerito]
Mónadas y notación che (che...es...)
Qriollo define la cualidad Mónada, similar a la que podés encontrar en otros lenguajes gringos:
la fija de coso en bolsa de coso
el >>= de bolsa de coso
en (coso en bolsa de cosito)
en bolsa de cosito
boludo
Además, Qriollo tiene la notación che que te sirve para definir una expresión monádica:
che el <nombre2> es <valor2>
...
che el <nombreN> es <valorN>
en <resultado>
que equivale a esta otra expresión:
<valor2> >>= (la que dado <nombre2> da
...
<valorN> >>= (la que dado <nombreN> da
<resultado>)...))
Ponele, usando la mónada lista, podés definir funciones que simulan el comportamiento no determinístico del retroceso pero de manera puramente funcional:
las triplas_pitagóricas de Numerito en [[Numerito]]
dado n
da che el x es 1...n
che el y es 1...n
che el z es 1...n
en si x * x + y * y == z * z
da fija [x, y, z]
si no da []
la tripla_pitagórica de Numerito en [Numerito]
dado n da
mirar triplas_pitagóricas n
si (resultado : _) da resultado
si no da escupir "NO.";
espichar 1
boludo
$ qr MonadaLista -i qriollo> tripla_pitagórica 10# [3#, 4#, 5#] de [Numerito]
Interfaz de funciones gringas (gringo ... y GRINGO...)
La interfaz de funciones gringas te deja extender el Qriollo con funcionalidad primitiva choreada del lenguaje objeto. El mecanismo general para las funciones gringas consiste en dos declaraciones. Primero tenés la declaración de funciones:
<nombre_qriollo> de <tipo>
Los lenguajes gringos que soporta Qriollo por ahora son C, Py y Jvm, que representan cada cual invocaciones gringas en C, en Python, y en la máquina virtual de Java.
Usando esta declaración definís un identificador <nombre_qriollo> que tiene el tipo <tipo>, que puede ser polimórfico, y que lo vas a tener disponible cuando el programa en Qriollo lo compilés al lenguaje objeto especificado.
Además de declarar funciones gringas, podés introducir otras declaraciones, que cumplen el papel de pragmas específicos para cada uno de los backends:
Tipos válidos en declaraciones de funciones gringas
Las funciones gringas las podés declarar solamente con tipos que respeten algunos formatos particulares. Por empezar, las funciones gringas no pueden recibir funciones como parámetros, es decir que su tipo está limitado a ser algo como (coso en cosito en ... en cosititito en resultado). Cada uno de los parámetros y el resultado pueden ser de alguno de los tipos que siguen:
- Numerito
- Posta
- Letra
- Texto
- Pendorcho "<descripción>"
Los cuatro primeros tipos representan datos de tipos parecidos al correspondiente tipo en Qriollo, con algunas salvedades y caprichos. Ponele, en el backend para C, que lo asume siempre sobre una arquitectura de 64 bits, un Numerito es un unsinged long long int, una Posta es un unsigned long long int que puede tomar los valores 0 o 1, una Letra es un char, y un Texto es un char *. En el backend para Python, un Numerito es un int, una Posta es un bool, una Letra y un Texto son de tipo unichr.
El tipo Pendorcho "<descripción>" sirve para tener un tipo opaco en el lenguaje objeto, y la descripción es una anotación que le puede servir al compilador de Qriollo para saber cuál es su tipo en el lenguaje objeto. La notación Pendorcho abrevia el tipo Pendorcho "". En el backend de Python, un Pendorcho representa un objeto cualquiera, y la anotación la ignora. En el backend de C, un Pendorcho "<tipo_gringo>" representa un puntero con el tipo gringo indicado; por ejemplo Pendorcho "FILE *" puede servir para obtener el resultado de una invocación a fopen.
El backend para Python te deja también declarar funciones que retornan valores del tipo Falible de coso, donde <tipo> es alguno de los tipos anteriores. No se permite anidar el tipo Falible en declaraciones de funciones gringas. El tipo Falible de coso te sirve para representar un cómputo que puede elevar excepciones: si no levantás ninguna excepción, el valor retornado es Joya valor, mientras que si levantás una excepción, el valor que te devuelve es Cagó mensaje, donde mensaje es un mensaje de error acorde.
La declaración de funciones gringas en C y en Python usa un mecanismo sencillo: el texto de la invocación gringa es un cacho de programa en el que todas las apariciones de ${1}, ${2}, ${3}, etcétera, se macroexpanden al parámetro correspondiente recibido por la función.
Interfaz gringa para C
Ponele, acá se ve cómo podés usar en Qriollo algunas funciones de la biblioteca estándar de C para implementar una funcionalidad muy básica de lectura y escritura de archivos. Este programa qriollo solamente funciona si lo compilás usando el backend de C, con la opción --c del compilador.
gringo C "fopen(${1}, \"r\")"
abrir_para_lectura de Texto en Pendorcho "FILE *"
gringo C "fgetc(${1})"
leer_una_letra de Pendorcho "FILE *" en Letra
gringo C "fclose(${1})"
cerrar_archivo de Pendorcho "FILE *" en ()
el programa es
escupirm (leer_una_letra archivo);
cerrar_archivo archivo
donde
el archivo es abrir_para_lectura "a.txt"
boludo
$ qr GringoC1 --c GringoC1.c && gcc -o GringoC1 GringoC1.c $ echo a > a.txt $ ./GringoC1 'a'
En el backend para C las declaraciones GRINGO C "<pragma>" permiten meter directamente código en C que quieras que forme parte del programa final.
Si querés podés usar la funcionalidad gringa para hacer cosas más jáquers. Ponele, podés definir un paquete para trabajar con vectores de números unboxed:
GRINGO C "typedef unsigned long long int *QVector;"
gringo C "malloc(sizeof(unsigned long long int) * ${1})"
crear de Numerito en Pendorcho "QVector"
gringo C "${1}[${2}] = ${3}"
meter de Pendorcho "QVector"
en Numerito
en Numerito
en ()
gringo C "${1}[${2}]"
obtener de Pendorcho "QVector"
en Numerito
en Numerito
el programa es
aplistar (la que dado i da meter vector i i)
(0#...tamaño);
aplistar (la que dado i da escupirm (obtener vector i))
(0#...tamaño)
donde
el tamaño es 10#
el vector es crear tamaño
boludo
$ qr GringoC2 --c -o GringoC2.c && gcc -o GringoC2 GringoC2.c $ ./GringoC2 0# 1# 2# 3# 4# 5# 6# 7# 8# 9#
Interfaz gringa para Python
Las ideas de la interfaz gringa para Python son muy parecidas a las de la interfaz gringa para C. Acá vienen los mismos ejemplos de arriba pero adaptados para Python:
gringo Py "open(${1}, 'r')"
abrir_para_lectura de Texto en Pendorcho
gringo Py "${1}.read(1)"
leer_una_letra de Pendorcho en Letra
gringo Py "${1}.close()"
cerrar_archivo de Pendorcho en ()
el programa es
escupirm (leer_una_letra archivo);
cerrar_archivo archivo
donde
el archivo es abrir_para_lectura "a.txt"
boludo
$ qr GringoPy1 --py -o GringoPy1.py $ echo a > a.txt $ python2 GringoPy1.py 'a'
gringo Py "[0 for i in range(${1})]"
crear de Numerito en Pendorcho
gringo Py "${1}[${2}] = ${3}"
meter de Pendorcho
en Numerito
en Numerito
en ()
gringo Py "${1}[${2}]"
obtener de Pendorcho
en Numerito
en Numerito
el programa es
aplistar (la que dado i da meter vector i i)
(0#...tamaño);
aplistar (la que dado i da escupirm (obtener vector i))
(0#...tamaño)
donde
el tamaño es 10#
el vector es crear tamaño
boludo
$ qr GringoPy2 --py | python2 -
0#
1#
2#
3#
4#
5#
6#
7#
8#
9#
Compilación condicional (pragma SI...BOLUDO)
Los programas en Qriollo permiten que les metás condiciones para determinar en tiempo de compilación qué cachos de programa incluir y cuáles no. La sintaxis para hacer esto:
<cacho_de_programa>
BOLUDO
Qriollo incluye el cacho de programa indicado si y solamente si se cumplen todas las condiciones que ponés entre paréntesis. Las condiciones pueden ser +Funcionalidad para pedir que una funcionalidad esté activada, o -Funcionalidad para pedir que una funcionalidad esté desactivada. Las funcionalidades son nombres empezados por mayúscula arbitrarios, que pueden representar lo que se te ocurra. Al momento de invocar el intérprete/compilador de Qriollo podés pasarle la opción --con Funcionalidad para indicarle que la funcionalidad está presente y --sin Funcionalidad para indicarle que la funcionalidad está ausente.
Ponele:
SI (+Temprano)
el saludo es "buen día"
BOLUDO
SI (-Temprano)
el saludo es "buenas noches"
BOLUDO
el programa es escupir saludo; escupir "\n"
$ qr CompilacionCondicional --con Temprano buen día $ qr CompilacionCondicional --sin Temprano buenas noches $ qr CompilacionCondicional --con Temprano --py -o temprano.py $ python2 temprano.py buen día $ qr CompilacionCondicional --sin Temprano --py -o tarde.py $ python2 tarde.py buenas noches
Las cuatro funcionalidades siguientes se prenden o apagan automáticamente dependiendo de la situación bajo la que estés compilando el programa:
- Compilado: se prende si estás compilando, en contraposición a interpretando.
- C: se prende si estás compilando a C.
- Py: se prende si estás compilando a Python.
- Jvm: se prende si estás compilando a la JVM.
La combinación de estas características permite que podás escribir programas que funcionan en varias plataformas. Ponele, se pueden combinar los ejemplos de manejo de archivos que se presentaron antes para que el programa funcione tanto si lo compilás a C como si lo compilás a Python:
SI (+C)
gringo C "fopen(${1}, \"r\")"
abrir_para_lectura de Texto en Pendorcho "FILE *"
gringo C "fgetc(${1})"
leer_una_letra de Pendorcho "FILE *" en Letra
gringo C "fclose(${1})"
cerrar_archivo de Pendorcho "FILE *" en ()
BOLUDO
SI (+Py)
gringo Py "open(${1}, 'r')"
abrir_para_lectura de Texto en Pendorcho
gringo Py "${1}.read(1)"
leer_una_letra de Pendorcho en Letra
gringo Py "${1}.close()"
cerrar_archivo de Pendorcho en ()
BOLUDO
el programa es
escupirm (leer_una_letra archivo);
cerrar_archivo archivo
donde
el archivo es abrir_para_lectura "a.txt"
boludo
$ echo a > a.txt $ qr GringoCondicional --py | python2 - 'a' $ qr GringoCondicional --c -o a.c && gcc -o a a.c && ./a 'a'
Módulos (entregar..., enchufar...)
Qriollo permite que definás e importés módulos. En Qriollo el nombre de un módulo lo tenés que empezar con mayúscula y puede incluir puntos que lo hacen relativo a la ruta donde se buscan los módulos. Ponele:
Nombre del módulo | Nombre del archivo |
---|---|
Fulano | Fulano.q |
Lenguaje.Qriollo.Parser | Lenguaje/Qriollo/Parser.q |
De manera predeterminada, Qriollo busca los módulos en el directorio actual (".") y nada más. Si a la variable de entorno RUTA_QRIOLLO le das un valor como directorio1:directorio2:...:directorioN, le podés indicar en qué directorios querés que busque los módulos. También podés controlarlo desde la línea de comandos, pasándole la opción --ruta directorio1:directorio2:...:directorioN. La búsqueda le da prioridad siempre al directorio que viene escrito primero.
La manera más precaria de usar módulos es la siguiente: si no le decís nada, un módulo exporta todos los nombres que define. Si querés que el módulo B use las definiciones del módulo A ponés enchufar A y listo. Ponele:
A.qel saludo es "hola\n"
enchufar A
el programa es escupir saludo
$ qr B
hola
Un módulo puede declarar exactamente la lista de todos los nombres que exporta, usando la cláusula:
Ponele:
A.qentregar (saludo_público)
el saludo_público es "hola\n"
el saludo_privado es "chau\n"
enchufar A
el programa es
escupir saludo_público
OJO. escupir saludo_privado no funciona
$ qr B
hola
Si un módulo B importa un módulo A puede acceder a todos los nombres privados de A si los califica con el nombre A. Un nombre calificado es un nombre que viene acompañado por el nombre del módulo y separado por un punto. Ponele:
A.qOJO. A no entrega ningún nombre
entregar ()
el saludo_privado es "hola\n"
enchufar A
el programa es
OJO. B puede usar los nombres privados de A
escupir A.saludo_privado
$ qr B
hola
Cuando un módulo importa a otro, se puede especificar cuáles de sus nombres importa, usando la sintaxis:
Ponele:
A.qentregar (saludo_relevante, saludo_irrelevante)
el saludo_relevante es "hola\n"
el saludo_irrelevante es "chau\n"
enchufar A (saludo_relevante)
el programa es
escupir saludo_relevante
OJO. escupir saludo_irrelevante no funciona
$ qr B
hola
El módulo que importa un nombre también puede decidir de qué manera quiere renombrarlo para su uso local, usando la sintaxis:
...,
<nombre_ajeno> como <nombre_local>,
...
)
Ponele:
A.qentregar (saludo)
el saludo es "hola\n"
enchufar A (saludo como bienvenida)
el programa es escupir bienvenida
$ qr B
hola
Caso de estudio: intérprete de Lizp
A continuación presentamos un intérprete de Lizp de juguete. Técnicamente se trata de un Mock Lisp, porque la estructura de datos usada para representar un programa en Lizp no es una estructura de datos de Lizp. Provee un Read-Eval-Print Loop mínimo, y lo podés compilar tanto a C como a Python.
SI (+C)
GRINGO C "#include <readline/readline.h>"
gringo C "readline(${1})"
_leer_línea de Texto en Pendorcho "char *"
gringo C "${1}"
_convertir de Pendorcho "char *" en Texto
gringo C "free(${1})"
_liberar de Pendorcho "char *" en ()
el leer_línea de Texto en Texto dado prompt da
ponele que
el pendorcho es _leer_línea prompt
el texto es _convertir pendorcho
en _liberar pendorcho; texto
BOLUDO
SI (+Py)
gringo Py "raw_input(${1})"
leer_línea de Texto en Texto
BOLUDO
OJO. Lee líneas hasta que la concatenación
OJO. de todas ellas tenga los paréntesis
OJO. balanceados.
el leer_sexpr de () en Texto
dado () da ciclo "" 0#
donde
el ciclo dados acc p da
ponele que
la línea es leer_línea (prompt p)
el p2 es paréntesis_pendientes p línea
en
si p2 > 0# da ciclo (acc ++ " " ++ línea) p2
si no da acc ++ " " ++ línea
el prompt dado p
si p > 0# da "..... "
si no da "Lizp> "
los paréntesis_pendientes de Numerito
en Texto
en Numerito
dados p (Texto xs) da rec p xs
donde
el rec de Numerito en [Letra] en Numerito
dados p [] da p
dados p ('(' : xs) da rec (p + 1#) xs
dados p (')' : xs) da rec (p - 1#) xs
dados p (_ : xs) da rec p xs
boludo
boludo
una Sexpr es
bien Átomo Texto
bien Cons (Ref de Sexpr) (Ref de Sexpr)
OJO. Read.
el entre de Letra en Letra en Letra en Posta
dados x y z da
letra_a_numerito x <= letra_a_numerito y &&
letra_a_numerito y <= letra_a_numerito z
el constituyente de Letra en Posta
dada letra da
entre 'a' letra 'z' ||
entre 'A' letra 'Z' ||
entre '0' letra '9' ||
entre '-' letra '-' ||
entre '?' letra '?' ||
entre '!' letra '!'
el leer_átomo de [Letra] en ([Letra], [Letra])
dada [] da ([], [])
dada (x : xs)
si constituyente x
da mirar leer_átomo xs
si (átomo, cola) da (x : átomo, cola)
boludo
si no da ([], (x : xs))
el leer_lista de [Letra] en ([Sexpr], [Letra])
dada [] da escupir "final temprano de la entrada\n";
espichar 1
dada (' ' : xs) da leer_lista xs
dada ('\t' : xs) da leer_lista xs
dada ('\r' : xs) da leer_lista xs
dada ('\n' : xs) da leer_lista xs
dada (')' : xs) da ([], xs)
dada (x : xs) da
mirar leer1 (x : xs)
si (sexpr, cola) da
mirar leer_lista cola
si (lista, cola2) da
(sexpr : lista, cola2)
boludo
boludo
el leer1 de [Letra] en (Sexpr, [Letra])
dada [] da escupir "final temprano de la entrada\n";
espichar 1
dada (' ' : xs) da leer1 xs
dada ('\t' : xs) da leer1 xs
dada ('\r' : xs) da leer1 xs
dada ('\n' : xs) da leer1 xs
dada ('(' : xs) da
mirar leer_lista xs
si (lista, cola) da
(plegard cons nil lista, cola)
boludo
dada (x : xs)
si constituyente x da
mirar leer_átomo (x : xs)
si (átomo, cola) da (Átomo $ Texto átomo, cola)
boludo
si no da
escupir "entrada deforme\n";
espichar 1
el leer de Texto en Sexpr
dado (Texto xs) da
mirar leer1 xs
si (sexpr, _) da sexpr
boludo
OJO. Eval.
el letras_iguales de [Letra] en [Letra] en Posta
dados [] [] da Sí
dados [] (_ : _) da No
dados (_ : _) [] da No
dados (x : xs) (y : ys) da
letra_a_numerito x == letra_a_numerito y &&
letras_iguales xs ys
el texto_igual de Texto en Texto en Posta
dados (Texto a) (Texto b) da letras_iguales a b
el buscar de Sexpr en Sexpr en Sexpr
dados (Cons (Ref (Cons (Ref k) (Ref v))) (Ref e)) a
si eq k a da v
si no da buscar e a
dados _ a da a
el reemplazar de Sexpr en Sexpr en Sexpr en ()
dados (Cons (Ref (Cons (Ref k) ref_v)) (Ref e)) a v
si eq k a da ref_v := v
si no da reemplazar e a v
dados _ _ _ da
escupir "variable indefinida\n";
espichar 1
el t de Sexpr es Átomo "t"
el nil de Sexpr es Átomo "nil"
el cons de Sexpr en Sexpr en Sexpr
dados x y da Cons (Ref x) (Ref y)
el atom de Sexpr en Sexpr
dado (Átomo _) da t
dado _ da nil
el car de Sexpr en Sexpr
dado (Átomo a) da nil
dado (Cons (Ref x) _) da x
el cdr de Sexpr en Sexpr
dado (Átomo a) da nil
dado (Cons _ (Ref x)) da x
el cadr de Sexpr en Sexpr
es car . cdr
el cddr de Sexpr en Sexpr
es cdr . cdr
el eq de Sexpr en Sexpr en Posta
dados (Átomo a) (Átomo b)
da texto_igual a b
dados _ _ da No
el evaluar de Sexpr en Sexpr en Sexpr
dados entorno (Átomo a)
da buscar entorno (Átomo a)
dados entorno (Cons (Ref x) (Ref xs))
si eq x (Átomo "tal-cual") da
car xs
si eq x (Átomo "bloque") da
evaluar_bloque entorno xs
si eq x (Átomo "fun") da
cons (Átomo "#procedimiento")
(cons entorno xs)
si eq x (Átomo "si") da
mirar eq (evaluar entorno (car xs)) nil
si Sí da evaluar_bloque entorno (cddr xs)
si No da evaluar entorno (cadr xs)
boludo
si eq x (Átomo "guardar!") da
ponele que
el valor es evaluar entorno (cadr xs)
en reemplazar entorno (car xs) valor; valor
si no da
aplicar (evaluar entorno x)
(evaluar_lista entorno (sexpr_lista xs))
el extender de [Sexpr] en [Sexpr] en Sexpr en Sexpr
dados [] _ entorno da entorno
dados (p : ps) [] entorno da entorno
dados (p : ps) (a : as) entorno da
cons (cons p a) (extender ps as entorno)
el aplicar de Sexpr en [Sexpr] en Sexpr
dados (Cons
(Ref (Átomo "#procedimiento"))
(Ref (Cons (Ref entorno)
(Ref (Cons (Ref parámetros)
(Ref cuerpo))))))
argumentos
da
evaluar_bloque
(extender
(sexpr_lista parámetros)
argumentos
entorno)
cuerpo
dados (Átomo "#prim:atomo?") [x] da atom x
dados (Átomo "#prim:cons") [x, y] da cons x y
dados (Átomo "#prim:car") [x] da car x
dados (Átomo "#prim:cdr") [x] da cdr x
dados (Átomo "#prim:igual?") [x, y]
si eq x y da t
si no da nil
dados _ _ da nil
el evaluar_bloque de Sexpr en Sexpr en Sexpr
dados entorno (Átomo a) da Átomo a
dados entorno
(Cons
(Ref (Cons
(Ref (Átomo "def"))
(Ref (Cons (Ref nombre)
(Ref
(Cons (Ref valor)
(Ref (Átomo "nil"))))))))
(Ref xs)) da
(mirar entorno
si Cons a r da
r := cons (cons nombre (evaluar entorno valor))
(desref r)
si no da ()
boludo);
evaluar_bloque entorno xs
dados entorno (Cons (Ref a) (Ref (Átomo _))) da
evaluar entorno a
dados entorno (Cons (Ref a) (Ref xs)) da
evaluar entorno a;
evaluar_bloque entorno xs
el evaluar_lista de Sexpr en [Sexpr] en [Sexpr]
dados entorno [] da []
dados entorno (sexpr : sexprs) da
evaluar entorno sexpr : evaluar_lista entorno sexprs
el sexpr_lista_1 de Sexpr en ([Sexpr], Quizá de Texto)
dados sexpr da rec [] sexpr
donde
el rec
dados xs (Átomo "nil") da (xs, Nada)
dados xs (Átomo a) da (xs, Este a)
dados xs (Cons (Ref x) (Ref ys))
da rec (xs ++ [x]) ys
boludo
el sexpr_lista de Sexpr en [Sexpr]
dados sexpr da
mirar sexpr_lista_1 sexpr
si (xs, _) da xs
boludo
OJO. Print.
el juntar
dados sep [] da ""
dados sep [x] da x
dados sep (x : y : ys)
da x ++ sep ++ juntar sep (y : ys)
encarnar Mostrable para Sexpr
el mostrar
dado (Átomo x) da x
dado sexpr da
mirar sexpr_lista_1 sexpr
si (xs, Nada) da
"(" ++ juntar " " (fap mostrar xs) ++ ")"
si (xs, Este a) da
"(" ++ juntar " " (fap mostrar xs)
++ " . " ++ a ++ ")"
boludo
boludo
OJO. Read + eval.
el entorno_global es
cons (cons nil nil) $
extender [Átomo "atomo?"] [Átomo "#prim:atomo?"] $
extender [Átomo "cons"] [Átomo "#prim:cons"] $
extender [Átomo "car"] [Átomo "#prim:car"] $
extender [Átomo "cdr"] [Átomo "#prim:cdr"] $
extender [Átomo "igual?"] [Átomo "#prim:igual?"] $
nil
el read_eval_print_loop dado () da
ponele que
la expresión es leer $ leer_sexpr ()
en
si eq expresión (Átomo "salir") da ()
si no da
escupir . mostrar . evaluar entorno_global $
expresión;
escupir "\n";
read_eval_print_loop ()
el programa es
read_eval_print_loop ()
Ejecución del intérprete de Lizp en C
$ qr Lizp.q --c -o Lizp.c && gcc -o Lizp Lizp.c -lreadline $ ./Lizp Lizp> (tal-cual (a b c)) (a b c) Lizp> (cons a (cons b (cons c nil))) (a b c) Lizp> (((fun (x) (fun (y) x)) (tal-cual a)) (tal-cual b)) a Lizp> (bloque ..... (def a 1) ..... (guardar! a 2) ..... a) 2 Lizp> salir
(def cero (fun () (tal-cual O)))
(def suc (fun (n) (cons (tal-cual S) n)))
(def pred cdr)
(def sumar
(fun (n m)
(si (igual? n (cero))
m
(suc (sumar (pred n) m)))))
(def multiplicar
(fun (n m)
(si (igual? n (cero))
(cero)
(sumar m (multiplicar (pred n) m)))))
(def factorial
(fun (n)
(si (igual? n (cero))
(suc (cero))
(multiplicar n (factorial (pred n))))))
(factorial
(suc (suc (suc (suc (cero)))))))
salir
$ ./Lizp < factorial.lizp Lizp> (bloque ..... (def cero (fun () (tal-cual O))) ..... (def suc (fun (n) (cons (tal-cual S) n))) ..... (def pred cdr) ..... ..... (def sumar ..... (fun (n m) ..... (si (igual? n (cero)) ..... m ..... (suc (sumar (pred n) m))))) ..... ..... (def multiplicar ..... (fun (n m) ..... (si (igual? n (cero)) ..... (cero) ..... (sumar m (multiplicar (pred n) m))))) ..... ..... (def factorial ..... (fun (n) ..... (si (igual? n (cero)) ..... (suc (cero)) ..... (multiplicar n (factorial (pred n)))))) ..... ..... (factorial ..... (suc (suc (suc (suc (cero))))))) (S S S S S S S S S S S S S S S S S S S S S S S S . O) Lizp> salir
Ejecución del intérprete de Lizp en Python
$ qr Lizp.q --py -o Lizp.py $ python2 Lizp.py $ qr Lizp Lizp> (bloque ..... (def mapcar ..... (fun (f x) ..... (si (igual? x nil) ..... nil ..... (cons (f (car x)) (mapcar f (cdr x)))))) ..... (mapcar (fun (x) (cons x x)) (tal-cual (1 2 3)))) ((1 . 1) (2 . 2) (3 . 3)) Lizp> salir
Convenciones léxicas
Los identificadores aceptados son [a-zA-Z_][a-zA-Z0-9_]*. El identificador "_" (guión bajo) lo tiene reservado para los comodines. En realidad no solo te acepta caracteres ASCII en [a-zA-Z] sino caracteres Unicode alfabéticos. La entrada te la supone codificada en UTF-8. Igual que en Haskell, los identificadores comenzados por minúscula te los reserva para variables de datos y de tipos, mientras que los identificadores comenzados por mayúscula te los reserva para constructores de datos y de tipos.
Los espacios en blanco te los ignora salvo como delimitadores de símbolos léxicos. La identación es irrelevante.
Las palabras actualmente reservadas por Qriollo son:
Además, el módulo PRIM y algunos tipos de datos básicos (como Ref y Pendorcho) te los trata de manera especial.
Las constantes de tipo Numerito las tenés que escribir como [0-9]+#, donde el cardinal al final es obligatorio. Las constantes numéricas sin cardinal (ponele 42) te las convierte en una invocación a la función levantar_dígitos sobre una lista de numeritos que representa la escritura en base 2**32 de la constante numérica original, con los dígitos ordenados de menos significativo a más significativo. La función levantar_dígitos corresponde a la cualidad Digital, que provee funciones para convertir un tipo ida y vuelta a una lista de numeritos:
digitalizar de coso en [Numerito]
levantar_dígitos de [Numerito] en coso
boludo
Las constantes de tipo Letra (caracteres) las tenés que encerrar entre comillas simples (') y las constantes de tipo Texto (strings) entre comillas dobles. Las cadenas de texto no pueden incluir linefeeds. Se admiten secuencias de escape con su significado usual:
\\ \" \' \a \b \f \n \r \t \v
Los comentarios comienzan con OJO. (el punto es obligatorio) y terminan con un linefeed.
Recomendaciones de estilo
La recomendación de estilo más importante es:
Usá el lenguaje como te pinte.
Más allá de esa máxima, las recomendaciones generales son las mismas que las de los lenguajes gringos: elegí nombres declarativos, ponele sangría al programa para reflejar el nivel de anidamiento y demás.
Por tratarse de un lenguaje funcional, es mejor que limités el uso de sus características impuras: entrada/salida con efectos colaterales, referencias y manejo de continuaciones.
Una recomendación de estilo propia de Qriollo es tratar de declinar los artículos (el, la, los, las), cópulas (es, son), y, en general, todas las palabras clave que admitan flexión, de acuerdo con el contexto, para que el programa resulte eufónico.
Bichos y limitaciones conocidas
- No hay ninguna manera posible de hacer compilación separada. Esta es la mayor limitación de Qriollo en la actualidad. Resolver esta limitación requeriría una reestructuración fundamental del compilador, ya que el proceso de compilación asume de manera permanente que toda la información del programa la puede encontrar disponible en una única expresión.
- Como consecuencia de la limitación anterior, la interacción con el toplevel es extremadamente lenta, ya que la evaluación de una expresión requiere recompilar la expresión y todos los módulos de los que depende (p.ej. el Chamuyo).
- Por el momento Qriollo no cuenta con ninguna biblioteca más que el Chamuyo, que define apenas algunas funciones básicas. Para seguir con este largo camino de reinventar la rueda haría falta:
- Colecciones (listas, vectores, conjuntos, diccionarios, texto Unicode y texto binario).
- Aritmética (números enteros grandes, números de punto flotante, racionales, complejos).
- Estructuras de control adicionales (iteradores, excepciones, condiciones).
- Entrada/salida (manejo de archivos, sockets, protocolos de internet, interfaz con bases de datos).
- Cualidades (funtores, mónadas, tipos de datos plegables).
- Concurrencia (corrutinas, procesos con colas de mensajes).
- Utilidades varias (expresiones regulares, fecha/hora, interfaz con el sistema operativo, imágenes, audio, criptografía, números pseudoaleatorios, compresión, etcétera).
- Herramienta de documentación para Qriollo.
- Generador de parsers.
- Todo lo demás.
- El compilador de Qriollo no hace inferencia de kinds. Esto es incorrecto y permite compilar expresiones con errores de tipos, o inclusive usar tipos que nunca están definidos. Se podría agregar inferencia de kinds con algo de esfuerzo. Por el momento este cambio tiene baja prioridad, ya que representa un bajo beneficio por sobre su costo de implementación en un lenguaje concebido en joda.
- En un conjunto de declaraciones locales que están todas al mismo nivel (letrec), una función no se porta de manera polimórfica salvo que le indiqués su tipo. Ponele, el programa siguiente da un error de tipos porque la identidad no es polimórfica: la identidad dado x da xPero el programa siguiente funciona, porque la identidad se declara explícitamente como polimórfica:
el programa es (identidad 1#, identidad 'a')la identidad de coso en cosoEl programa siguiente también funciona, porque aunque la declaración de la identidad no incluye explícitamente su tipo, su definición es interna:
dado x da x
el programa es (identidad 1#, identidad 'a')el programa es (identidad 1#, identidad 'a')
donde
la identidad dado x da x
boludo - El backend de la JVM es inestable y probablemente desaparezca en el futuro. Quizás una mejor alternativa sería que el compilador genere código fuente Java.
- El backend de C tiene limitaciones de memoria. No podés reservar objetos de más de Qr_BLOCK_CAPACITY palabras. Esta limitación no debería afectar los programas normales, ya que la mayor parte de los objetos aparentemente "grandes" para vos en realidad te los construye a partir de objetos chiquitos (ponele, una cadena de texto muy larga la representa como una lista enlazada de pequeños nodos). El garbage collector es precario y prioriza su brevedad y simplicidad de implementación.
Licencia
Qriollo se distribuye bajo la licencia GNU General Public License v3.0.
Desgraciadamente el mundo no está listo para la Licencia Qriolla:
"Este programa es una MIERDA y se distribuye con la esperanza de que lo uses para LO QUE SE TE CANTE EL ORTO siempre que NO ROMPAS LAS PELOTAS."
Reconocimientos
Por el diseño del logo: pewter .
Por la creación y mantenimiento del paquete AUR: godel .
Por sugerencias sobre el diseño del lenguaje: LeandroL , flebron , fmartin , notfancy .
Por la ayuda con el algoritmo de inferencia de tipos para cualidades: tmlm .