Saltar para o conteúdo
Ridgun
Português

Referência

Especificação da linguagem Ridgun

Visão geral

O Ridgun é uma linguagem funcional, com o paralelismo em primeiro lugar, operadores em linguagem natural e tipagem estática forte. A sua sintaxe inspira-se em Haskell e nas linguagens da família ML, e introduz construções novas para o paralelismo e para definir operadores legíveis por humanos.

Características principais:

  • Operadores em linguagem natural — Os operadores leem-se como frases em inglês
  • Paralelismo implícito — A execução paralela é uma primitiva sintática, não uma chamada a uma biblioteca
  • Tipagem estática forte — Todos os tipos são declarados nas assinaturas
  • Definições por padrões — As funções e os operadores são definidos por equações

Comentários

Os comentários de uma só linha usam --:

-- This is a comment

Cadeias de texto

O Ridgun tem dois tipos de literal de cadeia de texto, com comportamentos de interpolação diferentes.

Cadeias interpoladas (aspas duplas)

As cadeias entre aspas duplas suportam interpolação de código em linha com {expr}. A expressão dentro das chavetas é avaliada e o seu resultado é inserido na cadeia.

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

Para incluir uma chaveta literal dentro de "...", escape-a com uma barra invertida (\{ e \}) ou duplique-a ({{ 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"

Cadeias brutas (aspas simples)

As cadeias entre aspas simples são brutas — não há qualquer interpolação. Todos os caracteres são tratados como texto literal, incluindo as chavetas.

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

Resumo

SintaxeInterpolaçãoCaso de utilização
"..."Sim — {expr} (escape as chavetas com \{/\} ou {{/}})Cadeias dinâmicas com expressões embutidas
'...'Não — tudo literalTexto estático, modelos, padrões de expressões regulares

Estrutura do programa

Todo o código executável em Ridgun tem de estar dentro de uma função ou de um operador. A função main é o ponto de entrada obrigatório da execução do programa.

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

Um ficheiro de código-fonte Ridgun só pode conter:

  • instruções import
  • definições type
  • declarações e definições fn e op
  • Uma função main como ponto de entrada

Não são permitidas expressões soltas no nível superior. Tudo o que é executado tem de ser chamado a partir de main.

Utilização na linha de comandos

ridgunc <command> [options] <file.rg>
ComandoFunção
runCompila e executa um ficheiro de código-fonte
checkVerifica os tipos e analisa sem executar
compileGera um ficheiro de bytecode .rgc
buildCria um executável nativo; uma biblioteca com --lib ou --so
versionMostra a versão do compilador
helpMostra a ajuda

A formatação, os testes e os projetos cabem ao stable.


Funções e operadores

As funções e os operadores infixos ou em linguagem natural declaram-se com uma assinatura de tipos terminada em => <return_type>, seguida de uma ou mais equações (cases). Os prefixos iniciais fn :: e op :: são opcionais:

  • Forma canónica sem prefixo (começa na coluna 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 com prefixo (fn :: / op ::, começa na coluna 0, com as cláusulas alinhadas na coluna 6):
  fn :: sum Int Int => Int
        sum a   b   =  a + b

  fn :: format app err AppErr        => String
        format app err (IoError msg) =  "IO:{msg}"
  • Espaços de linguagem natural em fn ::, funções nativas e nomes de ficheiro (FR-COMP-005):
  • As declarações e chamadas fn :: suportam nomes de várias palavras separadas por espaços (fn :: format app err AppErr => String, fn :: project name => String), tal como op :: e as declarações sem prefixo.
  • As funções nativas do runtime e de teste podem ser chamadas com espaços (assert equals a b, str len s, read file p, cmm live blocks), mantendo a compatibilidade com as formas com _.
  • As referências de primeira classe a funções com nomes de várias palavras (map double val xs, ||map double val xs, filter is even xs, fold add pair 0 xs) e os nomes de ficheiro .rg com espaços (src/test/ridgun/test core language.rg) são suportados de forma nativa pelo ridgunc e pelo stable.

Variáveis de tipo polimórficas (A..Z) e palavras-chave contextuais (type, with)

Para garantir que não há qualquer ambiguidade lexical entre as palavras de um operador e os parâmetros de tipo, tanto nas assinaturas com prefixo (op ::) como nas sem prefixo:

  • Todos os elementos de tipo numa assinatura começam por uma letra maiúscula (ou por [...] / (...)):
  • As letras maiúsculas isoladas (A..Z) indicam variáveis de tipo polimórficas (por exemplo, [A], (A, B), (A => B), swap A with B => (B, A)). As ocorrências repetidas de uma variável de tipo nos vários parâmetros são unificadas em cada ponto de chamada e substituídas no tipo de retorno.
  • Os identificadores de vários caracteres com inicial maiúscula (Int, String, Bool, RExpr, Tensor) indicam tipos concretos ou definidos pelo utilizador.
  • Todos os elementos em minúsculas (ou símbolos de operador +, -, *, /, %) numa assinatura de nível superior fazem parte do nome da função ou do operador mixfix.
  • Palavras-chave contextuais type e with: type só introduz uma declaração de tipo no nível superior quando é seguido de UpperName =. Em qualquer outro lugar (assinaturas fn :: e op ::, palavras de operador, padrões de parâmetros, variáveis locais e nomes de campos de registos), type e with são identificadores ou palavras válidos, sem ambiguidade.
  • Uma assinatura de nível superior com um identificador inicial em minúsculas seguido apenas de tipos antes de => é uma função prefixa (fn); qualquer assinatura com duas ou mais palavras em minúsculas ou símbolos de operador intercalados com tipos antes de => é um operador mixfix, infixo ou posfixo (op).

A declaração e a definição são separadas:

  • Declaração (fn ::) — indica o nome, os tipos dos parâmetros e o tipo de retorno
  • Definição — associa nomes aos parâmetros e fornece a implementação

Operadores

Os operadores são a característica mais distintiva do Ridgun. São nomeados em linguagem natural e definidos com op ::. Ao contrário dos operadores tradicionais (+, -), os operadores do Ridgun são expressões de várias palavras que se leem 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

A chamada lê-se assim: add 3 to 5 → 8

As palavras entre os parâmetros com tipo (to, and, in, etc.) são palavras-chave do operador — fazem parte da sua identidade e têm de aparecer nos pontos de chamada.

Operadores paralelos (||)

Os operadores podem exprimir execução paralela como conceito de primeira classe através de ||:

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

  • Vários valores de retorno — => Res, Res declara dois valores de retorno
  • Bloco || expr -> var — Associa subcálculos a nomes de resultado (where foi eliminado; || unifica todos os ramos paralelos)
  • -> — Atribui o resultado de um subcálculo paralelo a uma variável (ou a uma fatia de tensor -> out[i, j])
  • Os ramos || independentes são executados em simultâneo como um DAG paralelo, com base nas dependências de dados

Bloco de pipe paralelo direto (|| expr)

Quando uma expressão ou função devolve diretamente uma tupla de cálculos paralelos, sem nomes de variáveis intermédios, as linhas || aparecem logo a seguir a =:

-- 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 linha || corre como uma tarefa paralela independente. Os resultados são recolhidos pela ordem de declaração.

Sobrecarga de operadores

Os operadores existentes (incluindo os nativos, como +) podem ser sobrecarregados para novos tipos:

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

Precedência

Os operadores simbólicos têm a precedência mais alta — a matemática primeiro.

Quando surgem operadores simbólicos (como +, *) no corpo de operadores em linguagem natural, ligam com mais força do que as palavras-chave do operador que os rodeia. Isto garante que as expressões aritméticas são avaliadas como se espera, sem parênteses extra.

PrecedênciaCategoriaExemplos
Mais altaOperadores simbólicos*, /, +, -, %
Aplicação de funçõesf x
Comparação==, !=, <, >
Lógicosand, or, xor, not
Mais baixaOperadores em linguagem naturaladd a to b, run a and b in parallel

Encadeamento de operadores e pipelines idiomáticos da esquerda para a direita (->) (FR-COMP-014)

Os operadores e as funções que transformam valores ou sequências podem ser encadeados da esquerda para a direita, de modo que os dados fluam naturalmente da origem para o destino, sem parênteses aninhados da direita para a esquerda (h (g (f x))) nem variáveis intermédias descartáveis (st1, st2, st3).

1. O estilo de pipeline idiomático recomendado em Ridgun (2b)

O Ridgun unifica a invocação infixa com operando à esquerda e as etapas de pipeline explícitas com -> na mesma AST. Embora as quatro formas abaixo sejam todas válidas e equivalentes, a forma 2b (começar naturalmente com um sujeito + operação à esquerda, encadear as transformações seguintes com -> e associar o resultado final com -> target) é o estilo idiomático recomendado em 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

Os pipelines de várias etapas também podem ser escritos em linhas de continuação indentadas que começam por ->:

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 coleções

  • Regra universal para funções e operadores: qualquer valor que venha da esquerda de -> (ou da esquerda de uma chamada infixa x op a b) entra como primeiro argumento da função ou do operador à direita:
  • x -> f a b $\equiv$ x f a b $\equiv$ f x a b
  • Funciona sem problemas tanto em declarações fn :: como em declarações mixfix op ::, sem duplicar assinaturas.
  • Se uma função a jusante precisar do valor encadeado numa posição que não seja a primeira, um marcador explícito _ coloca o valor encadeado em _ (payload -> write_file output_path _).
  • Combinadores de coleções (map, ||map, filter, ||filter, fold): aceitam a coleção na posição prefixa tradicional (map f xs, filter g xs, fold f init xs) ou à esquerda de uma etapa infixa ou de pipeline (xs map f, xs -> map f, xs filter g, xs -> filter g, xs fold init f, xs -> fold init f).
  • Etapas de agentes (-> send to, -> ask to): encadear para -> send to ag ou -> ask to ag passa o valor da esquerda como mensagem ([5, 10, 15] -> sum int list -> send to worker_ag).
  • Associação a uma variável terminal (-> var): quando uma instrução de pipeline num bloco sequencial termina com -> var (em que var é o nome de uma variável local e não de uma função declarada), associa var (var = <pipeline_result>) durante o resto do bloco.

3. Máscaras de sequência em pipelines (-> (+ _ _ +) ->)

Quando uma etapa devolve uma sequência ou tupla de vários valores, uma etapa de máscara de sequência (-> (+ _ _ +) ->) filtra os seus elementos (+ mantém uma posição, _ descarta-a) e desempacota a tupla selecionada diretamente para uma função a jusante com vários argumentos:

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

4. Instruções com efeitos secundários sem _ = e descarte explícito de unit (-> ())

Nos blocos sequenciais, as instruções com efeitos secundários (echo, write_file, write_bytes, shell_exec, log info, print_error_lines, sys_mkdir, etc.) escrevem-se diretamente, sem o _ = repetitivo:

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

Quando a expressão final de uma função => () chama uma função auxiliar que devolve um valor diferente de () (como shell_exec : String => Int), acrescente -> () para avaliar a expressão, descartar o valor devolvido e produzir ():

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 sequência

Os operadores que devolvem sequências também podem ser encadeados em 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 => são aceites como separadores do tipo de retorno nos cabeçalhos fn :: / op ::. Nas expressões, -> é o operador de pipeline da esquerda para a direita e de associação, enquanto => introduz fechos léxicos (x => expr).


Sistema de tipos

Assinaturas de tipos

As assinaturas de tipos aparecem nas declarações, depois de fn :: ou op :: (ou sem prefixo):

fn :: <name> <type1> <type2> ... => <return_type1> <return_type2> ...
op :: <words and types mixed> => <return_type1> <return_type2> ...
  • Os parâmetros são posicionais; os tipos aparecem onde se esperam os parâmetros.
  • Sintaxe unificada de tipos de tupla e de sequência (FR-COMP-008):
  • Em Ridgun, uma lista de argumentos T1 T2 ... Tn e uma tupla (T1, T2, ..., Tn) representam o mesmo tipo composto subjacente: uma sequência ordenada de valores com tipo e tamanho fixo (seq).
  • Por isso, os tipos separados por espaços e os tipos de tupla separados por vírgulas são 100% intercambiáveis nos tipos de retorno, entre parênteses retos de lista e nas tuplas entre parênteses:
  • Tipos de retorno depois 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
  • Porque é que isto não tem qualquer ambiguidade sintática: ao contrário de Haskell e ML (em que a justaposição Maybe Int indica aplicação de tipos), os tipos parametrizados em Ridgun usam parênteses explícitos (Tensor(Float, 3, 3)) e as listas usam parênteses retos ([T]). Além disso, todos os elementos de tipo começam por uma letra maiúscula (A..Z, Int, String), por [ ou por (, enquanto as palavras de funções e operadores estão em minúsculas. Assim, T1 T2 nunca entra em conflito com construtores de tipos nem com palavras-chave de operadores.
  • A seta => (ou ->) separa os tipos de entrada do tipo de retorno.

Tipos nativos

TipoDescrição
IntInteiro
RealNúmero real
BoolBooleano
CharUm único carácter Unicode (UTF-8)
StringCadeia de texto (UTF-8)
ComplexNúmero complexo
TensorTensor mutável de N dimensões (elementos Real, CPU)
Tensor_GPUTensor mutável de N dimensões (elementos Real, GPU)

[!NOTE] Todo o texto em Ridgun está codificado em UTF-8 por omissão. Char representa um único ponto de código Unicode, e String é uma sequência de Char.

Literais Bool

Bool tem dois valores primitivos:

True                                     -- boolean true
False                                    -- boolean false

True e False são construtores — podem ser usados em expressões, passados como argumentos e usados em pattern matching:

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

Operadores lógicos

O Ridgun usa operadores lógicos em palavras e em minúsculas em vez de símbolos:

OperadorSignificadoExemplo
andConjunção lógicaa and b
orDisjunção lógicaa or b
xorOu exclusivoa xor b
notNegação lógicanot a
result = True and False                  -- False
result = True or False                   -- True
result = True xor True                   -- False
result = not False                       -- True

São operadores infixos em forma de palavra-chave, com a semântica booleana habitual.

Tipos de coleção nativos

Listas

As listas são um tipo recursivo nativo imutável, com sintaxe própria. Não é preciso definir nenhum ADT à mão. As listas são adequadas para o processamento funcional de dados, a computação simbólica e os algoritmos recursivos. Para cálculo numérico mutável, consulte Tensores mais abaixo.

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

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

Sintaxe literal: [elem1, elem2, ...] — um literal de lista. [] é a lista vazia.

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

Pattern matching com cons: (x:xs) — decompõe uma lista na sua cabeça x e na sua cauda xs. [] coincide com a lista vazia. Use _ como wildcard, que coincide sem associar nenhum nome.

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

Concatenação: :: junta duas listas (ou sequências).

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

Tensores

Os tensores são o tipo numérico nativo mutável de N dimensões do Ridgun. Generalizam vetores, matrizes e arrays de mais dimensões num único tipo. Os elementos são Real por omissão.

Sintaxe literal: Tensor seguido de parênteses retos aninhados. A profundidade do aninhamento 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]]]

Indexação: são suportadas a indexação encadeada e a indexação separada por vírgulas.

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

Mutação (in-place): os tensores são mutáveis — os elementos e as fatias podem ser alterados no próprio tensor (ao estilo de PyTorch e NumPy).

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

Contração de tensores (@): o operador @ faz a soma de Einstein. Os índices subscritos de cada operando definem a contração: os índices partilhados por todos os operandos são somados; os índices únicos sobrevivem 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 contração funciona com vários tensores numa única expressão:

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

Operações de Hadamard (+, -, *, /): os operadores aritméticos com índices subscritos fazem operações elemento a elemento. Os índices iguais alinham dimensões; aos índices sem correspondência aplica-se 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 necessidade de unsqueeze, expand_dims ou de redimensionar à mão — os índices são as regras de broadcasting.

Variante para GPU: Tensor_GPU coloca o tensor na memória da GPU para cálculo acelerado.

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

Sequências (seq)

As sequências são o tipo composto central do Ridgun. Uma seq é uma coleção ordenada de valores, com tamanho fixo — no fundo, uma tupla com tipo. As listas de parâmetros de funções e operadores são implicitamente sequências.

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

Associação de seq ((name)) — pôr o nome de um parâmetro entre parênteses associa todos os parâmetros a uma única variável 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

Seletor por máscara (s(+ _)) — extrai elementos por máscara, em que + seleciona uma posição e _ a salta:

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

Seletor por índice (s(0 3)) — extrai elementos pelas suas posições, contadas a partir de zero:

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

Junção plana (:) — concatena duas sequências numa única sequência plana:

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

Aninhamento (,) — compõe sequências como elementos distintos (seq de seqs):

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

Tipos de dados algébricos (ADTs)

O Ridgun usa tipos de dados algébricos para os tipos definidos pelo utilizador. Não há classes, métodos nem blocos impl — as funções e os operadores recebem tipos como parâmetros.

Tipos produto (registos) e sintaxe de atualização de registos (with)

Um tipo com campos com nome:

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

Acede-se aos campos pelo nome, como funções (host c, port c), e atualizam-se de forma funcional com with e dois pontos ::

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 soma (variantes)

Um tipo com vários construtores, separados por |:

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

ADTs soma genéricos e registos produto genéricos (FR-COMP-013)

Os ADTs soma e os registos produto definidos pelo utilizador podem declarar um ou mais parâmetros de tipo em maiúsculas (A..Z ou nomes de variáveis de tipo com inicial maiúscula) 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 aplicações de tipos paramétricos (Result Int String, Page User, [Box Int]) podem ser usadas em assinaturas fn :: / op ::, em campos de registos aninhados e nos dados dos construtores de ADTs.
  • Os construtores (Ok 42, Ok "hello", Page [1, 2] 2), o pattern matching, os acessores de campos (items p, total p) e as atualizações funcionais de registos (p with total = 10) instanciam variáveis de tipo novas em cada ponto de chamada.
  • type Option A = Some A | None é pré-registado como ADT polimórfico nativo quando o utilizador ainda não o declarou, de modo que try receive from ag devolve Some v ou None sem exigir declarações repetitivas (FR-COMP-013).

Funções anónimas e fechos léxicos ((x => expr), (a b => expr) — FR-COMP-013)

As funções anónimas (lambdas) escrevem-se entre parênteses, com os parâmetros separados por espaços, seguidos de => e da expressão do corpo ((x => expr) ou (a b => expr), por exemplo map (x => x + offset) xs, fold (acc x => acc + x) 0 xs), e podem capturar variáveis locais ou parâmetros do âmbito léxico que as envolve:

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

As funções anónimas regem-se por cinco regras canónicas:

  1. Parênteses obrigatórios: todas as lambdas têm de estar entre parênteses (... => ...). Escrever x => expr sem parênteses (como f = x => x + 1) é um erro de compilação (lambda expression must be enclosed in parentheses: '(x => expr)').
  2. Lambdas sem parâmetros ((=> expr)): o Ridgun proíbe () antes de => nas assinaturas de funções (fn :: main => ()). Coerentemente, uma lambda sem parâmetros escreve-se (=> expr) (invocada com thunk ()), e o seu tipo nas assinaturas é (=> Type). Escrever (() => expr) ou (() => Type) é um erro de compilação.
  3. Vários parâmetros ou desestruturação de tuplas ((a b => expr) e ((a b) => expr)): (a b => expr) é uma lambda de 2 parâmetros (como fold (acc x => acc + x) 0 xs), enquanto ((a b) => expr) é uma lambda de 1 parâmetro que desestrutura uma tupla (map ((a b) => a + b) [(1, 2), (3, 4)]). Confundi-las produz um erro de tipos próprio que explica a diferença.
  4. Corpos de lambda em várias linhas: quando => está no fim da linha, o bloco indentado por baixo forma um corpo de várias instruções (ou de guardas |), fechado por ):
   map (x =>
     doubled = x * 2
     doubled + offset
   ) [1, 2, 3]
  1. Tipos em maiúsculas e valores em minúsculas: (Conn => Conn) é um tipo de função e (conn => conn) é um valor lambda. Escrever tipos em maiúsculas (Conn => Conn) numa expressão ou parâmetros em minúsculas (conn => conn) numa assinatura fn :: é rejeitado com um erro de compilação claro.

As associações capturadas são elevadas automaticamente para fechos de aplicação parcial geridos por RMM1/RMM2.

Tipos recursivos

Os tipos podem referir-se a si próprios:

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

Nomes de construtores partilhados entre tipos (FR-COMP-005)

Várias declarações type podem reutilizar os mesmos nomes de construtores de variantes (como Error, Var, Lit, None), com aridades iguais ou diferentes. O verificador de tipos distingue os padrões de construtor pelo tipo de parâmetro esperado na coluna, e resolve as chamadas a construtores pela aridade e pelos tipos dos argumentos e pelos tipos de retorno de ADTs de união:

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

Extensões abertas de tipos (expand type) e junção automática de cláusulas fn :: (FR-COMP-005)

Os ADTs soma e os registos produto existentes podem ser estendidos com expand type UpperName = ...:

  • Estender um ADT soma: acrescenta novos construtores de variante ao tipo existente.
  • Estender um registo produto: acrescenta novos campos com nome (field: Type) e gera automaticamente as respetivas funções de acesso.
  • Variantes com nome de tipo, sem nomes de construtor redundantes: nas declarações soma type e expand type, uma variante cujo nome seja apenas um tipo nativo (String, Int, Real/Float, Bool, Char), sem mais argumentos de tipo a seguir, representa diretamente valores desse tipo (sem escrever Int Int ou String String). Quem chama pode passar valores simples (describe "hej", describe 42) ou chamadas explícitas ao construtor (describe (String "hej"), describe (Int 42)), e as equações podem coincidir tanto com literais ("admin", 0) como com padrões com tipo ((String s), (Int n)).
  • Junção automática de cláusulas fn :: / op :: (sem expand fn): voltar a declarar um fn :: ou op :: existente com novas cláusulas de padrões junta automaticamente essas cláusulas à definição existente da função, colocando as cláusulas de padrões específicos de construtor ou de literal antes de qualquer cláusula geral de recurso (_ / variável):
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

Espaços de nomes de módulo, acesso qualificado e visibilidade estrita de importações por módulo (FR-COMP-012)

Cada ficheiro .rg ou pacote .rglib define um espaço de nomes de módulo (derivado do nome do ficheiro sem extensão ou de um cabeçalho explícito module :: <name>) e pode importar outros módulos com import <mod> ou import <mod> as <alias>.

  1. Módulos com vários ficheiros (module :: <name>):
  • Vários ficheiros que declaram o mesmo cabeçalho module :: <name> (por exemplo, orm.rg e orm_changeset.rg, ambos a começar por module :: orm) partilham um único espaço de nomes lógico de módulo orm e veem diretamente os tipos, construtores, campos de registo e funções uns dos outros.
  1. Acesso qualificado (mod.Type, mod.Constructor, mod.field, mod.fn):
  • Os tipos exportados, os construtores de ADTs soma, os acessores de campos de registos produto, as atualizações de registos (with) e as funções podem ser referidos com um prefixo <mod_or_alias>. (http.Config, db.Status, http.Ok 200, http.port cfg, http.check status cfg).
  1. Resolução estrita de nomes não qualificados por módulo, em 3 níveis:
  • Cada módulo M (incluindo o módulo raiz da aplicação) resolve tipos, construtores, campos de registo, funções e chamadas de várias palavras não qualificados estritamente segundo o grafo de importações do próprio M, em três níveis de prioridade:
  • Nível 1 — O próprio módulo (M): as declarações no próprio M (em todos os ficheiros que partilham module :: M) têm sempre a prioridade mais alta.
  • Nível 2 — Importações diretas de M: se M não definir o nome, o ridgunc procura nos módulos importados diretamente por M (import D). Se uma única importação direta D definir o nome, este resolve-se sem ambiguidade para a declaração de D, mesmo que outros módulos da unidade de compilação (dependências transitivas, módulos irmãos ou a aplicação raiz) também definam o mesmo nome. Se duas ou mais importações diretas de M definirem o mesmo nome e M não o definir, as referências não qualificadas são rejeitadas por ambíguas (__ambiguous__.<name>) e têm de ser qualificadas (d1.<name> ou d2.<name>).
  • Nível 3 — Importações transitivas de M: só são consultadas quando nem o Nível 1 nem o Nível 2 definem o nome. Por isso, uma importação direta em M (import json) oculta de forma limpa uma função auxiliar transitiva (codec.json_get_str, importada internamente por solstice) sem obrigar solstice ou M a renomear ou qualificar as suas chamadas.
  • Isolamento em relação a módulos irmãos não importados e à aplicação raiz: as declarações definidas apenas em módulos que M não importou (nem direta nem transitivamente) — incluindo bibliotecas irmãs importadas pela aplicação raiz ou declarações da própria aplicação raiz ao compilar uma biblioteca importada M — são invisíveis para a procura não qualificada dentro de M (__unimported__.<name>).

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

Todas as definições ou associações em Ridgun — seja uma cláusula de função (fn ::), uma cláusula de operador (op ::), uma variável local imutável (x =), uma variável local mutável (mut x =) ou um ramo de guarda (| cond = / | otherwise =) — terminam com = e aceitam do lado direito exatamente as mesmas quatro formas de bloco ou expressão:

Prefixo depois de =Semântica de execuçãoSignificado
<expr> em linhaUma única expressão (com linhas || expr -> var opcionais abaixo)Avalia <expr> diretamente (resolvendo as associações de DAG paralelo || que estiverem por baixo).
(none) (linhas indentadas)Sequencial (de cima para baixo)Avalia as instruções por ordem (mut, atribuições, efeitos secundários); a última expressão é o resultado.
| <cond> = ...Condicional (guardas e árvores de decisão aninhadas)Avalia as condições booleanas de cima para baixo até ao primeiro ramo True (ou | otherwise =).
|| <expr>Paralelo (DAG ou tupla de green threads)Avalia os ramos || em simultâneo (|| expr -> var para nós de DAG com nome, || expr para resultados paralelos em tupla).

1. As guardas (|) vêm sempre depois de = (funções, operadores, variáveis locais e árvores de decisão aninhadas)

Os blocos de guardas (| cond = ... e | otherwise = ...) aparecem sempre depois de um =, o que unifica as cláusulas de funções, as cláusulas de operadores, as definições de variáveis locais e as árvores condicionais aninhadas:

Cláusulas de funções e operadores com 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

Atribuição a variáveis locais com guardas

Como | é válido depois de qualquer =, as variáveis locais podem ser associadas condicionalmente sem precisar de uma palavra-chave if/else nem de uma função auxiliar:

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

Blocos sequenciais de várias linhas, árvores de decisão profundas e terminadores de nível (|. / ||.)

Cada ramo de guarda termina com =, por isso o seu corpo pode ser, por sua vez, um bloco sequencial ((none)), um bloco paralelo (||) ou outro bloco de guardas | aninhado — o que permite árvores de decisão arbitrárias (3 a 4 níveis de profundidade, ou mais) dentro de | cond = e | otherwise =.

  • Fechar níveis aninhados (|. e ||.):
  • Em blocos simples de 1 nível ou no fim de uma função ou árvore, não é preciso nenhum terminador.
  • Quando um sub-bloco | (ou ||) aninhado termina e a execução regressa a um nível exterior em que ainda há mais ramos ou instruções por baixo, |. (para |) ou ||. (para ||) fecha explicitamente esse nível aninhado.
  • São válidas tanto a colocação em linha no fim da última linha (| otherwise = val |., o estilo compacto canónico) como a colocação na linha 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. Imutável por omissão (x = ...) e variáveis mutáveis explícitas (mut x = ...)

Dentro dos blocos sequenciais, as variáveis locais seguem a Opção 1:

  • Associação imutável (x = <rhs>):
  • Declara uma nova variável local imutável x.
  • Tentar reatribuir x = ... mais à frente no mesmo âmbito, sem uma declaração mut x = ... anterior, é um erro de compilação.
  • Declaração mutável (mut x = <rhs>) e reatribuição (x = <new_rhs>):
  • mut x = <rhs> declara x como mutável no âmbito atual e inicializa-a.
  • As reatribuições seguintes a x no mesmo âmbito escrevem-se simplesmente x = <new_rhs> (sem repetir mut).
  • Com RMM2, reatribuir x = <new_rhs> incrementa a estrutura nova (+1) antes de decrementar a estrutura antiga (-1, em cascata para os seus filhos se o seu contador chegar 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 ADTs

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

O Ridgun unifica toda a execução paralela em || (where foi removido):

Associações de DAG paralelo com nome (|| 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 linhas || expr -> var por baixo de uma expressão formam um grafo acíclico dirigido (DAG) paralelo.
  • O compilador analisa as dependências de variáveis entre as linhas || (x e y não têm dependências e correm em paralelo na Vaga 1; z depende de x e y e corre na Vaga 2, antes de x + y + z ser avaliado).
  • Também são suportadas escritas paralelas em fatias de tensores (|| expr -> out[i, j]).

Blocos de pipe paralelo direto (|| expr)

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

Cada linha || corre numa green thread paralela separada, e os resultados são devolvidos como uma tupla pela ordem de declaração (ou reunidos, quando o bloco está dentro de um bloco sequencial).

Canais com tipo

Para fluxos de dados e topologias produtor-consumidor entre green threads, o Ridgun oferece canais MPMC com tipo (Channel):

Função ou operador nativoAssinaturaFinalidade
new_channel() => ChannelCriar um canal com tipo sem limite
new_bounded_channelInt => ChannelCriar um canal limitado com capacidade de contrapressão
send_throughT Channel => ()Enviar um valor para um canal (bloqueia se for limitado e estiver cheio)
receive_fromChannel => TReceber o valor seguinte de um canal
try_receive_fromChannel => OptionConsulta não bloqueante que devolve Some v ou None

Primitivas de E/S

O Ridgun oferece funções nativas para escrever no terminal.

echo

Imprime um número arbitrário de cadeias de texto no terminal, separadas por espaços e seguidas de uma quebra de linha.

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

O sufixo ... no tipo do último parâmetro marca-o como variádico — aceita um ou mais argumentos desse tipo.

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

out

Um operador que escreve uma cadeia de texto diretamente no terminal e a devolve (o que permite encadear).

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

FunçãoTipoFinalidade
echoString... => ()Imprimir cadeias de texto com espaços e quebra de linha
outString => StringImprimir uma cadeia de texto e devolvê-la para encadear

Biblioteca padrão

As bibliotecas importam-se com import:

import test

Testes unitários (lib/test.rg)

Uma biblioteca de testes simples que usa echo para escrever no terminal.

Asserções:

OperadorFinalidade
assert Int equals IntIgualdade de inteiros
assert Real equals RealIgualdade de reais
assert String equals StringIgualdade de cadeias de texto
assert Bool is trueO booleano é verdadeiro
assert Bool is falseO booleano é falso

Estrutura dos testes:

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

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

Resumo dos elementos sintáticos

ElementoSintaxeFinalidade
Comentário-- textComentário de uma só linha
Declaração de funçãofn :: name types => returnDeclarar a assinatura de tipos de uma função
Definição de funçãoname params = bodyDefinir a implementação de uma função
Declaração de operadorop :: words types words => returnDeclarar um operador em linguagem natural
Definição de operadorwords params words = bodyDefinir a implementação de um operador
Vários retornos=> type, typeDeclarar vários valores de retorno
Parâmetros variádicosType...Aceitar um ou mais argumentos de um tipo
Literal de tensorTensor [[1, 2], [3, 4]]Criar um tensor de N dimensões
Indexação de tensorest[0, 1] ou t[0][1]Aceder a elementos de um tensor
Mutação de tensorest[0, 1] = 5.0Alterar elementos de um tensor no próprio tensor
Contração de EinsteinA_ij @ B_jkContração de tensores sobre índices partilhados
Operação de HadamardA_ij * B_ijAritmética elemento a elemento com alinhamento de índices
echoecho "a" "b" "c"Imprimir cadeias de texto no terminal
out"text" outEscrever uma cadeia de texto no terminal
Associação local imutávelx = exprAssociar uma variável local imutável (= suporta (none), |, ||)
Associação local mutávelmut x = exprDeclarar uma variável local mutável (reatribuída com x = new_expr)
Bloco de guardas| cond = exprRamo de guarda condicional depois de = (suporta | aninhados)
Bloco de DAG paralelo|| expr -> varSubcálculos paralelos com resultados de DAG com nome
Bloco de pipe paralelo|| exprExecução paralela que devolve uma tupla
Sobrecarga de operadoresop :: type + type => typeSobrecarregar operadores existentes para novos tipos
Literais BoolTrue, FalseValores booleanos primitivos
Operadores lógicosand, or, xor, notOperadores lógicos em palavras e em minúsculas
Ponto de entradafn :: main => ()Ponto de entrada obrigatório do programa

Formatação canónica do código-fonte (stable format e ferramentas de edição)

Apenas estética — não é um requisito do compilador (FR-TOOL-001): o lexer e o parser do Ridgun são insensíveis ao alinhamento horizontal em colunas. Para o ridgunc, os ficheiros .rg não alinhados e os alinhados em colunas são 100% equivalentes. As regras abaixo definem a disposição estética canónica aplicada pelo stable format e pelo ridgun-mode do GNU Emacs (C-c C-f).

  1. Grupo de alinhamento por assinatura (fn :: / op ::):
  • Cada declaração fn :: ou op :: inicia um grupo de alinhamento que abrange a sua assinatura, as suas cláusulas de pattern matching e as suas associações || expr -> var, e que termina na declaração de nível superior seguinte (fn, op, type, import, global, context, shared, hardware, @).
  1. Alinhamento em colunas dos elementos do lado esquerdo ($C_0 = 6$):
  • fn :: e op :: têm 6 caracteres de largura, por isso o elemento $0$ do lado esquerdo da assinatura começa na coluna $C_0 = 6$.
  • Cada posição de elemento $i \in [0, N-1]$ antes de =>/-> (tratando "...", '...', [...] e (...) como elementos únicos) tem largura $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 começa na coluna $C_i = C_{i-1} + W_{i-1} + 1$. Os padrões de cláusula com mais de 18 caracteres são tratados como valores atípicos: não alargam $W_i$ no resto do grupo e simplesmente transbordam na sua própria linha, com 1 espaço.
  • As cláusulas de definição correspondentes são indentadas até à coluna $6$ ($C_0$), para que o nome da função ou as palavras do operador e cada padrão de argumento fiquem alinhados verticalmente por baixo das posições correspondentes da assinatura.
  1. Alinhamento vertical de =>, = e -> ($C_{\text{arrow}}$):
  • Sejam $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 assinatura, => é colocado em $C_{\text{arrow}}$ e o tipo de retorno em $C_{\text{ret}}$.
  • Nas cláusulas de uma só linha (<lhs> = <rhs>), nas cláusulas de bloco de várias linhas (<lhs> =) e nas cláusulas encabeçadas por guardas (<lhs> = seguido de | cond = ...), = é colocado em $C_{\text{arrow}}$ (alinhado diretamente por baixo de =>); nas cláusulas de uma só linha, <rhs> começa em $C_{\text{ret}}$.
  • Nas associações de DAG paralelo (|| <lhs> -> <rhs>), || começa na coluna $8$ (6 + 2), -> é colocado em $C_{\text{arrow}}$ e <rhs> começa em $C_{\text{ret}}$.
  • Alternativa compacta (> 4 parâmetros ou > 80 colunas): se uma declaração tiver mais de 4 posições de parâmetro ou se a sua linha de assinatura alinhada ultrapassar 80 caracteres, o preenchimento horizontal em colunas é desativado para esse grupo e os elementos ficam separados por um único espaço (" "), mantendo a indentação das cláusulas na coluna 6.
  1. Indentação estrutural (deslocamento de 2 espaços):
  • Declarações de nível superior (fn, op, type, import, global, context, shared, hardware, @): coluna 0.
  • Cláusulas de funções ou operadores por baixo de uma assinatura: coluna 6.
  • Guardas (| cond = expr) e pipes paralelos (|| expr / || expr -> var): prev_indent + 2 (coluna 8 dentro de uma cláusula, ou indent + 2 dentro de uma associação local ou de uma guarda aninhada).
  • Linhas do corpo de um bloco sequencial a seguir a uma linha que termina em =: prev_indent + 2.

Operações de ordem superior sequenciais e paralelas sobre coleções (FR-COMP-001, FR-COMP-014)

O Ridgun oferece os combinadores de coleções sequenciais habituais (map, filter, fold), a par de combinadores paralelos multicore explícitos com o prefixo || (||map, ||filter). Os cinco combinadores suportam tanto a sintaxe idiomática de pipeline ou infixa da esquerda para a direita (xs map f -> filter g -> fold 0 h -> res) como a sintaxe prefixa tradicional (map f xs):

NativoFormas prefixa e de pipelineModelo de execução
mapxs map f / xs -> map f / map f xsSequencial: avalia f x estritamente da esquerda para a direita na thread que faz a chamada.
`map`
filterxs filter p / xs -> filter p / filter p xsSequencial: avalia o predicado p x estritamente da esquerda para a direita na thread que faz a chamada.
`filter`
foldxs fold init f / xs -> fold init f / fold f init xsDobra sequencial à esquerda: acumula acc = f acc x da esquerda para a direita, a partir de init.

E/S de ficheiros literal, fluxos de bytes e funções nativas POSIX (FR-VM-001, FR-VM-002)

  1. E/S literal de ficheiros e de fluxos de bytes (FR-VM-001):
  • read_file : String => String lê o conteúdo dos ficheiros tal como está (preserva \r\n e a presença ou ausência exata de quebra de linha no fim do ficheiro, e nunca aplica o pré-processador de import .rg do compilador a ficheiros de dados).
  • load_rg_source : String => String é usada exclusivamente pelas ferramentas do compilador ao carregar ficheiros de código-fonte .rg com junção de import.
  • read_bytes : String => [Int] e write_bytes : String [Int] => () leem e escrevem fluxos binários de bytes em bruto (0..255, incluindo 0x00 NUL e 0x80..0xFF).
  1. Funções nativas POSIX de sistema de ficheiros, processos e segurança (FR-VM-002):
  • Sistema de ficheiros: list_dir, file_lstat, sha256_file, sys_mkdir, sys_chmod.
  • Processos e ambiente: sys_exec, sys_chdir, sys_getcwd, sys_getenv, sys_setenv, sys_exit.
  • Segurança e separação de privilégios: 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, espera por eventos, primitivas de processos, ADTs de alvo e quotas declarativas with timeout (FR-COMP-007):
  • ADTs de plataforma alvo pré-declarados:
  • type SysOs = Linux | MacOS | Windows (sys os => SysOs, aberto a extensão com expand type SysOs = ...)
  • type SysArch = X86_64 | Arm64 (sys arch => SysArch, aberto a extensão com expand type SysArch = ...)
  • Literais com unidade de duração: os literais inteiros aceitam os sufixos _ms (* 1), _s (* 1000) e _min (* 60000) (por exemplo, 500_ms, 5_s, 2_min), que complementam os sufixos de unidades de bytes (_KB, _MB, _GB).
  • Camada 1 — Primitivas PAL multiplataforma para bibliotecas de sistema .rglib (posix.rglib / io.rglib):
  • sys os => SysOs e sys arch => SysArch (ADTs soma do sistema operativo e da arquitetura alvo, em tempo de compilação)
  • sys open mode String String => Int (path, modo portátil "r", "w", "a", "rw", "w+", "a+" -> descritor ou handle >= 0 ou -errno, traduzido para cada sistema operativo alvo)
  • sys open String Int Int => Int (path, flags normalizadas da PAL do Ridgun (0 = leitura, 1 = escrita, 2 = leitura e escrita, 64 = criar, 512 = truncar, 1024 = acrescentar, 2048 = não bloqueante), mode -> fd >= 0 ou -errno)
  • sys close Int => Int (fd -> 0 ou -errno)
  • sys read fd Int Int => String (fd, max_bytes -> cadeia 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 do Ridgun (1 = POLLIN/leitura, 4 = POLLOUT/escrita), timeout_ms -> revents > 0 quando estiver pronto, 0 se o tempo limite expirar, < 0 com -errno; usa sys remaining ms quando timeout_ms < 0 dentro de with timeout)
  • sys terminate Int Bool => Int (pid, force -> terminação portátil de processos, ordenada ou forçada) 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 -> via de escape de baixo nível para chamadas ao sistema na ABI do sistema operativo alvo)
  • sys time ms => Int e sys sleep ms Int => () (relógio monotónico em milissegundos e espera)
  • sys deadline ms => Int e sys remaining ms => Int (prazo léxico ativo e milissegundos restantes, ou -1 quando não há limite)
  • Camada 2 — Quota de execução declarativa with timeout <duration> do:
  • Tal como with region 64_KB do define uma quota léxica de bytes, with timeout 500_ms do (ou a forma combinada with region 64_KB, timeout 500_ms do) define um prazo léxico de execução (sys_time_ms + duration_ms) no contexto da thread atual e repõe o prazo anterior à saída do bloco:
       with timeout 500_ms do
         rem = sys remaining ms
         rc  = sys poll fd 1 rem
         rc
  1. Compilação de bibliotecas reutilizáveis (.rglib e ELF64 .so) e stable extensível (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>) compilam módulos sem exigir fn :: main => () e preservam todas as funções, operadores e tipos exportados.
  • O stable descobre comandos próprios fn :: task <words...> => String e extensões fn :: plugins => [String] (plugins/*.rg, .stable/plugins/*.rg), listados com stable tasks e stable plugins.

Gestão de memória do Ridgun (RMM): RMM1 (CMM) e RMM2 (FR-COMP-002)

O Ridgun gere a memória do heap (String, [a], (a, b), ADTs, Tensor) de forma determinística, sem um coletor de lixo (GC) com rastreio, através da Ridgun Memory Management (RMM):

  1. Política RMM1 — Gestão de memória compilada (CMM linear + condicional):
  • Aplica-se quando o fluxo de controlo e as funções chamadas são conhecidos estaticamente em tempo de compilação, sobre associações não aninhadas:
  • CMM linear (rg_cmm_move / rg_cmm_drop): o compilador determina em tempo de compilação a instrução exata da última utilização de cada variável local, move as variáveis devolvidas (rg_cmm_move(&mut lv_x)) e emite a libertação incondicional (rg_cmm_drop(&mut lv_x)) logo a seguir à última utilização de uma variável, quando já não restam possíveis aliases vivos.
  • CMM condicional (rg_cmm_drop_unless / ptr(a) != ptr(c)): quando uma chamada com ramificação estática (c = choose_list cond a b) devolve uma de várias entradas candidatas e uma ou mais candidatas continuam vivas a par de c, o compilador constrói a tabela de possíveis aliases de ancestrais (RCmmAlias) e emite uma guarda de identidade de ponteiros de 1 ciclo na instrução estática da última utilização de cada variável:
       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 — Contagem por atribuição e em cascata nas estruturas (política ativa):
  • Unifica a gestão de memória entre funções, retornos com ramificação (choose_list), contentores aninhados (xs = [a, b, ..., z], xs[3] = a), variáveis locais mutáveis (mut x = ...) e execução em várias threads:
  • Escalares primitivos (Int, Real, Bool, ()): nunca são contados; são copiados por valor na pilha ou nos registadores.
  • Argumentos de entrada de funções (pats): nunca são tocados (+0 à entrada, -0 à saída).
  • Variáveis locais internas (x = ... / mut x = ...): recebem +1 na atribuição (x = expr, xs[3] = a). Numa reatribuição mutável (x = new_expr), new_expr recebe +1 antes de o x antigo receber -1.
  • Fim da função ou saída do âmbito:
  • As variáveis locais internas não devolvidas recebem primeiro -1: se rc == 0, o bloco pai é libertado e propaga -1 em cascata, um por um, a cada estrutura filha direta (libertando recursivamente qualquer filha cujo rc chegue a 0).
  • Uma variável local interna devolvida (return a) recebe -1 (ou é movida com rg_cmm_move) e, mesmo que o seu rc seja 0 em trânsito, é poupada, para que a atribuição de quem chamou (c = make_pair ()) some +1 (0 + 1 = 1). Quando se devolve um argumento de entrada (choose_list cond a b), este não foi tocado dentro da função (rc = 1), por isso o c = choose_list cond a b de quem chamou soma +1 (1 + 1 = 2, partilhado entre a e c).
  1. Política RMM3 (reservada):
  • Reservada para uma futura política de otimização estática agressiva.
  • Consulte docs/etc/cmm_architecture.md (no repositório do Ridgun) para a especificação completa de RMM1 e RMM2.