Saltar al contenido
Ridgun
Español

Referencia

Especificación del lenguaje Ridgun

Visión general

Ridgun es un lenguaje funcional y pensado para el paralelismo desde el principio, con operadores en lenguaje natural y tipado estático fuerte. Su sintaxis se inspira en Haskell y en los lenguajes de la familia ML, e introduce construcciones nuevas para el paralelismo y para definir operadores legibles por personas.

Características principales:

  • Operadores en lenguaje natural — Los operadores se leen como frases en inglés
  • Paralelismo implícito — La ejecución paralela es una primitiva sintáctica, no una llamada a una librería
  • Tipado estático fuerte — Todos los tipos se declaran en las firmas
  • Definiciones por patrones — Las funciones y los operadores se definen mediante ecuaciones

Comentarios

Los comentarios de una línea usan --:

-- This is a comment

Cadenas

Ridgun tiene dos tipos de literales de cadena, con distinto comportamiento de interpolación.

Cadenas interpoladas (comillas dobles)

Las cadenas entre comillas dobles admiten interpolación de código en línea con {expr}. La expresión entre llaves se evalúa y su resultado se inserta en la cadena.

name = "Ridgun"
"Hello, {name}!"                         -- "Hello, Ridgun!"
"Sum is {sum 3 4}"                       -- "Sum is 7"
"Nested: {factorial 5} done"             -- "Nested: 120 done"

Para incluir una llave literal dentro de "...", escápala con una barra invertida (\{ y \}) o duplícala ({{ y }}):

"Shell expansion: \$\{VAR:-default\}"    -- "Shell expansion: ${VAR:-default}"
"This is {{not interpolated}}"           -- "This is {not interpolated}"
"Use \{ \} or {{}} for literal braces"   -- "Use { } or {} for literal braces"

Cadenas en crudo (comillas simples)

Las cadenas entre comillas simples van en crudo: no se realiza ninguna interpolación. Todos los caracteres se tratan como texto literal, incluidas las llaves.

'Hello, {name}!'                         -- "Hello, {name}!"
'{this is just pure chars}'              -- "{this is just pure chars}"
'No {interpolation} here'               -- "No {interpolation} here"

Resumen

SintaxisInterpolaciónCaso de uso
"..."Sí — {expr} (escapa las llaves con \{/\} o {{/}})Cadenas dinámicas con expresiones incrustadas
'...'No — todo literalTexto estático, plantillas, patrones de expresiones regulares

Estructura de un programa

Todo el código ejecutable de Ridgun debe estar dentro de una función o de un operador. La función main es el punto de entrada obligatorio para ejecutar el programa.

fn :: main => ()
      main = echo "Hello, Ridgun!"

Un archivo fuente de Ridgun solo puede contener:

  • sentencias import
  • definiciones type
  • declaraciones y definiciones fn y op
  • una función main como punto de entrada

No se permiten expresiones sueltas en el nivel superior. Todo lo que se ejecuta debe llamarse desde main.

Uso de la línea de comandos

ridgunc <command> [options] <file.rg>
ComandoFunción
runCompila y ejecuta un archivo fuente
checkComprueba los tipos y analiza sin ejecutar
compileGenera un archivo de bytecode .rgc
buildCrea un ejecutable nativo; una biblioteca con --lib o --so
versionMuestra la versión del compilador
helpMuestra la ayuda

El formato, las pruebas y los proyectos corresponden a stable.


Funciones y operadores

Las funciones y los operadores infijos o en lenguaje natural se declaran con una firma de tipos que termina en => <return_type>, seguida de una o más ecuaciones (cases). Los prefijos iniciales fn :: y op :: son opcionales:

  • Forma canónica sin prefijo (empieza en la columna 0):
  list len [A]    => Int
  list len []     =  0
  list len (_:xs) =  1 + (list len xs)

  swap A with B => (B, A)
  swap x with y =  (y, x)
  • Forma explícita con prefijo (fn :: / op ::, empieza en la columna 0, con las cláusulas alineadas en la columna 6):
  fn :: sum Int Int => Int
        sum a   b   =  a + b

  fn :: format app err AppErr        => String
        format app err (IoError msg) =  "IO:{msg}"
  • Espacios naturales en fn ::, funciones nativas y nombres de archivo (FR-COMP-005):
  • Las declaraciones y llamadas fn :: admiten nombres de varias palabras separadas por espacios (fn :: format app err AppErr => String, fn :: project name => String), igual que op :: y las declaraciones sin prefijo.
  • Las funciones nativas del runtime y de tests se pueden llamar con espacios (assert equals a b, str len s, read file p, cmm live blocks), y las formas con _ siguen siendo compatibles.
  • ridgunc y stable admiten de forma nativa las referencias de primera clase a funciones de varias palabras (map double val xs, ||map double val xs, filter is even xs, fold add pair 0 xs) y los nombres de archivo .rg con espacios (src/test/ridgun/test core language.rg).

Variables de tipo polimórficas (A..Z) y palabras clave contextuales (type, with)

Para garantizar que no haya ninguna ambigüedad léxica entre las palabras de un operador y los parámetros de tipo, tanto en las firmas con prefijo (op ::) como en las firmas sin prefijo:

  • Todo token de tipo en una firma empieza por una letra mayúscula (o por [...] / (...)):
  • Las letras mayúsculas sueltas (A..Z) denotan variables de tipo polimórficas (por ejemplo, [A], (A, B), (A => B), swap A with B => (B, A)). Las apariciones repetidas de una variable de tipo entre los parámetros se unifican en cada punto de llamada y se sustituyen en el tipo de retorno.
  • Los identificadores de varios caracteres con inicial mayúscula (Int, String, Bool, RExpr, Tensor) denotan tipos concretos o definidos por el usuario.
  • Todo token en minúsculas (o símbolo de operador +, -, *, /, %) en una firma de nivel superior forma parte del nombre de la función o del operador mixfix.
  • Palabras clave contextuales type y with: type solo introduce una declaración de tipo en el nivel superior cuando le sigue UpperName =. En cualquier otro lugar (firmas fn :: y op ::, palabras de operador, patrones de parámetros, variables locales y nombres de campos de registro), type y with son identificadores o palabras válidos, sin ambigüedad.
  • Una firma de nivel superior con un identificador inicial en minúsculas seguido solo de tipos antes de => es una función prefija (fn); cualquier firma con dos o más palabras en minúsculas o símbolos de operador intercalados con tipos antes de => es un operador mixfix, infijo o postfijo (op).

La declaración y la definición están separadas:

  • Declaración (fn ::) — indica el nombre, los tipos de los parámetros y el tipo de retorno
  • Definición — vincula los nombres de los parámetros y aporta la implementación

Operadores

Los operadores son el rasgo distintivo de Ridgun. Se nombran con lenguaje natural y se definen con op ::. A diferencia de los operadores tradicionales (+, -), los operadores de Ridgun son expresiones de varias palabras que se leen como frases.

Sintaxis

op :: <natural language signature with types> -> <return_type>
      <natural language pattern with params> = <body>

Ejemplo básico

-- Declaration: "add [Int] to [Int]" returns an Int
op :: add Int to Int => Int
      add a to b = a + b

Su uso se leería así: add 3 to 5 → 8

Las palabras entre los parámetros con tipo (to, and, in, etc.) son palabras clave del operador: forman parte de la identidad del operador y deben aparecer en los puntos de llamada.

Operadores paralelos (||)

