Saltar ao contido
Ridgun
Galego

Referencia

Especificación da linguaxe Ridgun

Visión xeral

Ridgun é unha linguaxe funcional, pensada desde o inicio para o paralelismo, con operadores en linguaxe natural e tipado estático forte. A súa sintaxe inspírase nas linguaxes da familia de Haskell e ML, e introduce construcións novas para o paralelismo e para definir operadores que se len con facilidade.

Características principais:

  • Operadores en linguaxe natural — Os operadores lense como frases en inglés
  • Paralelismo implícito — A execución paralela é unha primitiva sintáctica, non unha chamada a unha biblioteca
  • Tipado estático forte — Todos os tipos se declaran nas sinaturas
  • Definicións por patróns — As funcións e os operadores defínense mediante ecuacións

Comentarios

Os comentarios dunha soa liña usan --:

-- This is a comment

Cadeas

Ridgun ten dous tipos de literal de cadea, que se comportan de xeito distinto coa interpolación.

Cadeas interpoladas (comiñas dobres)

As cadeas entre comiñas dobres admiten interpolación de código en liña con {expr}. A expresión entre chaves avalíase e o seu resultado insírese na cadea.

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

Para incluír unha chave literal dentro de "...", escápaa cunha barra invertida (\{ e \}) ou duplícaa ({{ e }}):

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

Cadeas crúas (comiñas simples)

As cadeas entre comiñas simples son crúas: non se fai ningunha interpolación. Todos os caracteres se tratan como texto literal, incluídas as chaves.

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

Resumo

SintaxeInterpolaciónUso
"..."Si — {expr} (escapa as chaves con \{/\} ou {{/}})Cadeas dinámicas con expresións incrustadas
'...'Non — todo é literalTexto estático, modelos, patróns de expresións regulares

Estrutura do programa

Todo o código executable de Ridgun debe estar dentro dunha función ou dun operador. A función main é o punto de entrada obrigatorio para executar o programa.

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

Un ficheiro fonte de Ridgun só pode conter:

  • instrucións import
  • definicións type
  • declaracións e definicións fn e op
  • unha función main como punto de entrada

Non se permiten expresións soltas no nivel superior. Todo o que se executa ten que chamarse desde main.

Uso da liña de comandos

ridgunc <command> [options] <file.rg>
OrdeFunción
runCompila e executa un ficheiro fonte
checkComproba os tipos e analiza sen executar
compileXera un ficheiro de bytecode .rgc
buildCrea un executable nativo; unha biblioteca con --lib ou --so
versionMostra a versión do compilador
helpMostra a axuda

O formato, as probas e os proxectos corresponden a stable.


Funcións e operadores

