Qriollo
El lenguaje más boludo del mundo

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

HolaMundo.q
enchufar Chamuyo

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)

Primos.q
enchufar Chamuyo

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

AB.q
enchufar Chamuyo

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

ClasesDeTipos.q
enchufar Chamuyo

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

Historia

Qriollo sigue una larga tradición de lenguajes rioplatenses:

CHORONGOL '67 – Juan Carlos Turing

Visual Gómez 1.0 – Simon Peyton Gómez

salchicha++ – el Yoni Backus y la turra Lovelace

Bajarlo

Prerrequisitos

Se requiere ghc (Glasgow Haskell Compiler) para compilar el compilador de Qriollo.

Instalación

  1. Manera 1: desde el AUR
    1. Instalar el paquete AUR de Qriollo:
      $ yaourt -S qriollo
    2. 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
    3. Ponele:
      $ echo enchufar Chamuyo el programa es escupir \"Hola mundo\\n\" > HolaMundo.q
      $ qr HolaMundo.q
      hola
  2. Manera 2: desde github
    1. Clonar el repositorio github:
      $ git clone https://github.com/qriollo/qriollo.git
      $ cd qriollo/
    2. Compilar:
      $ make
    3. Probarlo:
      $ ./qr HolaMundo.q
      Hola mundo
  3. Manera 3: a manopla
    1. Descargar los fuentes de Qriollo:
      $ tar xzf Qriollo-0.91.tar.gz
      $ cd Qriollo-0.91/
    2. Compilar:
      $ make
    3. 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

Manual

Índice

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:

DeclaracionValor.q
enchufar Chamuyo

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:

NumerosHasta.q
enchufar Chamuyo

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.

Doble.q
enchufar Chamuyo

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:

un <tipo> es
  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:

un <tipo> de <parámetros> es
  bien <constructor> <tipos_parámetros>
  ...
  bien <constructor> <tipos_parámetros>

Un ejemplo de un tipo algebraico que no tiene parámetros es:

DefExpresion.q
enchufar Chamuyo

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:

DefArbol.q
enchufar Chamuyo

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:

la <función>
  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:

SumaArbol.q
enchufar Chamuyo

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:

PatPosta.q
enchufar Chamuyo

el neg
  dadoda No
  dado No da

la conj
  dados Sí Sí da
  dados _  _  da No
$ qr PatPosta -i
qriollo> conj (neg No) (neg No)
Sí
    de Posta

Ejemplo de patrones anidados con tuplas y numeritos:

PatTupla.q
enchufar Chamuyo

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:

PatLista.q
enchufar Chamuyo

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:

mirar <expresió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:

MirarDigitos.q
enchufar Chamuyo

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.

MirarMinimo.q
enchufar Chamuyo

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

dados <patrones>
     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:

MirarMinimo2.q
enchufar Chamuyo

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 que dado <parámetro> da <resultado>

La construcción te acepta varios parámetros:

la que dados <parámetros> da <resultado>

Y más en general te acepta que pongás un análisis de casos con guardas condicionales como cualquier función:

la que
  dados <parámetros> da <resultado>
  ...
  dados <parámetros> da <resultado>

Ponele:

Anonimas1.q
enchufar Chamuyo

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]
Anonimas2.q
enchufar Chamuyo

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:

dados <patrones> da <resultado>
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:

Base.q
enchufar Chamuyo

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:

ponele que
  <declaración>
  ...
  <declaración>
en
  <cuerpo>

Ponele, el algoritmo para incorpordenar una lista:

Incorpordenar.q
enchufar Chamuyo

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

Logica1.q
enchufar Chamuyo

el y de Posta en Posta en Posta
  dados Sí Sí da
  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
  dados Sí No da
  dados No Sí da
  dados No No da No

el neg de Posta en Posta
  dadoda No
  dado No da
$ 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# => Nocomparación por igual
10# != 3# => comparación por distinto
10# < 3# => Nocomparación por menor
10# > 3# => comparación por mayor
10# <= 3# => Nocomparación por menor o igual
10# >= 3# => comparación por mayor o igual
~10# => 4294967285#complemento bit a bit

Ponele:

Aritmetica.q
enchufar Chamuyo

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'7campanita
'\b'8backspace
'\f'12salto de página
'\t'9tab
'\v'11tab vertical
'\n'10salto de línea
'\r'13retorno de carro
'\"'34comilla doble (")
'\''39comilla simple (')
'\\'92contrabarra (\)

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:

Letras.q
enchufar Chamuyo

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:

InvertirLista.q
enchufar Chamuyo

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:

SumarPares.q
enchufar Chamuyo

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:

MayusculizarTexto.q
enchufar Chamuyo

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:

QuizaMinimo.q
enchufar Chamuyo

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#).

Referencias1.q
enchufar Chamuyo

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.

Referencias2.q
enchufar Chamuyo

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:

Polimorfismo.q
enchufar Chamuyo

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:

  1. una expresión: ponele que <definiciones> en <cuerpo>,
  2. una cláusula: <cuerpo> donde <definiciones> boludo,
  3. 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:

IdPolimorfica1.q
enchufar Chamuyo

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:

IdPolimorfica2.q
enchufar Chamuyo

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:

IdPolimorfica3.q
enchufar Chamuyo

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 <condición> da <resultado>
si no da <resultado>

Ponele:

Condicionales.q
enchufar Chamuyo

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:

chirimbolo <asociatividad> <precedencia> <nombre>

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.

Chirimbolo1.q
enchufar Chamuyo

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]))
Chirimbolo2.q
enchufar Chamuyo

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)]
Chirimbolo3.q
enchufar Chamuyo

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:

cualidad <nombre_cualidad> para <variable_de_tipo>
  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:

cualidad Comparable para coso
  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":

encarnar <nombre_cualidad> para <tipo_concreto>
  <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 ("<="):

encarnar Comparable para Numerito
  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:

encarnar <nombre_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:

encarnar Comparable para [coso]
                     con Comparable para coso
  el comparar
    dadas []       _        da
    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:

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

el escupir_coso de coso en () con Mostrable para coso
  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.

CualidadFinito.q
enchufar Chamuyo

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.

CualidadMonoide.q
enchufar Chamuyo

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

CualidadIterable.q
enchufar Chamuyo

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:

ccc de ((Cont de coso) en coso) en coso
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.

ContExcepciones.q
enchufar Chamuyo

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.

ContCorrutinas.q
enchufar Chamuyo

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

ContRetroceso.q
enchufar Chamuyo

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:

cualidad Mónada para bolsa
  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 <nombre1> es <valor1>
che el <nombre2> es <valor2>
...
che el <nombreN> es <valorN>
en <resultado>

que equivale a esta otra expresión:

<valor1> >>= (la que dado <nombre1> da
<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:

MonadaLista.q
enchufar Chamuyo

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:

gringo <lenguaje_gringo> "<invocación_gringa>"
       <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:

GRINGO <lenguaje_gringo> "<opción_gringa>"

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.

GringoC1.q
enchufar Chamuyo

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:

GringoC2.q
enchufar Chamuyo

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:

GringoPy1.q
enchufar Chamuyo

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'
GringoPy2.q
enchufar Chamuyo

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:

SI (±Funcionalidad, ..., ±Funcionalidad)
  <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:

CompilacionCondicional.q
enchufar Chamuyo

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:

  1. Compilado: se prende si estás compilando, en contraposición a interpretando.
  2. C: se prende si estás compilando a C.
  3. Py: se prende si estás compilando a Python.
  4. 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:

GringoCondicional.q
enchufar Chamuyo

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
FulanoFulano.q
Lenguaje.Qriollo.ParserLenguaje/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.q
enchufar Chamuyo

el saludo es "hola\n"
B.q
enchufar Chamuyo
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:

entregar (<nombre>, ..., <nombre>)

Ponele:

A.q
enchufar Chamuyo
entregar (saludo_público)

el saludo_público es "hola\n"
el saludo_privado es "chau\n"
B.q
enchufar Chamuyo
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.q
enchufar Chamuyo

OJO. A no entrega ningún nombre
entregar ()

el saludo_privado es "hola\n"
B.q
enchufar Chamuyo
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:

enchufar <módulo> (<nombre>, ..., <nombre>)

Ponele:

A.q
enchufar Chamuyo
entregar (saludo_relevante, saludo_irrelevante)

el saludo_relevante   es "hola\n"
el saludo_irrelevante es "chau\n"
B.q
enchufar Chamuyo
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:

enchufar <módulo> (
     ...,
     <nombre_ajeno> como <nombre_local>,
     ...
  )

Ponele:

A.q
enchufar Chamuyo
entregar (saludo)

el saludo es "hola\n"
B.q
enchufar Chamuyo
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.

Lizp.q
enchufar Chamuyo

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
  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
          sida 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
factorial.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)))))))
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:

ante bien boluda boludo BOLUDA BOLUDO che chirimbolo como con cualidad cuya cuyas cuyo cuyos da dada dadas dado dados de donde el en encarnar enchufar entregar es gringa gringas gringo gringos GRINGA GRINGAS GRINGO GRINGOS la las los mirar no para pero ponele que si SI son tiene tienen un una unas unos

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:

cualidad Digital para coso
  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

  1. 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.
  2. 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).
  3. 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:
    1. Colecciones (listas, vectores, conjuntos, diccionarios, texto Unicode y texto binario).
    2. Aritmética (números enteros grandes, números de punto flotante, racionales, complejos).
    3. Estructuras de control adicionales (iteradores, excepciones, condiciones).
    4. Entrada/salida (manejo de archivos, sockets, protocolos de internet, interfaz con bases de datos).
    5. Cualidades (funtores, mónadas, tipos de datos plegables).
    6. Concurrencia (corrutinas, procesos con colas de mensajes).
    7. Utilidades varias (expresiones regulares, fecha/hora, interfaz con el sistema operativo, imágenes, audio, criptografía, números pseudoaleatorios, compresión, etcétera).
    8. Herramienta de documentación para Qriollo.
    9. Generador de parsers.
    10. Todo lo demás.
  4. 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.
  5. 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 x

    el programa es (identidad 1#, identidad 'a')
    Pero el programa siguiente funciona, porque la identidad se declara explícitamente como polimórfica:
    la identidad de coso en coso
      dado x da x

    el programa es (identidad 1#, identidad 'a')
    El programa siguiente también funciona, porque aunque la declaración de la identidad no incluye explícitamente su tipo, su definición es interna:
    el programa es (identidad 1#, identidad 'a')
    donde
      la identidad dado x da x
    boludo
  6. 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.
  7. 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 .