Los operadores pueden expresar la ejecución paralela como un concepto de primera clase mediante ||:

-- Run two configs in parallel with named DAG bindings (|| expr -> var)
op :: run Conf and Conf in parallel => Res, Res
      run a and b in parallel       =  res_a, res_b
        || run my own process a     -> res_a
        || run my own process b     -> res_b

Elementos clave:

  • Varios valores de retorno — => Res, Res declara dos valores de retorno
  • Bloque || expr -> var — Vincula subcálculos a nombres de resultado (where se ha eliminado; || unifica todas las ramas paralelas)
  • -> — Asigna el resultado de un subcálculo paralelo a una variable (o a una porción de tensor -> out[i, j])
  • Las ramas || independientes se ejecutan de forma concurrente como un DAG paralelo, según las dependencias de datos

Bloque directo de tubería paralela (|| expr)

Cuando una expresión o función devuelve directamente una tupla de cálculos paralelos sin nombres de variable intermedios, las líneas || aparecen justo después de =:

-- Run two processes in parallel and return their results as a tuple
op :: another way to run two processes Conf Conf in parallel => Res, Res
      another way to run two processes a b in parallel =
        || run my own process a
        || run my own process b

Cada línea || se ejecuta como una tarea paralela independiente. Los resultados se recogen en el orden de declaración.

Sobrecarga de operadores

Los operadores existentes (incluidos los nativos como +) se pueden sobrecargar para tipos nuevos:

-- Overload + for Complex numbers
op :: Complex + Complex => Complex
      a + b = (real a) + (real b) + complex_i * ((imaginary a) + (imaginary b))

Precedencia

Los operadores simbólicos tienen la precedencia más alta: primero las matemáticas.

Cuando aparecen operadores simbólicos (como +, *) dentro del cuerpo de un operador en lenguaje natural, se vinculan con más fuerza que las palabras clave del operador que los rodea. Así, las expresiones aritméticas se evalúan como se espera, sin paréntesis de más.

PrecedenciaCategoríaEjemplos
MáximaOperadores simbólicos*, /, +, -, %
Aplicación de funciónf x
Comparación==, !=, <, >
Lógicosand, or, xor, not
MínimaOperadores en lenguaje naturaladd a to b, run a and b in parallel

Encadenamiento de operadores y tuberías idiomáticas de izquierda a derecha (->) (FR-COMP-014)

Los operadores y las funciones que transforman valores o secuencias se pueden encadenar de izquierda a derecha, de modo que los datos fluyan con naturalidad del origen al destino, sin paréntesis anidados de derecha a izquierda (h (g (f x))) ni variables intermedias desechables (st1, st2, st3).

1. El estilo de tubería idiomático recomendado en Ridgun (2b)

Ridgun unifica en el mismo AST la invocación infija con el operando a la izquierda y las etapas explícitas de tubería con ->. Aunque las cuatro formas siguientes son válidas y equivalentes, la forma 2b (empezar de forma natural con un sujeto y una operación a la izquierda, encadenar las transformaciones siguientes con -> y vincular el resultado final con -> target) es el estilo idiomático recomendado en Ridgun:

-- Recommended idiomatic style (2b):
xs map (x => x * 2) -> filter (x => x > 6) -> compact limit 14 -> results

-- Equivalent unified forms (all normalize to the exact same AST):
xs -> map (x => x * 2) -> filter (x => x > 6) -> compact limit 14 -> results  -- (2) explicit -> from subject
xs map (x => x * 2) filter (x => x > 6) compact limit 14 -> results           -- (1) contiguous infix chain
map (x => x * 2) xs -> filter (x => x > 6) -> compact limit 14 -> results     -- (3) prefix start + -> chain

Las tuberías de varias etapas también se pueden escribir en líneas de continuación sangradas que empiezan por ->:

raw_nums map (x => x * 3)
  -> filter (x => x >= 12)
  -> fold 0 (acc x => acc + x)
  -> total_sum

2. Regla universal del primer argumento con -> y combinadores de colecciones

  • Regla universal para funciones y operadores: cualquier valor que venga de la izquierda de -> (o de la izquierda de una llamada infija x op a b) entra como primer argumento de la función o del operador de la derecha:
  • x -> f a b $\equiv$ x f a b $\equiv$ f x a b
  • Funciona igual para las declaraciones fn :: y para las declaraciones mixfix op ::, sin duplicar firmas.
  • Si una función posterior necesita el valor de la tubería en un parámetro que no es el primero, un marcador explícito _ coloca el valor de la tubería en _ (payload -> write_file output_path _).
  • Combinadores de colecciones (map, ||map, filter, ||filter, fold): aceptan la colección en la posición prefija tradicional (map f xs, filter g xs, fold f init xs) o por la izquierda de una etapa infija o de tubería (xs map f, xs -> map f, xs filter g, xs -> filter g, xs fold init f, xs -> fold init f).
  • Etapas de agente (-> send to, -> ask to): al pasar por -> send to ag o -> ask to ag, el valor de la izquierda se envía como mensaje ([5, 10, 15] -> sum int list -> send to worker_ag).
  • Vinculación final a una variable (-> var): cuando una sentencia de tubería en un bloque secuencial termina en -> var (donde var es el nombre de una variable local y no una función declarada), vincula var (var = <pipeline_result>) durante el resto del bloque.

3. Máscaras de secuencia en tuberías (-> (+ _ _ +) ->)

Cuando una etapa devuelve una secuencia de varios valores o una tupla, una etapa de máscara de secuencia (-> (+ _ _ +) ->) filtra sus elementos (+ conserva una posición, _ la descarta) y desempaqueta la tupla seleccionada directamente en una función posterior de varios argumentos:

parse header packet "v0.9.9" -> (+ _ _ +) -> combine code msg -> status_line

4. Sentencias con efectos secundarios sin _ = y descarte explícito de unit (-> ())

En los bloques secuenciales, las sentencias con efectos secundarios (echo, write_file, write_bytes, shell_exec, log info, print_error_lines, sys_mkdir, etc.) se escriben directamente, sin la coletilla _ =:

verify strict if enabled strict_mode input_path
s_errs -> format_type_errors -> print_error_lines
echo "[ridgunc --strict] {input_path}: {err_c} error(s) found."
sys_exit 1

Cuando la expresión final de una función => () llama a una función auxiliar que devuelve un valor distinto de () (como shell_exec : String => Int), añade -> () para evaluar la expresión, descartar su valor de retorno y producir ():

fn :: exec built run Int => ()
      exec built run 0   =  shell_exec "./target/app" -> ()
      exec built run _   =  sys_exit 1

5. Encadenamiento de operadores de secuencia sin puntos (point-free)

Los operadores que devuelven secuencias también se pueden encadenar sin puntos:

op :: Int Int switch -> Int Int
      a   b   switch =  b   a

op :: Int Int invariant -> Int Int
      a   b   invariant =  a b switch switch

Nota: en las cabeceras fn :: / op :: se aceptan tanto -> como => como separadores del tipo de retorno. En las expresiones, -> es el operador de tubería y vinculación de izquierda a derecha, mientras que => introduce clausuras léxicas (x => expr).


Sistema de tipos

Firmas de tipos

Las firmas de tipos aparecen en las declaraciones, después de fn :: o op :: (o sin prefijo):