As funcións e os operadores en linguaxe natural ou infixos decláranse cunha sinatura de tipos que remata en => <return_type>, seguida dunha ou máis ecuacións (cases). Os prefixos iniciais fn :: e op :: son opcionais:

  • Forma canónica sen prefixo (comeza na 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 prefixo (fn :: / op ::, comeza na columna 0 coas cláusulas aliñadas na 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}"
  • Espazos de linguaxe natural en fn ::, funcións integradas e nomes de ficheiro (FR-COMP-005):
  • As declaracións e as chamadas fn :: admiten nomes de varias palabras separadas por espazos (fn :: format app err AppErr => String, fn :: project name => String), igual que op :: e as declaracións sen prefixo.
  • As funcións integradas do runtime e de probas pódense chamar con espazos (assert equals a b, str len s, read file p, cmm live blocks), e as formas con _ seguen sendo válidas por compatibilidade.
  • As referencias de primeira clase a funcións de varias palabras (map double val xs, ||map double val xs, filter is even xs, fold add pair 0 xs) e os nomes de ficheiro .rg con espazos (src/test/ridgun/test core language.rg) están admitidos de forma nativa en ridgunc e stable.

Variables de tipo polimórficas (A..Z) e palabras chave contextuais (type, with)

Para garantir que non haxa ningunha ambigüidade léxica entre as palabras dos operadores e os parámetros de tipo, tanto nas sinaturas con prefixo (op ::) como nas sen prefixo:

  • Todo elemento de tipo nunha sinatura comeza por letra maiúscula (ou por [...] / (...)):
  • As letras maiúsculas soltas (A..Z) denotan variables de tipo polimórficas (por exemplo, [A], (A, B), (A => B), swap A with B => (B, A)). As aparicións repetidas dunha variable de tipo entre parámetros unifícanse en cada punto de chamada e substitúense no tipo de retorno.
  • Os identificadores de varios caracteres con maiúscula inicial (Int, String, Bool, RExpr, Tensor) denotan tipos concretos ou definidos polo usuario.
  • Todo elemento en minúsculas (ou símbolo de operador +, -, *, /, %) nunha sinatura de nivel superior forma parte do nome da función ou do operador mixfix.
  • Palabras chave contextuais type e with: type só introduce unha declaración de tipo no nivel superior cando vai seguida de UpperName =. En calquera outro lugar (sinaturas fn :: e op ::, palabras de operadores, patróns de parámetros, variables locais e nomes de campos de rexistro), type e with son identificadores ou palabras válidos, sen ambigüidade.
  • Unha sinatura de nivel superior con un único identificador inicial en minúsculas seguido só de tipos antes de => é unha función prefixa (fn); calquera sinatura con dúas ou máis palabras en minúsculas ou símbolos de operador intercalados con tipos antes de => é un operador mixfix, infixo ou postfixo (op).

A declaración e a definición van separadas:

  • Declaración (fn ::) — indica o nome, os tipos dos parámetros e o tipo de retorno
  • Definición — liga os nomes dos parámetros e proporciona a implementación

Operadores

Os operadores son o trazo distintivo de Ridgun. Nómeanse en linguaxe natural e defínense con op ::. A diferenza dos operadores tradicionais (+, -), os de Ridgun son expresións de varias palabras que se len como frases.

Sintaxe

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

Exemplo básico

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

O uso lerase así: add 3 to 5 → 8

As palabras entre parámetros tipados (to, and, in, etc.) son palabras chave do operador: forman parte da súa identidade e teñen que aparecer nos puntos de chamada.

Operadores paralelos (||)

Os operadores poden expresar a execución paralela como un concepto de primeira clase usando ||:

-- 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 dous valores de retorno
  • Bloque || expr -> var — Liga subcálculos a nomes de resultado (where elimínase; || unifica todas as ramas paralelas)
  • -> — Asigna o resultado dun subcálculo paralelo a unha variable (ou a unha porción de tensor, -> out[i, j])
  • As ramas || independentes execútanse concorrentemente como un DAG paralelo baseado nas dependencias de datos

Bloque de tubaxe paralela directa (|| expr)

Cando unha expresión ou función devolve directamente unha tupla de cálculos paralelos sen nomes de variable intermedios, as liñas || aparecen xusto despois do =:

-- 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 liña || execútase como unha tarefa paralela independente. Os resultados recóllense na orde de declaración.

Sobrecarga de operadores

Os operadores existentes (incluídos os integrados como +) pódense sobrecargar para tipos novos:

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

Precedencia

Os operadores simbólicos teñen a maior precedencia: primeiro as matemáticas.

Cando aparecen operadores simbólicos (como +, *) dentro do corpo de operadores en linguaxe natural, ligan con máis forza que as palabras chave do operador que os rodea. Así, as expresións aritméticas avalíanse como se espera, sen parénteses adicionais.

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

Encadeamento de operadores e tubaxes idiomáticas de esquerda a dereita (->) (FR-COMP-014)

Os operadores e as funcións que transforman valores ou secuencias pódense encadear de esquerda a dereita, de xeito que os datos flúan con naturalidade da orixe ao destino sen parénteses aniñadas de dereita a esquerda (h (g (f x))) nin variables intermedias desbotables (st1, st2, st3).

1. O estilo de tubaxe idiomático recomendado en Ridgun (2b)

Ridgun unifica a invocación infixa co operando á esquerda e as etapas explícitas de tubaxe -> na mesma AST. Aínda que as catro formas de abaixo son válidas e equivalentes, a forma 2b (comezar de xeito natural cun suxeito e unha operación á esquerda, encadear as transformacións seguintes con -> e ligar o resultado final con -> target) é o 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

As tubaxes de varias etapas tamén se poden escribir en liñas de continuación sangradas que comezan con ->:

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

2. Regra universal do primeiro argumento de -> e combinadores de coleccións

  • Regra universal para funcións e operadores: calquera valor que veña da esquerda de -> (ou da esquerda dunha chamada infixa x op a b) entra como primeiro argumento da función ou do operador da dereita:
  • x -> f a b $\equiv$ x f a b $\equiv$ f x a b
  • Funciona sen problemas tanto coas declaracións fn :: como coas declaracións mixfix op ::, sen duplicar sinaturas.
  • Se unha función posterior precisa o valor da tubaxe nun parámetro que non sexa o primeiro, un marcador explícito _ coloca o valor en _ (payload -> write_file output_path _).
  • Combinadores de coleccións (map, ||map, filter, ||filter, fold): aceptan a colección tanto na posición prefixa tradicional (map f xs, filter g xs, fold f init xs) como á esquerda dunha etapa infixa ou de tubaxe (xs map f, xs -> map f, xs filter g, xs -> filter g, xs fold init f, xs -> fold init f).
  • Etapas de axente (-> send to, -> ask to): ao enviar a tubaxe a -> send to ag ou -> ask to ag, o valor da esquerda pásase como mensaxe ([5, 10, 15] -> sum int list -> send to worker_ag).
  • Ligazón a unha variable terminal (-> var): cando unha instrución de tubaxe dun bloque secuencial remata en -> var (onde var é o nome dunha variable local e non unha función declarada), liga var (var = <pipeline_result>) para o resto do bloque.

3. Máscaras de secuencia en tubaxes (-> (+ _ _ +) ->)

Cando unha etapa devolve unha secuencia ou tupla de varios valores, unha etapa de máscara de secuencia (-> (+ _ _ +) ->) filtra os seus elementos (+ conserva unha posición, _ descártaa) e desempaqueta a tupla seleccionada directamente nunha función posterior de varios argumentos:

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

4. Instrucións con efectos secundarios sen _ = e descarte explícito de unit (-> ())

Nos bloques secuenciais, as instrucións con efectos secundarios (echo, write_file, write_bytes, shell_exec, log info, print_error_lines, sys_mkdir, etc.) escríbense directamente, sen a repetición de _ =:

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

Cando a expresión final dunha función => () chama a unha función auxiliar que devolve un valor distinto de () (como shell_exec : String => Int), engade -> () para avaliar a expresión, descartar o seu valor de retorno e producir ():

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

5. Encadeamento point-free de operadores de secuencia

Os operadores que devolven secuencias tamén se poden encadear en estilo point-free:

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: tanto -> como => se aceptan como separadores do tipo de retorno nas cabeceiras fn :: / op ::. Nas expresións, -> é o operador de tubaxe e de ligazón de esquerda a dereita, mentres que => introduce clausuras léxicas (x => expr).


Sistema de tipos

Sinaturas de tipos

As sinaturas de tipos aparecen nas declaracións despois de fn :: ou op :: (ou sen prefixo):

fn :: <name> <type1> <type2> ... => <return_type1> <return_type2> ...
op :: <words and types mixed> => <return_type1> <return_type2> ...
  • Os parámetros son posicionais; os tipos aparecen onde se esperan os parámetros.
  • Sintaxe unificada de tipos de tupla e secuencia (FR-COMP-008):
  • En Ridgun, unha lista de argumentos T1 T2 ... Tn e unha tupla (T1, T2, ..., Tn) representan o mesmo tipo composto subxacente: unha secuencia ordenada e de tamaño fixo de valores tipados (seq).
  • Polo tanto, os tipos separados por espazos e os tipos de tupla separados por comas son 100 % intercambiables nos tipos de retorno, nos corchetes de lista e nas tuplas entre parénteses:
  • Tipos de retorno despois 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énteses ((T1 T2 ... Tn)):
      fn :: fst (A B) => A
            fst (a, _) =  a
  • Por que isto non ten ningunha ambigüidade sintáctica: a diferenza de Haskell/ML (onde a xustaposición Maybe Int denota aplicación de tipo), en Ridgun os tipos parametrizados usan parénteses explícitas (Tensor(Float, 3, 3)) e as listas usan corchetes ([T]). Ademais, todo elemento de tipo comeza por letra maiúscula (A..Z, Int, String), [ ou (, mentres que as palabras de funcións e operadores van en minúsculas. Por iso, T1 T2 nunca entra en conflito con construtores de tipo nin con palabras chave de operadores.
  • A frecha => (ou ->) separa os tipos de entrada do tipo de retorno.

Tipos integrados

TipoDescrición
IntEnteiro
RealNúmero real
BoolBooleano
CharUn só carácter Unicode (UTF-8)
StringCadea de texto (UTF-8)
ComplexNúmero complexo
TensorTensor N-dimensional mutable (elementos Real, CPU)
Tensor_GPUTensor N-dimensional mutable (elementos Real, GPU)

[!NOTE] Todo o texto en Ridgun está codificado en UTF-8 por defecto. Char representa un único punto de código Unicode, e String é unha secuencia de Char.

Literais de Bool

Bool ten dous valores primitivos:

True                                     -- boolean true
False                                    -- boolean false

True e False son construtores: pódense usar en expresións, pasar como argumentos e usar en pattern matching:

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

Operadores lóxicos

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

OperadorSignificadoExemplo
andConxunción lóxicaa and b
orDisxunción lóxicaa or b
xorOU exclusivoa xor b
notNegación lóxicanot a
result = True and False                  -- False
result = True or False                   -- True
result = True xor True                   -- False
result = not False                       -- True

Son operadores infixos de palabra chave coa semántica booleana habitual.

Tipos de colección nativos

Listas

As listas son un tipo recursivo integrado e inmutable con sintaxe propia. Non fai falta definir ningún ADT á man. As listas son axeitadas para o procesamento funcional de datos, o cálculo simbólico e os algoritmos recursivos. Para o cálculo numérico mutable, consulta Tensores máis abaixo.

Sintaxe de tipo: [type] — unha lista de elementos do tipo indicado.

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

Sintaxe literal: [elem1, elem2, ...] — un literal de lista. [] é a lista baleira.

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

Pattern matching con cons: (x:xs) — descompón unha lista na súa cabeza x e na súa cola xs. [] coincide coa lista baleira. Usa _ como comodín que coincide pero non liga ningún valor.

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 dúas listas (ou secuencias).

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

Tensores

Os tensores son o tipo numérico N-dimensional mutable nativo de Ridgun. Xeneralizan vectores, matrices e arrays de máis dimensións nun único tipo. Os elementos son Real por defecto.

Sintaxe literal: Tensor seguido de corchetes aniñados. A profundidade do aniñamento determina a dimensionalidade.

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: admítense tanto a indexación encadeada como a 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): os tensores son mutables: os elementos e as porcións pódense modificar in-place (ao estilo de PyTorch/NumPy).

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

Contracción de tensores (@): o operador @ realiza a suma de Einstein. Os subíndices de cada operando definen a contracción: os índices compartidos por todos os operandos súmanse; os únicos sobreviven no 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)

A contracción funciona con varios tensores nunha soa 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)