fn :: <name> <type1> <type2> ... => <return_type1> <return_type2> ...
op :: <words and types mixed> => <return_type1> <return_type2> ...
  • Los parámetros son posicionales; los tipos aparecen donde se esperan los parámetros.
  • Sintaxis unificada de tipos de tupla y de secuencia (FR-COMP-008):
  • En Ridgun, una lista de argumentos T1 T2 ... Tn y una tupla (T1, T2, ..., Tn) representan el mismo tipo compuesto subyacente: una secuencia ordenada de tamaño fijo de valores con tipo (seq).
  • Por eso, los tipos separados por espacios y los tipos de tupla separados por comas son 100% intercambiables en los tipos de retorno, en los corchetes de lista y en las tuplas entre paréntesis:
  • Tipos de retorno después de => / ->:
      fn :: parse header String => Int Int [String String] String
      -- 100% equivalent to:
      fn :: parse header String => (Int, Int, [(String, String)], String)
  • Listas de tuplas ([T1 T2 ... Tn]):
      [String String]        -- equivalent to [(String, String)]
      [Int String Bool]      -- equivalent to [(Int, String, Bool)]
  • Tuplas entre paréntesis ((T1 T2 ... Tn)):
      fn :: fst (A B) => A
            fst (a, _) =  a
  • Por qué esto no crea ninguna ambigüedad sintáctica: a diferencia de Haskell o ML (donde la yuxtaposición Maybe Int denota aplicación de tipos), los tipos parametrizados de Ridgun usan paréntesis explícitos (Tensor(Float, 3, 3)) y las listas usan corchetes ([T]). Además, todo token de tipo empieza por una letra mayúscula (A..Z, Int, String), por [ o por (, mientras que las palabras de funciones y operadores van en minúsculas. Así, T1 T2 nunca entra en conflicto con constructores de tipos ni con palabras clave de operadores.
  • La flecha => (o ->) separa los tipos de entrada del tipo de retorno.

Tipos nativos

TipoDescripción
IntEntero
RealNúmero real
BoolBooleano
CharUn único carácter Unicode (UTF-8)
StringCadena de texto (UTF-8)
ComplexNúmero complejo
TensorTensor mutable de N dimensiones (elementos Real, CPU)
Tensor_GPUTensor mutable de N dimensiones (elementos Real, GPU)

[!NOTE] Todo el texto en Ridgun está codificado en UTF-8 por defecto. Char representa un único punto de código Unicode y String es una secuencia de Char.

Literales de Bool

Bool tiene dos valores primitivos:

True                                     -- boolean true
False                                    -- boolean false

True y False son constructores: se pueden usar en expresiones, pasar como argumentos y usar en pattern matching:

fn :: is_valid Int => Bool
      is_valid n =
        | n > 0     = True
        | otherwise = False

Operadores lógicos

Ridgun usa operadores lógicos con palabras en minúsculas en lugar de símbolos:

OperadorSignificadoEjemplo
andConjunción lógicaa and b
orDisyunción lógicaa or b
xorDisyunción exclusivaa xor b
notNegación lógicanot a
result = True and False                  -- False
result = True or False                   -- True
result = True xor True                   -- False
result = not False                       -- True

Son operadores infijos de palabra clave con la semántica booleana estándar.

Tipos de colección nativos

Listas

Las listas son un tipo recursivo nativo e inmutable con sintaxis propia. No hace falta definir ningún ADT a mano. Las listas son adecuadas para el procesamiento funcional de datos, el cálculo simbólico y los algoritmos recursivos. Para el cálculo numérico mutable, consulta Tensores más abajo.

Sintaxis de tipo: [type] — una lista de elementos del tipo indicado.

fn :: sum_list [Int] => Int
fn :: reverse [Int] => [Int]

Sintaxis de literal: [elem1, elem2, ...] — un literal de lista. [] es la lista vacía.

fn :: my_list => [Int]
my_list = [1, 2, 3, 4, 5]

Pattern matching con cons: (x:xs) — descompone una lista en su cabeza x y su cola xs. [] coincide con la lista vacía. Usa _ como comodín que coincide pero no vincula.

fn :: sum_list [Int] => Int
      sum_list []     = 0
      sum_list (x:xs) = x + sum_list xs

fn :: list_length [Int] => Int
      list_length []     = 0
      list_length (_:xs) = 1 + list_length xs

Concatenación: :: une dos listas (o secuencias).

fn :: append [Int] [Int] => [Int]
      append a b = a :: b

Tensores

Los tensores son el tipo numérico nativo mutable de N dimensiones de Ridgun. Generalizan vectores, matrices y arrays de más dimensiones en un solo tipo. Los elementos son Real por defecto.

Sintaxis de literal: Tensor seguido de corchetes anidados. La profundidad de anidamiento determina la dimensionalidad.

v = Tensor [1, 2, 3]                    -- 1D (vector), shape: (3)
m = Tensor [[1, 2], [5, 3]]              -- 2D (matrix), shape: (2, 2)
t = Tensor [[[1, 2], [3, 4]],            -- 3D tensor, shape: (2, 2, 2)
            [[5, 6], [7, 8]]]

Indexación: se admite tanto la indexación encadenada como la separada por comas.

val = m[0][1]        -- chained indexing: row 0, col 1
val = m[0, 1]        -- comma indexing: equivalent
row = m[0]           -- slice: entire first row

Mutación (in-place): los tensores son mutables: los elementos y las porciones se pueden modificar in-place (al estilo de PyTorch y NumPy).

m[0, 1] = 5.0                            -- set element
m[0] = Tensor [10, 20]                   -- set entire row

Contracción de tensores (@): el operador @ realiza la suma de Einstein. Los subíndices de cada operando definen la contracción: los índices compartidos por todos los operandos se suman; los índices únicos sobreviven en el resultado.

-- A has shape (s, d, f), B has shape (n, d, f)
-- Shared indices: d, f → contracted (summed)
-- Result T has shape (s, n)
T = A_sdf @ B_ndf

-- Equivalent to: np.einsum('sdf,ndf->sn', A, B)

La contracción funciona con varios tensores en una sola expresión:

-- A(a,b,c), B(a,b,c), C(a,b,c,d)
-- Shared: a, b, c → contracted. Result R has shape (d)
R = A_abc @ B_abc @ C_abcd

-- Equivalent to: np.einsum('abc,abc,abcd->d', A, B, C)

Operaciones de Hadamard (+, -, *, /): los operadores aritméticos con subíndices realizan operaciones elemento a elemento. Los índices que coinciden alinean las dimensiones; los que no coinciden se expanden por broadcasting.

-- Element-wise multiply: same shape, same indices
H = A_ij * B_ij                          -- shape (i, j)

-- Broadcasting: B has fewer dimensions
S = A_ij * B_j                           -- B broadcasts along i

-- Element-wise add
C = A_ij + B_ij

-- Element-wise divide
D = A_ij / B_j                           -- B broadcasts along i

Esto elimina la necesidad de unsqueeze, expand_dims o de reorganizar la forma a mano: los índices son las reglas de broadcasting.

Variante para GPU: Tensor_GPU coloca el tensor en la memoria de la GPU para acelerar el cálculo.

g = Tensor_GPU [[1, 2], [3, 4]]
result = g_ij @ g_jk                     -- GPU-accelerated matrix multiply

Secuencias (seq)

Las secuencias son el tipo compuesto central de Ridgun. Una seq es una colección ordenada de valores de tamaño fijo; en esencia, una tupla con tipo. Las listas de parámetros de funciones y operadores son secuencias de forma implícita.

-- These parameter types form implicit sequences:
fn :: sum Int Int => Int          -- params: seq(Int, Int)
fn :: process Int Real => Bool   -- params: seq(Int, Real)

Vinculación de secuencia ((name)) — poner el nombre de un parámetro entre paréntesis vincula todos los parámetros como una sola variable de secuencia:

fn :: first_of_seq Int Int => Int
      first_of_seq (s)     =  s(+ _)

-- Equivalent without seq binding:
fn :: first_of Int Int => Int
      first_of s   _   => s

Selector de máscara (s(+ _)) — extrae elementos mediante una máscara, donde + selecciona una posición y _ la omite:

s(+ _)                -- select 1st, skip 2nd
s(_ +)                -- skip 1st, select 2nd
s(+ _ _ _)            -- select 1st, skip 2nd-4th
s(_ _ _ +)            -- skip 1st-3rd, select 4th

Selector de índices (s(0 3)) — extrae elementos por sus posiciones, contadas desde cero:

fn :: extreme_points Int Int Int Int => Int Int
      extreme_points (s)             =  s(0 3)

Unión plana (:) — concatena dos secuencias en una sola secuencia plana:

seq1 = a, b         -- seq(a, b)
seq2 = c, d         -- seq(c, d)
seq1:seq2            -- seq(a, b, c, d)

Anidamiento (,) — compone secuencias como elementos distintos (secuencia de secuencias):

seq1, seq2           -- seq(seq(a, b), seq(c, d))

Tipos de datos algebraicos (ADT)

Ridgun usa tipos de datos algebraicos para los tipos definidos por el usuario. No hay clases, métodos ni bloques impl: las funciones y los operadores reciben los tipos como parámetros.

Tipos producto (registros) y sintaxis de actualización de registros (with)

Un tipo con campos con nombre:

type Conf  = host: String, port: Int, tls: Bool
type Point = x: Real, y: Real

A los campos se accede por su nombre, como funciones (host c, port c), y se actualizan de forma funcional con with y dos puntos ::

fn :: get_host Conf => String
      get_host c    =  host c

-- Functional record update (creates a new Conf with updated fields;
-- mutates in place via Arc::try_unwrap when the record is uniquely owned):
fn :: enable_tls Conf => Conf
      enable_tls c    =  c with tls = True, port = 443

Tipos suma (variantes)

Un tipo con varios constructores, separados por |:

type Result = Ok Int | Err String
type Option = Some Int | None
type Color = Red | Green | Blue

ADT suma genéricos y registros producto genéricos (FR-COMP-013)

Los ADT suma y los registros producto definidos por el usuario pueden declarar uno o más parámetros de tipo en mayúsculas (A..Z o nombres de variable de tipo con inicial mayúscula) entre el nombre del tipo y =:

type Result T E = Ok T | Err E
type Option A   = Some A | None
type Box A      = Box A
type Page T     = items: [T], total: Int
type Pair A B   = fst: A, snd: B
  • Las aplicaciones de tipos paramétricos (Result Int String, Page User, [Box Int]) se pueden usar en firmas fn :: / op ::, en campos de registro anidados y en los datos de los constructores de ADT.
  • Los constructores (Ok 42, Ok "hello", Page [1, 2] 2), el pattern matching, los accesores de campo (items p, total p) y las actualizaciones funcionales de registros (p with total = 10) instancian variables de tipo nuevas en cada punto de llamada.
  • type Option A = Some A | None está preregistrado como ADT polimórfico nativo cuando el usuario no lo ha declarado ya, de modo que try receive from ag devuelve Some v o None sin necesidad de declaraciones repetitivas (FR-COMP-013).

Funciones anónimas y clausuras léxicas ((x => expr), (a b => expr) — FR-COMP-013)

Las funciones anónimas (lambdas) se escriben entre paréntesis, con los parámetros separados por espacios, seguidos de => y la expresión del cuerpo ((x => expr) o (a b => expr); por ejemplo, map (x => x + offset) xs, fold (acc x => acc + x) 0 xs), y pueden capturar variables locales o parámetros de su ámbito léxico envolvente:

fn :: add offset Int [Int] => [Int]
      add offset k   xs    =  map (x => x + k) xs

Cinco reglas canónicas rigen las funciones anónimas:

  1. Paréntesis obligatorios: toda lambda debe ir entre paréntesis (... => ...). Escribir x => expr sin paréntesis (como f = x => x + 1) es un error de compilación (lambda expression must be enclosed in parentheses: '(x => expr)').
  2. Lambdas sin parámetros ((=> expr)): Ridgun prohíbe () antes de => en las firmas de funciones (fn :: main => ()). De forma coherente, una lambda sin parámetros se escribe (=> expr) (se invoca como thunk ()), y su tipo en las firmas es (=> Type). Escribir (() => expr) o (() => Type) es un error de compilación.
  3. Varios parámetros frente a desestructuración de tupla ((a b => expr) frente a ((a b) => expr)): (a b => expr) es una lambda de 2 parámetros (como fold (acc x => acc + x) 0 xs), mientras que ((a b) => expr) es una lambda de 1 parámetro que desestructura una tupla (map ((a b) => a + b) [(1, 2), (3, 4)]). Si los confundes, obtienes un error de tipos específico que explica la diferencia.
  4. Cuerpos de lambda de varias líneas: cuando => está al final de la línea, el bloque sangrado de debajo forma un cuerpo de varias sentencias (o de guardas |) que se cierra con ):
   map (x =>
     doubled = x * 2
     doubled + offset
   ) [1, 2, 3]
  1. Tipos en mayúsculas frente a valores en minúsculas: (Conn => Conn) es un tipo de función y (conn => conn) es un valor lambda. Escribir tipos en mayúsculas (Conn => Conn) en una expresión o parámetros en minúsculas (conn => conn) en una firma fn :: se rechaza con un error de compilación claro.

Las vinculaciones capturadas se elevan automáticamente a clausuras de aplicación parcial gestionadas por RMM1/RMM2.

Tipos recursivos

Los tipos pueden hacer referencia a sí mismos:

type List = Empty | Cons Int List
type Tree = Leaf Int | Node Tree Tree

Nombres de constructor compartidos entre tipos (FR-COMP-005)

Varias declaraciones type pueden reutilizar los mismos nombres de constructor de variante (como Error, Var, Lit, None), con la misma aridad o con otra distinta. El verificador de tipos distingue los patrones de constructor por el tipo de parámetro esperado en la columna, y resuelve las llamadas a constructores por la aridad y los tipos de los argumentos y por los tipos de retorno de los ADT de unión:

type ParseOut = Parsed Int | Error String
type DbOut    = Rows Int   | Error Int String
type MiniExpr = Var String | Lit Int
type MiniPat  = Var String | Lit Int | Wild

Extensiones abiertas de tipos (expand type) y fusión automática de cláusulas fn :: (FR-COMP-005)

Los ADT suma y los registros producto existentes se pueden extender con expand type UpperName = ...:

  • Extender un ADT suma: añade nuevos constructores de variante al tipo existente.
  • Extender un registro producto: añade nuevos campos con nombre (field: Type) y genera automáticamente sus funciones de acceso.
  • Variantes con nombre de tipo sin nombres de constructor redundantes: en las declaraciones suma type y expand type, una variante cuyo nombre es un tipo nativo sin más (String, Int, Real/Float, Bool, Char), sin más argumentos de tipo detrás, representa directamente los valores de ese tipo (sin escribir Int Int ni String String). Quien llama puede pasar valores directos (describe "hej", describe 42) o llamadas explícitas al constructor (describe (String "hej"), describe (Int 42)), y las ecuaciones pueden coincidir tanto con literales ("admin", 0) como con patrones tipados ((String s), (Int n)).
  • Fusión automática de cláusulas fn :: / op :: (sin expand fn): volver a declarar un fn :: o op :: existente con cláusulas de patrón nuevas las fusiona automáticamente con la definición existente de la función, y coloca las cláusulas con patrones concretos de constructor o de literal antes de cualquier cláusula de respaldo que lo capture todo (_ o variable):
type Something = String | Int | MyStruct Float Int
expand type Something = Bool

fn :: describe something Something            => String
      describe something "admin"              =  "special_admin"
      describe something 0                    =  "zero_int"
      describe something (String   s)         =  "str:{s}"
      describe something (Int      n)         =  "int:{n}"
      describe something (Bool     b)         =  "bool:{b}"
      describe something (MyStruct ratio cnt) =  "struct:{ratio}:{cnt}"
type AppErr = IoError String | Timeout Int

fn :: format app err AppErr        => String
      format app err (IoError msg) =  "IO:{msg}"
      format app err (Timeout ms)  =  "TO:{ms}"
      format app err _             =  "UNKNOWN"

expand type AppErr = AuthError String | DbError Int String

fn :: format app err AppErr              => String
      format app err (AuthError user)    =  "AUTH:{user}"
      format app err (DbError   code db) =  "DB:{code}:{db}"

type AppConfig        = host: String, port: Int
expand type AppConfig = tls: Bool

Espacios de nombres de módulo, acceso cualificado y visibilidad estricta de importaciones por módulo (FR-COMP-012)

Cada archivo .rg o paquete .rglib define un espacio de nombres de módulo (derivado del nombre del archivo sin extensión o de una cabecera explícita module :: <name>) y puede importar otros módulos mediante import <mod> o import <mod> as <alias>.

  1. Módulos de varios archivos (module :: <name>):
  • Varios archivos que declaran la misma cabecera module :: <name> (por ejemplo, orm.rg y orm_changeset.rg, que empiezan ambos por module :: orm) comparten un único espacio de nombres lógico de módulo orm y ven directamente los tipos, constructores, campos de registro y funciones de los demás.
  1. Acceso cualificado (mod.Type, mod.Constructor, mod.field, mod.fn):
  • Los tipos exportados, los constructores de ADT suma, los accesores de campo de registros producto, las actualizaciones de registros (with) y las funciones se pueden referenciar con un prefijo <mod_or_alias>. (http.Config, db.Status, http.Ok 200, http.port cfg, http.check status cfg).
  1. Resolución estricta de nombres sin cualificar por módulo, en 3 niveles:
  • Cada módulo M (incluido el módulo raíz de la aplicación) resuelve los tipos, constructores, campos de registro, funciones y llamadas de varias palabras sin cualificar estrictamente según el grafo de importaciones del propio M, en tres niveles de prioridad:
  • Nivel 1 — El propio módulo (M): las declaraciones del propio M (en todos los archivos que comparten module :: M) siempre tienen la máxima prioridad.
  • Nivel 2 — Importaciones directas de M: si M no define el nombre, ridgunc busca en los módulos que M importa directamente (import D). Si una única importación directa D define el nombre, este se resuelve sin ambigüedad a la declaración de D, aunque otros módulos de la unidad de compilación (dependencias transitivas, módulos hermanos o la aplicación raíz) también definan ese nombre. Si dos o más importaciones directas de M definen el mismo nombre y M no lo define, las referencias sin cualificar se rechazan por ambiguas (__ambiguous__.<name>) y deben cualificarse (d1.<name> frente a d2.<name>).
  • Nivel 3 — Importaciones transitivas de M: solo se consultan cuando ni el nivel 1 ni el nivel 2 definen el nombre. Por tanto, una importación directa en M (import json) oculta limpiamente una función auxiliar transitiva (codec.json_get_str, que solstice importa internamente) sin obligar a solstice ni a M a renombrar o cualificar sus llamadas.
  • Aislamiento de los módulos hermanos no importados y de la aplicación raíz: las declaraciones definidas solo en módulos que M no ha importado (ni directa ni transitivamente), incluidas las librerías hermanas importadas por la aplicación raíz o las declaraciones de la propia aplicación raíz al compilar una librería importada M, son invisibles para la búsqueda sin cualificar dentro de M (__unimported__.<name>).

Gramática unificada de = ((none), |, ||) y variables locales (FR-COMP-003)

Toda definición o vinculación en Ridgun, ya sea una cláusula de función (fn ::), una cláusula de operador (op ::), una variable local inmutable (x =), una variable local mutable (mut x =) o una rama de guarda (| cond = / | otherwise =), termina en = y acepta a la derecha exactamente las mismas cuatro formas de bloque o de expresión:

Prefijo después de =Semántica de ejecuciónSignificado
<expr> en líneaUna sola expresión (con líneas || expr -> var opcionales debajo)Evalúa <expr> directamente (resolviendo las vinculaciones del DAG paralelo || que tenga debajo).
(none) (líneas sangradas)Secuencial (de arriba abajo)Evalúa las sentencias en orden (mut, asignaciones, efectos secundarios); la última expresión es el resultado.
| <cond> = ...Condicional (guardas y árboles de decisión anidados)Evalúa las condiciones booleanas de arriba abajo hasta la primera rama True (o | otherwise =).
|| <expr>Paralelo (DAG de green threads o tupla)Evalúa las ramas || de forma concurrente (|| expr -> var para nodos de DAG con nombre, || expr para resultados paralelos en tupla).

1. Las guardas (|) siempre van detrás de = (funciones, operadores, variables locales y árboles de decisión anidados)

Los bloques de guardas (| cond = ... y | otherwise = ...) siempre aparecen detrás de un =, lo que unifica las cláusulas de función, las cláusulas de operador, las definiciones de variables locales y los árboles condicionales anidados:

Cláusulas de función y de operador con guardas

fn :: abs Int => Int
      abs n =
        | n < 0     = 0 - n
        | otherwise = n

op :: clamp Int between Int and Int => Int
      clamp x   between lo  and hi  =
        | x < lo    = lo
        | x > hi    = hi
        | otherwise = x

Asignación de variables locales con guardas

Como | es válido después de cualquier =, las variables locales se pueden vincular de forma condicional sin necesitar una palabra clave if/else ni una función auxiliar:

discount =
  | is_vip      = 20
  | total > 100 = 10
  | otherwise   = 0

Bloques secuenciales de varias líneas, árboles de decisión profundos y cierres anidados (|. / ||.)

Cada rama de guarda termina en =, así que su cuerpo puede ser a su vez un bloque secuencial ((none)), un bloque paralelo (||) u otro bloque de guardas | anidado, lo que permite árboles de decisión arbitrarios (de 3 o 4 niveles de profundidad, o más) dentro de | cond = y | otherwise =.

  • Cerrar niveles anidados (|. y ||.):
  • En los bloques simples de 1 nivel o al final de una función o árbol, no hace falta ningún token de cierre.
  • Cuando un subbloque anidado | (o ||) termina y la ejecución vuelve a un nivel exterior con más ramas o sentencias debajo, |. (para |) o ||. (para ||) cierra explícitamente ese nivel anidado.
  • Son válidas tanto la colocación en línea al final de la última línea (| otherwise = val |., el estilo compacto canónico) como la colocación en la línea siguiente (|.):
fn :: evaluate_payment Account Customer Payment => String
      evaluate_payment account customer payment =
        | is_locked account   = "REJECTED_ACCOUNT_LOCKED"
        | amount payment <= 0 = "ERROR_INVALID_AMOUNT"
        | otherwise =
            | tier customer == "ENTERPRISE" =
                | amount payment > 50000 =
                    | has_dual_signature payment = "APPROVED_ENTERPRISE_DUAL_SIG"
                    | tenure customer > 10       = "RISK_COMMITTEE_REVIEW"
                    | otherwise                  = "REJECTED_ENTERPRISE_LIMIT" |.
                | country payment != "US" =
                    | is_whitelisted (country payment) = "APPROVED_ENTERPRISE_INTL"
                    | otherwise                        = "HELD_COMPLIANCE_INTL" |.
                | otherwise = "APPROVED_ENTERPRISE_STANDARD" |.
            | tier customer == "VIP" =
                | amount payment > 10000 =
                    | has_2fa payment = "APPROVED_VIP_2FA"
                    | otherwise       = "REQUEST_VIP_SMS" |.
                | otherwise = "APPROVED_VIP_DIRECT" |.
            | otherwise =
                | amount payment > 1000 =
                    | has_2fa payment and risk payment < 30 = "APPROVED_RETAIL_2FA"
                    | otherwise                             = "REJECTED_RETAIL_RISK" |.
                | otherwise = "APPROVED_RETAIL_LOW_AMOUNT"

fn :: charge_order Payment => Int
      charge_order payment =
        || fetch_base_fee payment     -> base_fee
        || compute_risk_score payment -> risk_score ||.
        surcharge =
          | risk_score > 80 = 50
          | otherwise       = 0 |.
        base_fee + surcharge

2. Inmutable por defecto (x = ...) frente a variables mutables explícitas (mut x = ...)

Dentro de los bloques secuenciales, las variables locales siguen la opción 1:

  • Vinculación inmutable (x = <rhs>):
  • Declara una nueva variable local inmutable x.
  • Intentar reasignar x = ... más adelante en el mismo ámbito sin una declaración previa mut x = ... es un error de compilación.
  • Declaración mutable (mut x = <rhs>) y reasignación (x = <new_rhs>):
  • mut x = <rhs> declara x como mutable en el ámbito actual y la inicializa.
  • Las reasignaciones posteriores a x en el mismo ámbito se escriben simplemente como x = <new_rhs> (sin repetir mut).
  • Con RMM2, reasignar x = <new_rhs> incrementa la estructura nueva (+1) antes de decrementar la antigua (-1, en cascada a sus hijos si su contador llega a 0).
fn :: accumulate Int => Int
      accumulate n =
        factor = 2
        mut acc = n * factor
        acc = acc + 10
        acc =
          | acc > 100 = 100
          | otherwise = acc
        acc

3. Pattern matching sobre literales y constructores de ADT

fn :: factorial Int => Int
      factorial 0   =  1
      factorial n   =  n * factorial (n - 1)

fn :: unwrap Result    => Int
      unwrap (Ok n)    =  n
      unwrap (Err msg) =  0

fn :: length List => Int
      length Empty         =  0
      length (Cons _ rest) =  1 + length rest

fn :: sum_tree Tree              => Int
      sum_tree (Leaf n)          =  n
      sum_tree (Node left right) =  sum_tree left + sum_tree right

Primitivas de paralelismo (||)

Ridgun unifica toda la ejecución paralela bajo || (where se ha eliminado):

Vinculaciones con nombre en un DAG paralelo (|| expr -> var)

fn :: compute_total Int Int => Int
      compute_total a   b   =  x + y + z
        || heavy_calc a     -> x
        || heavy_calc b     -> y
        || combine x y      -> z
  • Todas las líneas || expr -> var debajo de una expresión forman un grafo acíclico dirigido (DAG) paralelo.
  • El compilador analiza las dependencias de variables entre las líneas || (x e y no tienen dependencias y se ejecutan en paralelo en la oleada 1; z depende de x y de y y se ejecuta en la oleada 2, antes de evaluar x + y + z).
  • También se admiten escrituras paralelas en porciones de tensor (|| expr -> out[i, j]).

Bloques directos de tubería paralela (|| expr)

op :: run Conf and Conf in parallel => Res, Res
      run a    and b    in parallel =
        || run_process a
        || run_process b

Cada línea || se ejecuta como un green thread paralelo independiente, y los resultados se devuelven como una tupla en el orden de declaración (o se unen cuando el bloque está dentro de un bloque secuencial).

Canales con tipo

Para flujos de datos y topologías productor-consumidor entre green threads, Ridgun ofrece canales MPMC con tipo (Channel):

Función u operador nativoFirmaPropósito
new_channel() => ChannelCrea un canal con tipo sin límite
new_bounded_channelInt => ChannelCrea un canal limitado con capacidad de contrapresión
send_throughT Channel => ()Envía un valor a un canal (se bloquea si está limitado y lleno)
receive_fromChannel => TRecibe el siguiente valor de un canal
try_receive_fromChannel => OptionSondeo no bloqueante que devuelve Some v o None

Primitivas de E/S

Ridgun ofrece funciones nativas para la salida por terminal.

echo

Imprime en la terminal un número arbitrario de cadenas, separadas por espacios, seguidas de un salto de línea.

fn :: echo String... => ()

El sufijo ... en el último tipo de parámetro lo marca como variádico: acepta uno o más argumentos de ese tipo.

echo "hello"                            -- prints: hello
echo "hello" "world" "!"               -- prints: hello world !
echo "result: {result}"                 -- prints: result: 42

out

Un operador que escribe una cadena directamente en la terminal y la devuelve (lo que permite encadenar).

op :: String out => String
"hello" out                              -- prints: hello
"step 1 done" out                        -- prints: step 1 done

-- Chaining: out returns the string, so it can be passed onward
fn :: log_and_process String => String
      log_and_process s = process (s out) and report

Resumen

FunciónTipoPropósito
echoString... => ()Imprime cadenas con espacios y un salto de línea
outString => StringImprime una cadena y la devuelve para encadenar

Librería estándar

Las librerías se importan con import:

import test

Tests unitarios (lib/test.rg)

Una librería de tests sencilla que usa echo para la salida por terminal.

Aserciones:

OperadorPropósito
assert Int equals IntIgualdad de enteros
assert Real equals RealIgualdad de reales
assert String equals StringIgualdad de cadenas
assert Bool is trueEl booleano es verdadero
assert Bool is falseEl booleano es falso

Estructura de un test:

import test

fn :: main => ()
      main =
        describe "Arithmetic" does

          test "sum adds correctly" does
            assert (sum 3 4) equals 7

          test "factorial base case" does
            assert (factorial 0) equals 1

Salida por terminal:

═══ Arithmetic ═══
● sum adds correctly
  ✓ PASS
● factorial base case
  ✓ PASS

Resumen de elementos sintácticos

ElementoSintaxisPropósito
Comentario-- textComentario de una línea
Declaración de funciónfn :: name types => returnDeclara la firma de tipos de una función
Definición de funciónname params = bodyDefine la implementación de una función
Declaración de operadorop :: words types words => returnDeclara un operador en lenguaje natural
Definición de operadorwords params words = bodyDefine la implementación de un operador
Varios retornos=> type, typeDeclara varios valores de retorno
Parámetros variádicosType...Acepta uno o más argumentos de un tipo
Literal de tensorTensor [[1, 2], [3, 4]]Crea un tensor de N dimensiones
Indexación de tensort[0, 1] o t[0][1]Accede a los elementos de un tensor
Mutación de tensort[0, 1] = 5.0Modifica elementos de un tensor in-place
Contracción de EinsteinA_ij @ B_jkContracción de tensores sobre índices compartidos
Operación de HadamardA_ij * B_ijAritmética elemento a elemento con alineación de índices
echoecho "a" "b" "c"Imprime cadenas en la terminal
out"text" outEscribe una cadena en la terminal
Vinculación local inmutablex = exprVincula una variable local inmutable (= admite (none), |, ||)
Vinculación local mutablemut x = exprDeclara una variable local mutable (se reasigna con x = new_expr)
Bloque de guardas| cond = exprRama de guarda condicional después de = (admite | anidado)
Bloque de DAG paralelo|| expr -> varSubcálculos paralelos con resultados de DAG con nombre
Bloque de tubería paralela|| exprEjecución paralela que devuelve una tupla
Sobrecarga de operadoresop :: type + type => typeSobrecarga operadores existentes para tipos nuevos
Literales de BoolTrue, FalseValores booleanos primitivos
Operadores lógicosand, or, xor, notOperadores lógicos con palabras en minúsculas
Punto de entradafn :: main => ()Punto de entrada obligatorio del programa

Formato canónico del código fuente (stable format y herramientas de editor)

Solo estética, no es un requisito del compilador (FR-TOOL-001): el lexer y el parser de Ridgun no son sensibles a la alineación horizontal por columnas. Para ridgunc, los archivos fuente .rg sin alinear y alineados por columnas son 100% equivalentes. Las reglas siguientes definen la maquetación estética canónica que aplican stable format y el ridgun-mode de GNU Emacs (C-c C-f).

  1. Grupo de alineación por firma (fn :: / op ::):
  • Cada declaración fn :: u op :: inicia un grupo de alineación que abarca su firma, sus cláusulas de pattern matching y sus vinculaciones || expr -> var, y que termina en la siguiente declaración de nivel superior (fn, op, type, import, global, context, shared, hardware, @).
  1. Alineación por columnas de los tokens del lado izquierdo ($C_0 = 6$):
  • fn :: y op :: ocupan 6 caracteres, así que el token $0$ del lado izquierdo de la firma empieza en la columna $C_0 = 6$.
  • Cada hueco de token $i \in [0, N-1]$ antes de =>/-> (tratando "...", '...', [...] y (...) como tokens únicos) tiene un ancho $W_i = \max(\text{len}(\text{sig_tok}i), \max{\text{clause} : \text{len}(\text{clause_tok}_i) \le 18} \text{len}(\text{clause_tok}_i))$ y empieza en la columna $C_i = C_{i-1} + W_{i-1} + 1$. Los patrones de cláusula de más de 18 caracteres se tratan como valores atípicos: no amplían $W_i$ para el resto del grupo y simplemente se desbordan en su propia línea con 1 espacio.
  • Las cláusulas de definición correspondientes se sangran hasta la columna $6$ ($C_0$), de modo que el nombre de la función o las palabras del operador y cada patrón de argumento queden alineados verticalmente debajo de sus huecos de la firma.
  1. Alineación vertical de =>, = y -> ($C_{\text{arrow}}$):
  • Sean $C_{\text{arrow}} = \max\big(C_{N-1} + W_{N-1} + 1,; \max_{\text{pipe_bind}}(8 + 3 + \text{len}(\text{pipe_lhs}) + 1)\big)$ y $C_{\text{ret}} = C_{\text{arrow}} + \text{len}(\text{arrow}) + 1$.
  • En la firma, => se coloca en $C_{\text{arrow}}$ y el tipo de retorno en $C_{\text{ret}}$.
  • En las cláusulas de una línea (<lhs> = <rhs>), en las cláusulas de bloque de varias líneas (<lhs> =) y en las cláusulas encabezadas por guardas (<lhs> = seguido de | cond = ...), = se coloca en $C_{\text{arrow}}$ (alineado justo debajo de =>); en las cláusulas de una línea, <rhs> empieza en $C_{\text{ret}}$.
  • En las vinculaciones de DAG paralelo (|| <lhs> -> <rhs>), || empieza en la columna $8$ (6 + 2), -> se coloca en $C_{\text{arrow}}$ y <rhs> empieza en $C_{\text{ret}}$.
  • Alternativa compacta (> 4 parámetros o > 80 columnas): si una declaración tiene más de 4 huecos de parámetro o su línea de firma alineada superaría los 80 caracteres, se desactiva el relleno horizontal por columnas para ese grupo y los tokens se separan con un solo espacio (" "), manteniendo el sangrado de las cláusulas en la columna 6.
  1. Sangrado estructural (desplazamiento de 2 espacios):
  • Declaraciones de nivel superior (fn, op, type, import, global, context, shared, hardware, @): columna 0.
  • Cláusulas de función u operador bajo una firma: columna 6.
  • Guardas (| cond = expr) y tuberías paralelas (|| expr / || expr -> var): prev_indent + 2 (columna 8 dentro de una cláusula, o indent + 2 dentro de una vinculación local o de una guarda anidada).
  • Líneas del cuerpo de un bloque secuencial después de una línea que termina en =: prev_indent + 2.

Operaciones de colección de orden superior, secuenciales y paralelas (FR-COMP-001, FR-COMP-014)

Ridgun ofrece los combinadores de colecciones secuenciales habituales (map, filter, fold) junto con combinadores paralelos multicore explícitos con el prefijo || (||map, ||filter). Los cinco combinadores admiten tanto la sintaxis idiomática de tubería o infija de izquierda a derecha (xs map f -> filter g -> fold 0 h -> res) como la sintaxis prefija tradicional (map f xs):

NativaFormas prefija y de tuberíaModelo de ejecución
mapxs map f / xs -> map f / map f xsSecuencial: evalúa f x estrictamente de izquierda a derecha en el hilo que hace la llamada.
`map`
filterxs filter p / xs -> filter p / filter p xsSecuencial: evalúa el predicado p x estrictamente de izquierda a derecha en el hilo que hace la llamada.
`filter`
foldxs fold init f / xs -> fold init f / fold f init xsPlegado secuencial por la izquierda: acumula acc = f acc x de izquierda a derecha a partir de init.

E/S de archivos sin alteraciones, flujos de bytes y funciones POSIX nativas (FR-VM-001, FR-VM-002)

  1. E/S de archivos y flujos de bytes sin alteraciones (FR-VM-001):
  • read_file : String => String lee el contenido de un archivo tal cual (conserva \r\n y la presencia o ausencia exacta del salto de línea final, y nunca ejecuta el preprocesador de import de .rg del compilador sobre archivos de datos).
  • load_rg_source : String => String la usan exclusivamente las herramientas del compilador al cargar archivos fuente .rg con inserción de import.
  • read_bytes : String => [Int] y write_bytes : String [Int] => () leen y escriben flujos de bytes binarios en crudo (0..255, incluidos 0x00 NUL y 0x80..0xFF).
  1. Funciones nativas POSIX de sistema de archivos, procesos y seguridad (FR-VM-002):
  • Sistema de archivos: list_dir, file_lstat, sha256_file, sys_mkdir, sys_chmod.
  • Procesos y entorno: sys_exec, sys_chdir, sys_getcwd, sys_getenv, sys_setenv, sys_exit.
  • Seguridad y separación de privilegios: sys_getuid, sys_getgid, sys_username, sys_user_groups, sys_lookup_uid_gid, sys_setresgid, sys_setgroups, sys_setresuid, sys_execve, sys_read_pass.
  1. E/S PAL multiplataforma, sondeo, primitivas de procesos, ADT de destino y cuotas declarativas with timeout (FR-COMP-007):
  • ADT de plataforma de destino predeclarados:
  • type SysOs = Linux | MacOS | Windows (sys os => SysOs, abierto a extensiones con expand type SysOs = ...)
  • type SysArch = X86_64 | Arm64 (sys arch => SysArch, abierto a extensiones con expand type SysArch = ...)
  • Literales con unidad de duración: los literales enteros aceptan los sufijos _ms (* 1), _s (* 1000) y _min (* 60000) (por ejemplo, 500_ms, 5_s, 2_min), que complementan a los sufijos de unidad de bytes (_KB, _MB, _GB).
  • Capa 1 — Primitivas PAL multiplataforma para las librerías de sistema .rglib (posix.rglib / io.rglib):
  • sys os => SysOs y sys arch => SysArch (ADT suma del sistema operativo y la arquitectura de destino en tiempo de compilación)
  • sys open mode String String => Int (path, modo portable "r", "w", "a", "rw", "w+", "a+" -> descriptor o handle >= 0 o -errno, traducido para cada sistema operativo de destino)
  • sys open String Int Int => Int (path, flags normalizados de la PAL de Ridgun (0 = lectura, 1 = escritura, 2 = rdwr, 64 = crear, 512 = truncar, 1024 = añadir al final, 2048 = no bloqueante), mode -> fd >= 0 o -errno)
  • sys close Int => Int (fd -> 0 o -errno)
  • sys read fd Int Int => String (fd, max_bytes -> cadena leída de fd)
  • sys write fd Int String => Int (fd, data -> bytes escritos o -errno)
  • sys poll Int Int Int => Int (fd, events normalizados de la PAL de Ridgun (1 = POLLIN/lectura, 4 = POLLOUT/escritura), timeout_ms -> > 0 con los revents cuando está listo, 0 si se agota el tiempo, < 0 con -errno; usa sys remaining ms cuando timeout_ms < 0 dentro de with timeout)
  • sys terminate Int Bool => Int (pid, force -> terminación de procesos portable, ordenada o forzada) y sys kill Int Int => Int (pid, señal normalizada -> 0 o -errno)
  • sys call Int Int Int Int Int Int Int => Int (nr, a1..a6 -> vía de escape de bajo nivel a las syscalls según la ABI del sistema operativo de destino)
  • sys time ms => Int y sys sleep ms Int => () (reloj monotónico en milisegundos y espera)
  • sys deadline ms => Int y sys remaining ms => Int (plazo léxico activo y milisegundos restantes, o -1 cuando no hay límite)
  • Capa 2 — Cuota de ejecución declarativa with timeout <duration> do:
  • Igual que with region 64_KB do establece una cuota léxica de bytes, with timeout 500_ms do (o la forma combinada with region 64_KB, timeout 500_ms do) establece un plazo léxico de ejecución (sys_time_ms + duration_ms) en el contexto del hilo actual y restaura el plazo anterior al salir del bloque:
       with timeout 500_ms do
         rem = sys remaining ms
         rc  = sys poll fd 1 rem
         rc
  1. Compilación de librerías reutilizables (.rglib y .so ELF64) y stable extensible (FR-COMP-006, FR-TOOL-003):
  • ridgunc build --lib <file.rg> -o <out.rglib> y ridgunc build --so <file.rg> -o <lib.so> (o stable built lib / stable new --lib <name>) compilan módulos sin necesidad de fn :: main => () y conservan todas las funciones, operadores y tipos exportados.
  • stable descubre comandos personalizados fn :: task <words...> => String y extensiones fn :: plugins => [String] (plugins/*.rg, .stable/plugins/*.rg), que se listan con stable tasks y stable plugins.

Gestión de memoria de Ridgun (RMM): RMM1 (CMM) y RMM2 (FR-COMP-002)

Ridgun gestiona la memoria del heap (String, [a], (a, b), ADT, Tensor) de forma determinista, sin un recolector de basura de rastreo, mediante Ridgun Memory Management (RMM):

  1. Política RMM1 — Gestión de memoria compilada (CMM lineal + condicional):
  • Se aplica cuando el flujo de control y las funciones llamadas se conocen estáticamente en tiempo de compilación sobre vinculaciones no anidadas:
  • CMM lineal (rg_cmm_move / rg_cmm_drop): el compilador determina en tiempo de compilación la sentencia exacta del último uso de cada variable local, mueve las variables devueltas (rg_cmm_move(&mut lv_x)) y emite una liberación incondicional (rg_cmm_drop(&mut lv_x)) justo después del último uso de una variable cuando no queda vivo ningún posible alias.
  • CMM condicional (rg_cmm_drop_unless / ptr(a) != ptr(c)): cuando una llamada con ramificación estática (c = choose_list cond a b) devuelve una de varias entradas candidatas y una o más candidatas siguen vivas junto a c, el compilador construye la tabla de posibles alias de ancestros (RCmmAlias) y emite una guarda de identidad de punteros de 1 ciclo en la sentencia estática del último uso de cada variable:
       a = make_a ()               -- Allocates M1
       b = make_b ()               -- Allocates M2
       c = choose_list cond a b    -- b dies here while c is live -> if ptr(b) != ptr(c) { free(b) }
       print_list a                -- a dies here while c is live -> if ptr(a) != ptr(c) { free(a) }
       consume_list c              -- c dies here (no live peers) -> free(c) (unconditional)
  1. Política RMM2 — Conteo por asignación y cascada estructural (política activa):
  • Unifica la gestión de memoria entre funciones, retornos con ramificación (choose_list), contenedores anidados (xs = [a, b, ..., z], xs[3] = a), variables locales mutables (mut x = ...) y la ejecución multihilo:
  • Escalares primitivos (Int, Real, Bool, ()): nunca se cuentan; se copian por valor en la pila o en los registros.
  • Argumentos de entrada de la función (pats): nunca se tocan (+0 al entrar, -0 al salir).
  • Variables locales internas (x = ... / mut x = ...): reciben +1 en la asignación (x = expr, xs[3] = a). En la reasignación mutable (x = new_expr), new_expr recibe +1 antes de que la antigua x reciba -1.
  • Fin de la función o salida del ámbito:
  • Las variables locales internas no devueltas reciben primero -1: si rc == 0, se libera el bloque padre y se propaga -1 en cascada, uno por uno, a cada estructura hija directa (liberando de forma recursiva cualquier hija cuyo rc llegue a 0).
  • Una variable local interna devuelta (return a) recibe -1 (o se mueve mediante rg_cmm_move), y aunque su rc sea 0 en tránsito, se libra, de modo que la asignación de quien llama (c = make_pair ()) suma +1 (0 + 1 = 1). Cuando se devuelve un argumento de entrada (choose_list cond a b), no se ha tocado dentro de la función (rc = 1), así que c = choose_list cond a b en quien llama suma +1 (1 + 1 = 2, compartido entre a y c).
  1. Política RMM3 (reservada):
  • Reservada para una futura política de optimización estática agresiva.
  • Consulta docs/etc/cmm_architecture.md (en el repositorio de Ridgun) para ver la especificación completa de RMM1 y RMM2.