Operacións de Hadamard (+, -, *, /): os operadores aritméticos con subíndices realizan operacións elemento a elemento. Os índices coincidentes aliñan dimensións; os non coincidentes fan 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

Isto elimina a necesidade de unsqueeze, expand_dims ou de cambiar a forma á man: os índices son as regras de broadcasting.

Variante para GPU: Tensor_GPU coloca o tensor na memoria da GPU para acelerar o cálculo.

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

Secuencias (seq)

As secuencias son o tipo composto central de Ridgun. Un seq é unha colección ordenada e de tamaño fixo de valores: en esencia, unha tupla tipada. As listas de parámetros de funcións e operadores son implicitamente secuencias.

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

Ligazón de secuencia ((name)) — envolver o nome dun parámetro entre parénteses liga todos os parámetros como unha única variable seq:

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 por máscara (s(+ _)) — extrae elementos mediante unha máscara, onde + selecciona unha posición e _ sáltaa:

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

Selector por índice (s(0 3)) — extrae elementos polas súas posicións, contadas desde cero:

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

Unión plana (:) — concatena dúas secuencias nunha única secuencia plana:

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

Aniñamento (,) — compón secuencias como elementos distintos (secuencia de secuencias):

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

Tipos de datos alxébricos (ADT)

Ridgun usa tipos de datos alxébricos para os tipos definidos polo usuario. Non hai clases, métodos nin bloques impl: as funcións e os operadores reciben os tipos como parámetros.

Tipos produto (rexistros) e sintaxe de actualización de rexistros (with)

Un tipo con campos con nome:

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

Aos campos accédese polo nome, como funcións (host c, port c), e actualízanse de forma funcional con with e dous 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 construtores, separados por |:

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

ADT suma xenéricos e rexistros produto xenéricos (FR-COMP-013)

Os ADT suma e os rexistros produto definidos polo usuario poden declarar un ou máis parámetros de tipo en maiúsculas (A..Z ou nomes de variables de tipo con maiúscula inicial) entre o nome do tipo e =:

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
  • As aplicacións de tipos paramétricos (Result Int String, Page User, [Box Int]) pódense usar en sinaturas fn :: / op ::, en campos de rexistro aniñados e nos datos que levan os construtores de ADT.
  • Os construtores (Ok 42, Ok "hello", Page [1, 2] 2), o pattern matching, os accesores de campo (items p, total p) e as actualizacións funcionais de rexistros (p with total = 10) instancian variables de tipo novas en cada punto de chamada.
  • type Option A = Some A | None está rexistrado de antemán como ADT polimórfico integrado cando o usuario non o declarou, así que try receive from ag devolve Some v ou None sen necesidade de declaracións repetitivas (FR-COMP-013).

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

As funcións anónimas (lambdas) escríbense entre parénteses, con parámetros separados por espazos seguidos de => e da expresión do corpo ((x => expr) ou (a b => expr), por exemplo map (x => x + offset) xs, fold (acc x => acc + x) 0 xs), e poden capturar variables locais ou parámetros do seu ámbito léxico envolvente:

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

Cinco regras canónicas rexen as funcións anónimas:

  1. Parénteses obrigatorias: toda lambda debe ir entre parénteses (... => ...). Escribir x => expr sen parénteses (como f = x => x + 1) é un erro de compilación (lambda expression must be enclosed in parentheses: '(x => expr)').
  2. Lambdas sen parámetros ((=> expr)): Ridgun prohibe () antes de => nas sinaturas de función (fn :: main => ()). En coherencia con iso, unha lambda sen parámetros escríbese (=> expr) (invócase como thunk ()), e o seu tipo nas sinaturas é (=> Type). Escribir (() => expr) ou (() => Type) é un erro de compilación.
  3. Varios parámetros fronte a desestruturación de tuplas ((a b => expr) fronte a ((a b) => expr)): (a b => expr) é unha lambda de 2 parámetros (como fold (acc x => acc + x) 0 xs), mentres que ((a b) => expr) é unha lambda de 1 parámetro que desestrutura unha tupla (map ((a b) => a + b) [(1, 2), (3, 4)]). Confundilas produce un erro de tipos específico que explica a diferenza.
  4. Corpos de lambda de varias liñas: cando => está ao final da liña, o bloque sangrado de debaixo forma un corpo de varias instrucións (ou de gardas |) que se pecha con ):
   map (x =>
     doubled = x * 2
     doubled + offset
   ) [1, 2, 3]
  1. Tipos en maiúsculas fronte a valores en minúsculas: (Conn => Conn) é un tipo de función e (conn => conn) é un valor lambda. Escribir tipos en maiúsculas (Conn => Conn) nunha expresión ou parámetros en minúsculas (conn => conn) nunha sinatura fn :: recházase cun erro de compilación claro.

As ligazóns capturadas elévanse automaticamente a clausuras de aplicación parcial xestionadas por RMM1/RMM2.

Tipos recursivos

Os tipos poden referirse a si mesmos:

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

Nomes de construtor compartidos entre tipos (FR-COMP-005)

Varias declaracións type poden reutilizar os mesmos nomes de construtor de variante (como Error, Var, Lit, None) coa mesma aridade ou con outra distinta. O verificador de tipos desambigua os patróns de construtor mediante o tipo de parámetro esperado da columna, e resolve as chamadas a construtores pola aridade e os tipos dos argumentos e polos tipos de retorno 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

Extensións abertas de tipos (expand type) e fusión automática de cláusulas fn :: (FR-COMP-005)

Os ADT suma e os rexistros produto existentes pódense estender con expand type UpperName = ...:

  • Estender un ADT suma: engade novos construtores de variante ao tipo existente.
  • Estender un rexistro produto: engade novos campos con nome (field: Type) e xera automaticamente as súas funcións de acceso.
  • Variantes con nome de tipo sen nomes de construtor redundantes: nas declaracións suma de type e expand type, unha variante cuxo nome é un tipo integrado sen máis (String, Int, Real/Float, Bool, Char), sen argumentos de tipo adicionais detrás, representa directamente valores dese tipo (sen escribir Int Int nin String String). Quen chama pode pasar valores sen máis (describe "hej", describe 42) ou chamadas explícitas ao construtor (describe (String "hej"), describe (Int 42)), e as ecuacións poden coincidir tanto con literais ("admin", 0) como con patróns tipados ((String s), (Int n)).
  • Fusión automática de cláusulas fn :: / op :: (sen expand fn): volver declarar un fn :: ou op :: existente con novas cláusulas de patrón fusiona automaticamente esas cláusulas na definición existente da función, e coloca as cláusulas de patrón específicas de construtor ou de literal antes de calquera cláusula xeral de reserva (_ / 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

Espazos de nomes de módulo, acceso cualificado e visibilidade estrita das importacións por módulo (FR-COMP-012)

Todo ficheiro .rg ou paquete .rglib define un espazo de nomes de módulo (derivado do nome base do ficheiro ou dunha cabeceira explícita module :: <name>) e pode importar outros módulos mediante import <mod> ou import <mod> as <alias>.

  1. Módulos de varios ficheiros (module :: <name>):
  • Varios ficheiros que declaran a mesma cabeceira module :: <name> (por exemplo, orm.rg e orm_changeset.rg, que comezan os dous con module :: orm) comparten un único espazo de nomes lóxico orm e ven directamente os tipos, construtores, campos de rexistro e funcións dos outros.
  1. Acceso cualificado (mod.Type, mod.Constructor, mod.field, mod.fn):
  • Os tipos exportados, os construtores de ADT suma, os accesores de campo de rexistros produto, as actualizacións de rexistros (with) e as funcións pódense referenciar cun prefixo <mod_or_alias>. (http.Config, db.Status, http.Ok 200, http.port cfg, http.check status cfg).
  1. Resolución estrita de nomes sen cualificar por módulo, en 3 niveis:
  • Cada módulo M (incluído o módulo raíz da aplicación) resolve os tipos, construtores, campos de rexistro, funcións e chamadas de varias palabras sen cualificar estritamente segundo o grafo de importacións do propio M, en tres niveis de prioridade:
  • Nivel 1 — Módulo propio (M): as declaracións do propio M (en todos os ficheiros que comparten module :: M) teñen sempre a prioridade máis alta.
  • Nivel 2 — Importacións directas de M: se M non define o nome, ridgunc busca nos módulos que M importa directamente (import D). Se unha soa importación directa D define o nome, resólvese sen ambigüidade á declaración de D, aínda que outros módulos da unidade de compilación (dependencias transitivas, módulos irmáns ou a aplicación raíz) tamén definan o mesmo nome. Se dúas ou máis importacións directas de M definen o mesmo nome e M non o define, as referencias sen cualificar recházanse por ambiguas (__ambiguous__.<name>) e deben cualificarse (d1.<name> fronte a d2.<name>).
  • Nivel 3 — Importacións transitivas de M: só se consultan cando nin o Nivel 1 nin o Nivel 2 definen o nome. En consecuencia, unha importación directa en M (import json) oculta limpamente unha función auxiliar transitiva (codec.json_get_str, que solstice importa internamente) sen obrigar a solstice nin a M a renomear ou cualificar as súas chamadas.
  • Illamento respecto de módulos irmáns non importados e da aplicación raíz: as declaracións definidas só en módulos que M non importou (nin directa nin transitivamente), incluídas as bibliotecas irmás importadas pola aplicación raíz ou as declaracións da propia aplicación raíz ao compilar unha biblioteca importada M, son invisibles para a busca sen cualificar dentro de M (__unimported__.<name>).

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

Toda definición ou ligazón en Ridgun, xa sexa unha cláusula de función (fn ::), unha cláusula de operador (op ::), unha variable local inmutable (x =), unha variable local mutable (mut x =) ou unha rama de garda (| cond = / | otherwise =), remata con = e acepta exactamente as mesmas catro formas de bloque ou expresión no lado dereito:

Prefixo despois de =Semántica de execuciónSignificado
<expr> en liñaExpresión única (con liñas opcionais || expr -> var debaixo)Avalía <expr> directamente (resolvendo as ligazóns do DAG paralelo || que haxa debaixo).
(none) (liñas sangradas)Secuencial (de arriba abaixo)Avalía as instrucións por orde (mut, asignacións, efectos secundarios); a última expresión é o resultado.
| <cond> = ...Condicional (gardas e árbores de decisión aniñadas)Avalía as condicións booleanas de arriba abaixo ata a primeira rama True (ou | otherwise =).
|| <expr>Paralela (DAG ou tupla con green threads)Avalía as ramas || concorrentemente (|| expr -> var para nodos do DAG con nome, || expr para resultados en tupla paralela).

1. As gardas (|) sempre seguen a = (funcións, operadores, variables locais e árbores de decisión aniñadas)

Os bloques de gardas (| cond = ... e | otherwise = ...) sempre aparecen detrás dun =, o que unifica as cláusulas de función, as cláusulas de operador, as definicións de variables locais e as árbores condicionais aniñadas:

Cláusulas de función e de operador con gardas

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 locais con gardas

Como | é válido despois de calquera =, as variables locais pódense ligar de forma condicional sen necesidade dunha palabra chave if/else nin dunha función auxiliar:

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

Bloques secuenciais de varias liñas, árbores de decisión profundas e peches aniñados (|. / ||.)

Cada rama de garda remata con =, así que o seu corpo pode ser á súa vez un bloque secuencial ((none)), un bloque paralelo (||) ou outro bloque de gardas | aniñado, o que permite árbores de decisión arbitrarias (de 3 ou 4 niveis, ou máis) dentro de | cond = e | otherwise =.

  • Peche de niveis aniñados (|. e ||.):
  • Nos bloques simples dun só nivel ou ao final dunha función ou árbore, non fai falta ningún elemento de peche.
  • Cando remata un subbloque | (ou ||) aniñado e a execución volve a un nivel exterior onde seguen máis ramas ou instrucións debaixo, |. (para |) ou ||. (para ||) pecha explicitamente ese nivel aniñado.
  • Son válidas tanto a colocación en liña ao final da última liña (| otherwise = val |., o estilo compacto canónico) como a colocación na liña seguinte (|.):
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. Inmutables por defecto (x = ...) fronte a variables mutables explícitas (mut x = ...)

Dentro dos bloques secuenciais, as variables locais seguen a Opción 1:

  • Ligazón inmutable (x = <rhs>):
  • Declara unha nova variable local inmutable x.
  • Tentar reasignar x = ... máis adiante no mesmo ámbito sen unha declaración previa mut x = ... é un erro de compilación.
  • Declaración mutable (mut x = <rhs>) e reasignación (x = <new_rhs>):
  • mut x = <rhs> declara x como mutable no ámbito actual e inicialízaa.
  • As reasignacións posteriores de x no mesmo ámbito escríbense simplemente x = <new_rhs> (sen repetir mut).
  • Con RMM2, reasignar x = <new_rhs> incrementa a nova estrutura (+1) antes de decrementar a antiga (-1, en fervenza cara aos seus fillos se o seu contador chega 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 literais e construtores 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 a execución paralela baixo || (where elimínase):

Ligazóns con nome en 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 as liñas || expr -> var baixo unha expresión forman un Grafo Acíclico Dirixido (DAG) paralelo.
  • O compilador analiza as dependencias de variables entre as liñas || (x e y non teñen dependencias e execútanse en paralelo na Onda 1; z depende de x e y e execútase na Onda 2, antes de avaliar x + y + z).
  • Tamén se admiten escrituras paralelas en porcións de tensor (|| expr -> out[i, j]).

Bloques de tubaxe paralela directa (|| expr)

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

Cada liña || execútase como un green thread paralelo separado, e os resultados devólvense como unha tupla na orde de declaración (ou agárdase por eles cando o bloque está dentro dun bloque secuencial).

Canles tipadas

Para fluxos de datos e topoloxías produtor-consumidor entre green threads, Ridgun ofrece canles MPMC tipadas (Channel):

Función ou operador integradoSinaturaFinalidade
new_channel() => ChannelCrea unha canle tipada sen límite
new_bounded_channelInt => ChannelCrea unha canle acoutada cunha capacidade que aplica contrapresión
send_throughT Channel => ()Envía un valor a unha canle (bloquéase se está acoutada e chea)
receive_fromChannel => TRecibe o seguinte valor dunha canle
try_receive_fromChannel => OptionConsulta sen bloqueo que devolve Some v ou None

Primitivas de E/S

Ridgun ofrece funcións integradas para a saída por terminal.

echo

Escribe na terminal un número arbitrario de cadeas, separadas por espazos e seguidas dun salto de liña.

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

O sufixo ... no tipo do último parámetro márcao como variádico: acepta un ou máis argumentos dese tipo.

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

out

Un operador que escribe unha cadea directamente na terminal e a devolve (o que permite encadealo).

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

Resumo

FunciónTipoFinalidade
echoString... => ()Escribe cadeas con espazos e salto de liña
outString => StringEscribe unha cadea e devólvea para encadear

Biblioteca estándar

As bibliotecas impórtanse con import:

import test

Probas unitarias (lib/test.rg)

Unha biblioteca de probas sinxela que usa echo para a saída por terminal.

Asercións:

OperadorFinalidade
assert Int equals IntIgualdade de enteiros
assert Real equals RealIgualdade de reais
assert String equals StringIgualdade de cadeas
assert Bool is trueO booleano é verdadeiro
assert Bool is falseO booleano é falso

Estrutura das probas:

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

Saída na terminal:

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

Resumo dos elementos sintácticos

ElementoSintaxeFinalidade
Comentario-- textComentario dunha soa liña
Declaración de funciónfn :: name types => returnDeclara a sinatura de tipos dunha función
Definición de funciónname params = bodyDefine a implementación dunha función
Declaración de operadorop :: words types words => returnDeclara un operador en linguaxe natural
Definición de operadorwords params words = bodyDefine a implementación dun operador
Varios retornos=> type, typeDeclara varios valores de retorno
Parámetros variádicosType...Acepta un ou máis argumentos dun tipo
Literal de tensorTensor [[1, 2], [3, 4]]Crea un tensor N-dimensional
Indexación de tensort[0, 1] ou t[0][1]Accede a elementos do tensor
Mutación de tensort[0, 1] = 5.0Modifica elementos do 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 aliñamento de índices
echoecho "a" "b" "c"Escribe cadeas na terminal
out"text" outEnvía unha cadea á terminal
Ligazón local inmutablex = exprLiga unha variable local inmutable (= admite (none), |, ||)
Ligazón local mutablemut x = exprDeclara unha variable local mutable (reasígnase con x = new_expr)
Bloque de gardas| cond = exprRama de garda condicional despois de = (admite | aniñado)
Bloque de DAG paralelo|| expr -> varSubcálculos paralelos con resultados do DAG con nome
Bloque de tubaxe paralela|| exprExecución paralela que devolve unha tupla
Sobrecarga de operadoresop :: type + type => typeSobrecarga operadores existentes para tipos novos
Literais de BoolTrue, FalseValores primitivos booleanos
Operadores lóxicosand, or, xor, notOperadores lóxicos con palabras en minúsculas
Punto de entradafn :: main => ()Punto de entrada obrigatorio do programa

Formato canónico do código fonte (stable format e ferramentas de editor)

Só estético, non é un requisito do compilador (FR-TOOL-001): o analizador léxico e o sintáctico de Ridgun non dependen do aliñamento horizontal en columnas. Para ridgunc, os ficheiros fonte .rg sen aliñar e aliñados en columnas son 100 % equivalentes. As regras de abaixo definen a disposición estética canónica que aplican stable format e ridgun-mode de GNU Emacs (C-c C-f).

  1. Grupo de aliñamento por sinatura (fn :: / op ::):
  • Cada declaración fn :: ou op :: comeza un grupo de aliñamento que abrangue a súa sinatura, as súas cláusulas de pattern matching e as súas ligazóns || expr -> var, e que remata na seguinte declaración de nivel superior (fn, op, type, import, global, context, shared, hardware, @).
  1. Aliñamento en columnas dos elementos do lado esquerdo ($C_0 = 6$):
  • fn :: e op :: teñen 6 caracteres de ancho, así que o elemento $0$ do lado esquerdo da sinatura comeza na columna $C_0 = 6$.
  • Cada posición de elemento $i \in [0, N-1]$ antes de =>/-> (tratando "...", '...', [...] e (...) como elementos únicos) ten 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))$ e comeza na columna $C_i = C_{i-1} + W_{i-1} + 1$. Os patróns de cláusula de máis de 18 caracteres trátanse como valores atípicos: non estiran $W_i$ para o resto do grupo e simplemente sobresaen na súa propia liña cun espazo.
  • As cláusulas de definición correspondentes sángranse ata a columna $6$ ($C_0$), de xeito que o nome da función ou as palabras do operador e cada patrón de argumento quedan aliñados en vertical baixo as súas posicións da sinatura.
  1. Aliñamento vertical de =>, = e -> ($C_{\text{arrow}}$):
  • Sexa $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)$ e $C_{\text{ret}} = C_{\text{arrow}} + \text{len}(\text{arrow}) + 1$.
  • Na sinatura, => colócase en $C_{\text{arrow}}$ e o tipo de retorno en $C_{\text{ret}}$.
  • Nas cláusulas dunha soa liña (<lhs> = <rhs>), nas cláusulas de bloque de varias liñas (<lhs> =) e nas cláusulas encabezadas por gardas (<lhs> = seguido de | cond = ...), = colócase en $C_{\text{arrow}}$ (aliñado xusto debaixo de =>); nas cláusulas dunha soa liña, <rhs> comeza en $C_{\text{ret}}$.
  • Nas ligazóns de DAG paralelo (|| <lhs> -> <rhs>), || comeza na columna $8$ (6 + 2), -> colócase en $C_{\text{arrow}}$ e <rhs> comeza en $C_{\text{ret}}$.
  • Alternativa compacta (> 4 parámetros ou > 80 columnas): se unha declaración ten máis de 4 posicións de parámetro ou a súa liña de sinatura aliñada superaría os 80 caracteres, desactívase o recheo horizontal en columnas para ese grupo e os elementos sepáranse cun só espazo (" "), conservando o sangrado das cláusulas na columna 6.
  1. Sangrado estrutural (desprazamento de 2 espazos):
  • Declaracións de nivel superior (fn, op, type, import, global, context, shared, hardware, @): columna 0.
  • Cláusulas de función ou de operador baixo unha sinatura: columna 6.
  • Gardas (| cond = expr) e tubaxes paralelas (|| expr / || expr -> var): prev_indent + 2 (columna 8 dentro dunha cláusula, ou indent + 2 dentro dunha ligazón local ou dunha garda aniñada).
  • Liñas do corpo dun bloque secuencial despois dunha liña que remata en =: prev_indent + 2.

Operacións de orde superior sobre coleccións, secuenciais e paralelas (FR-COMP-001, FR-COMP-014)

Ridgun ofrece os combinadores secuenciais de coleccións habituais (map, filter, fold) xunto con combinadores paralelos multinúcleo explícitos co prefixo || (||map, ||filter). Os cinco combinadores admiten tanto a sintaxe idiomática de tubaxe ou infixa de esquerda a dereita (xs map f -> filter g -> fold 0 h -> res) como a sintaxe prefixa tradicional (map f xs):

IntegradoFormas prefixa e de tubaxeModelo de execución
mapxs map f / xs -> map f / map f xsSecuencial: avalía f x estritamente de esquerda a dereita no fío que fai a chamada.
`map`
filterxs filter p / xs -> filter p / filter p xsSecuencial: avalía o predicado p x estritamente de esquerda a dereita no fío que fai a chamada.
`filter`
foldxs fold init f / xs -> fold init f / fold f init xsPregado secuencial á esquerda: acumula acc = f acc x de esquerda a dereita, empezando desde init.

E/S literal de ficheiros, fluxos de bytes e funcións integradas POSIX (FR-VM-001, FR-VM-002)

  1. E/S literal de ficheiros e fluxos de bytes (FR-VM-001):
  • read_file : String => String le o contido dos ficheiros tal cal (conserva \r\n e a presenza ou ausencia exacta do salto de liña ao final do ficheiro, e nunca executa o preprocesador de import de .rg do compilador sobre ficheiros de datos).
  • load_rg_source : String => String úsaa exclusivamente a cadea de ferramentas do compilador ao cargar ficheiros fonte .rg con inserción de import.
  • read_bytes : String => [Int] e write_bytes : String [Int] => () len e escriben fluxos de bytes binarios en cru (0..255, incluídos 0x00 NUL e 0x80..0xFF).
  1. Funcións integradas POSIX de sistema de ficheiros, procesos e seguridade (FR-VM-002):
  • Sistema de ficheiros: list_dir, file_lstat, sha256_file, sys_mkdir, sys_chmod.
  • Procesos e contorno: sys_exec, sys_chdir, sys_getcwd, sys_getenv, sys_setenv, sys_exit.
  • Seguridade e separación de privilexios: 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 da PAL multiplataforma, sondaxe, primitivas de procesos, ADT de destino e cotas declarativas with timeout (FR-COMP-007):
  • ADT predeclarados da plataforma de destino:
  • type SysOs = Linux | MacOS | Windows (sys os => SysOs, aberto a extensións mediante expand type SysOs = ...)
  • type SysArch = X86_64 | Arm64 (sys arch => SysArch, aberto a extensións mediante expand type SysArch = ...)
  • Literais con unidade de duración: os literais enteiros aceptan os sufixos _ms (* 1), _s (* 1000) e _min (* 60000) (por exemplo, 500_ms, 5_s, 2_min), que complementan os sufixos de unidade de bytes (_KB, _MB, _GB).
  • Capa 1 — Primitivas PAL multiplataforma para as bibliotecas do sistema .rglib (posix.rglib / io.rglib):
  • sys os => SysOs e sys arch => SysArch (ADT suma do sistema operativo e da arquitectura de destino en tempo de compilación)
  • sys open mode String String => Int (path, modo portable "r", "w", "a", "rw", "w+", "a+" -> descritor ou handle >= 0 ou -errno, traducido segundo o sistema operativo de destino)
  • sys open String Int Int => Int (path, flags normalizados da PAL de Ridgun (0 = lectura, 1 = escritura, 2 = lectura e escritura, 64 = crear, 512 = truncar, 1024 = engadir, 2048 = non bloqueante), mode -> fd >= 0 ou -errno)
  • sys close Int => Int (fd -> 0 ou -errno)
  • sys read fd Int Int => String (fd, max_bytes -> cadea lida de fd)
  • sys write fd Int String => Int (fd, data -> bytes escritos ou -errno)
  • sys poll Int Int Int => Int (fd, events normalizados da PAL de Ridgun (1 = POLLIN/lectura, 4 = POLLOUT/escritura), timeout_ms -> > 0 revents cando está listo, 0 se se esgota o tempo, < 0 con -errno; usa sys remaining ms cando timeout_ms < 0 dentro de with timeout)
  • sys terminate Int Bool => Int (pid, force -> terminación de procesos portable, ordenada ou forzada) e sys kill Int Int => Int (pid, sinal normalizado -> 0 ou -errno)
  • sys call Int Int Int Int Int Int Int => Int (nr, a1..a6 -> vía de escape de baixo nivel para chamadas ao sistema segundo a ABI do sistema operativo de destino)
  • sys time ms => Int e sys sleep ms Int => () (reloxo monótono en milisegundos e espera)
  • sys deadline ms => Int e sys remaining ms => Int (prazo léxico activo do tempo límite e milisegundos que quedan, ou -1 se non hai límite)
  • Capa 2 — Cota de execución declarativa with timeout <duration> do:
  • Igual que with region 64_KB do establece unha cota léxica de bytes, with timeout 500_ms do (ou a forma combinada with region 64_KB, timeout 500_ms do) establece un prazo léxico de execución (sys_time_ms + duration_ms) no contexto do fío actual e restaura o prazo anterior ao saír do bloque:
       with timeout 500_ms do
         rem = sys remaining ms
         rc  = sys poll fd 1 rem
         rc
  1. Compilación de bibliotecas reutilizables (.rglib e ELF64 .so) e stable extensible (FR-COMP-006, FR-TOOL-003):
  • ridgunc build --lib <file.rg> -o <out.rglib> e ridgunc build --so <file.rg> -o <lib.so> (ou stable built lib / stable new --lib <name>) compilan módulos sen necesidade de fn :: main => () e conservan todas as funcións, os operadores e os tipos exportados.
  • stable descobre comandos propios fn :: task <words...> => String e extensións fn :: plugins => [String] (plugins/*.rg, .stable/plugins/*.rg), que se listan con stable tasks e stable plugins.

Ridgun Memory Management (RMM): RMM1 (CMM) e RMM2 (FR-COMP-002)

Ridgun xestiona a memoria do heap (String, [a], (a, b), ADT, Tensor) de forma determinista, sen un colector de lixo (GC) de rastrexo, mediante Ridgun Memory Management (RMM):

  1. Política RMM1 — Xestión de Memoria Compilada (CMM lineal + condicional):
  • Aplícase cando o fluxo de control e as funcións chamadas se coñecen estaticamente en tempo de compilación, sobre ligazóns non aniñadas:
  • CMM lineal (rg_cmm_move / rg_cmm_drop): o compilador determina en tempo de compilación a instrución exacta do último uso de cada variable local, move as variables devoltas (rg_cmm_move(&mut lv_x)) e emite unha liberación incondicional (rg_cmm_drop(&mut lv_x)) xusto despois do último uso dunha variable cando non queda vivo ningún posible alias.
  • CMM condicional (rg_cmm_drop_unless / ptr(a) != ptr(c)): cando unha chamada con ramificación estática (c = choose_list cond a b) devolve unha de varias entradas candidatas e unha ou máis candidatas seguen vivas xunto con c, o compilador constrúe a táboa de devanceiros con posibles alias (RCmmAlias) e emite unha garda de identidade de punteiros de 1 ciclo na instrución do último uso estático 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 — Conta por asignación e fervenza estrutural (política activa):
  • Unifica a xestión de memoria entre funcións, retornos con ramificación (choose_list), contedores aniñados (xs = [a, b, ..., z], xs[3] = a), variables locais mutables (mut x = ...) e execución en varios fíos:
  • Escalares primitivos (Int, Real, Bool, ()): nunca se contan; cópianse por valor na pila ou nos rexistros do procesador.
  • Argumentos de entrada da función (pats): nunca se tocan (+0 ao entrar, -0 ao saír).
  • Variables locais internas (x = ... / mut x = ...): reciben +1 na asignación (x = expr, xs[3] = a). Na reasignación dunha variable mutable (x = new_expr), new_expr recibe +1 antes de que o antigo x reciba -1.
  • Fin da función ou saída do ámbito:
  • As variables locais internas que non se devolven reciben primeiro -1: se rc == 0, libérase o bloque pai e propágase -1 en fervenza, un por un, a cada estrutura filla directa (liberando recursivamente calquera filla cuxo rc chegue a 0).
  • Unha variable local interna devolta (return a) recibe -1 (ou móvese con rg_cmm_move), e aínda que o seu rc sexa 0 en tránsito, exímese para que a asignación de quen chama (c = make_pair ()) lle sume +1 (0 + 1 = 1). Cando se devolve un argumento de entrada (choose_list cond a b), non se tocou dentro da función (rc = 1), así que o c = choose_list cond a b de quen chama súmalle +1 (1 + 1 = 2, compartido entre a e c).
  1. Política RMM3 (reservada):
  • Reservada para unha futura política de optimización estática agresiva.
  • Consulta docs/etc/cmm_architecture.md (no repositorio de Ridgun) para ver a especificación completa de RMM1 e RMM2.