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
| Sintaxe | Interpolação | Caso de utilização |
|---|---|---|
"..." | Sim — {expr} (escape as chavetas com \{/\} ou {{/}}) | Cadeias dinâmicas com expressões embutidas |
'...' | Não — tudo literal | Texto 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
fneop - Uma função
maincomo 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>
| Comando | Função |
|---|---|
run | Compila e executa um ficheiro de código-fonte |
check | Verifica os tipos e analisa sem executar |
compile | Gera um ficheiro de bytecode .rgc |
build | Cria um executável nativo; uma biblioteca com --lib ou --so |
version | Mostra a versão do compilador |
help | Mostra 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 comoop ::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.rgcom espaços (src/test/ridgun/test core language.rg) são suportados de forma nativa peloridgunce pelostable.
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
typeewith:typesó introduz uma declaração de tipo no nível superior quando é seguido deUpperName =. Em qualquer outro lugar (assinaturasfn ::eop ::, palavras de operador, padrões de parâmetros, variáveis locais e nomes de campos de registos),typeewithsã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, Resdeclara dois valores de retorno - Bloco
|| expr -> var— Associa subcálculos a nomes de resultado (wherefoi 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ência | Categoria | Exemplos |
|---|---|---|
| Mais alta | Operadores simbólicos | *, /, +, -, % |
| Aplicação de funções | f x | |
| Comparação | ==, !=, <, > | |
| Lógicos | and, or, xor, not | |
| Mais baixa | Operadores em linguagem natural | add 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 infixax 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 mixfixop ::, 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 agou-> ask to agpassa 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 quevaré o nome de uma variável local e não de uma função declarada), associavar(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çalhosfn ::/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 ... Tne 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 Intindica 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 T2nunca 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
| Tipo | Descrição |
|---|---|
Int | Inteiro |
Real | Número real |
Bool | Booleano |
Char | Um único carácter Unicode (UTF-8) |
String | Cadeia de texto (UTF-8) |
Complex | Número complexo |
Tensor | Tensor mutável de N dimensões (elementos Real, CPU) |
Tensor_GPU | Tensor mutável de N dimensões (elementos Real, GPU) |
[!NOTE] Todo o texto em Ridgun está codificado em UTF-8 por omissão.
Charrepresenta um único ponto de código Unicode, eStringé uma sequência deChar.
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:
| Operador | Significado | Exemplo |
|---|---|---|
and | Conjunção lógica | a and b |
or | Disjunção lógica | a or b |
xor | Ou exclusivo | a xor b |
not | Negação lógica | not 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 assinaturasfn ::/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 quetry receive from agdevolveSome vouNonesem 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:
- Parênteses obrigatórios: todas as lambdas têm de estar entre parênteses
(... => ...). Escreverx => exprsem parênteses (comof = x => x + 1) é um erro de compilação (lambda expression must be enclosed in parentheses: '(x => expr)'). - 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 comthunk ()), e o seu tipo nas assinaturas é(=> Type). Escrever(() => expr)ou(() => Type)é um erro de compilação. - 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 (comofold (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. - 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]
- 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 assinaturafn ::é 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
typeeexpand 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 escreverInt IntouString 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 ::(semexpand fn): voltar a declarar umfn ::ouop ::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>.
- Módulos com vários ficheiros (
module :: <name>):
- Vários ficheiros que declaram o mesmo cabeçalho
module :: <name>(por exemplo,orm.rgeorm_changeset.rg, ambos a começar pormodule :: orm) partilham um único espaço de nomes lógico de móduloorme veem diretamente os tipos, construtores, campos de registo e funções uns dos outros.
- 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).
- 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óprioM, em três níveis de prioridade: - Nível 1 — O próprio módulo (
M): as declarações no próprioM(em todos os ficheiros que partilhammodule :: M) têm sempre a prioridade mais alta. - Nível 2 — Importações diretas de
M: seMnão definir o nome, oridguncprocura nos módulos importados diretamente porM(import D). Se uma única importação diretaDdefinir o nome, este resolve-se sem ambiguidade para a declaração deD, 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 deMdefinirem o mesmo nome eMnã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>oud2.<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 emM(import json) oculta de forma limpa uma função auxiliar transitiva (codec.json_get_str, importada internamente porsolstice) sem obrigarsolsticeouMa 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
Mnã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 importadaM— são invisíveis para a procura não qualificada dentro deM(__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ção | Significado |
|---|---|---|
<expr> em linha | Uma ú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çãomut x = ...anterior, é um erro de compilação. - Declaração mutável (
mut x = <rhs>) e reatribuição (x = <new_rhs>): mut x = <rhs>declaraxcomo mutável no âmbito atual e inicializa-a.- As reatribuições seguintes a
xno mesmo âmbito escrevem-se simplesmentex = <new_rhs>(sem repetirmut). - Com
RMM2, reatribuirx = <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 a0).
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 -> varpor 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
||(xeynão têm dependências e correm em paralelo na Vaga 1;zdepende dexeye corre na Vaga 2, antes dex + y + zser 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 nativo | Assinatura | Finalidade |
|---|---|---|
new_channel | () => Channel | Criar um canal com tipo sem limite |
new_bounded_channel | Int => Channel | Criar um canal limitado com capacidade de contrapressão |
send_through | T Channel => () | Enviar um valor para um canal (bloqueia se for limitado e estiver cheio) |
receive_from | Channel => T | Receber o valor seguinte de um canal |
try_receive_from | Channel => Option | Consulta 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ção | Tipo | Finalidade |
|---|---|---|
echo | String... => () | Imprimir cadeias de texto com espaços e quebra de linha |
out | String => String | Imprimir 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:
| Operador | Finalidade |
|---|---|
assert Int equals Int | Igualdade de inteiros |
assert Real equals Real | Igualdade de reais |
assert String equals String | Igualdade de cadeias de texto |
assert Bool is true | O booleano é verdadeiro |
assert Bool is false | O 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
| Elemento | Sintaxe | Finalidade |
|---|---|---|
| Comentário | -- text | Comentário de uma só linha |
| Declaração de função | fn :: name types => return | Declarar a assinatura de tipos de uma função |
| Definição de função | name params = body | Definir a implementação de uma função |
| Declaração de operador | op :: words types words => return | Declarar um operador em linguagem natural |
| Definição de operador | words params words = body | Definir a implementação de um operador |
| Vários retornos | => type, type | Declarar vários valores de retorno |
| Parâmetros variádicos | Type... | Aceitar um ou mais argumentos de um tipo |
| Literal de tensor | Tensor [[1, 2], [3, 4]] | Criar um tensor de N dimensões |
| Indexação de tensores | t[0, 1] ou t[0][1] | Aceder a elementos de um tensor |
| Mutação de tensores | t[0, 1] = 5.0 | Alterar elementos de um tensor no próprio tensor |
| Contração de Einstein | A_ij @ B_jk | Contração de tensores sobre índices partilhados |
| Operação de Hadamard | A_ij * B_ij | Aritmética elemento a elemento com alinhamento de índices |
echo | echo "a" "b" "c" | Imprimir cadeias de texto no terminal |
out | "text" out | Escrever uma cadeia de texto no terminal |
| Associação local imutável | x = expr | Associar uma variável local imutável (= suporta (none), |, ||) |
| Associação local mutável | mut x = expr | Declarar uma variável local mutável (reatribuída com x = new_expr) |
| Bloco de guardas | | cond = expr | Ramo de guarda condicional depois de = (suporta | aninhados) |
| Bloco de DAG paralelo | || expr -> var | Subcálculos paralelos com resultados de DAG com nome |
| Bloco de pipe paralelo | || expr | Execução paralela que devolve uma tupla |
| Sobrecarga de operadores | op :: type + type => type | Sobrecarregar operadores existentes para novos tipos |
| Literais Bool | True, False | Valores booleanos primitivos |
| Operadores lógicos | and, or, xor, not | Operadores lógicos em palavras e em minúsculas |
| Ponto de entrada | fn :: 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 oridgunc, os ficheiros.rgnão alinhados e os alinhados em colunas são 100% equivalentes. As regras abaixo definem a disposição estética canónica aplicada pelostable formate peloridgun-modedo GNU Emacs (C-c C-f).
- Grupo de alinhamento por assinatura (
fn ::/op ::):
- Cada declaração
fn ::ouop ::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,@).
- Alinhamento em colunas dos elementos do lado esquerdo ($C_0 = 6$):
fn ::eop ::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 de18caracteres 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.
- 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 (
> 4parâmetros ou> 80colunas): se uma declaração tiver mais de4posições de parâmetro ou se a sua linha de assinatura alinhada ultrapassar80caracteres, 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.
- Indentação estrutural (deslocamento de 2 espaços):
- Declarações de nível superior (
fn,op,type,import,global,context,shared,hardware,@): coluna0. - 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(coluna8dentro de uma cláusula, ouindent + 2dentro 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):
| Nativo | Formas prefixa e de pipeline | Modelo de execução |
|---|---|---|
map | xs map f / xs -> map f / map f xs | Sequencial: avalia f x estritamente da esquerda para a direita na thread que faz a chamada. |
| ` | map` | |
filter | xs filter p / xs -> filter p / filter p xs | Sequencial: avalia o predicado p x estritamente da esquerda para a direita na thread que faz a chamada. |
| ` | filter` | |
fold | xs fold init f / xs -> fold init f / fold f init xs | Dobra 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)
- E/S literal de ficheiros e de fluxos de bytes (
FR-VM-001):
read_file : String => Stringlê o conteúdo dos ficheiros tal como está (preserva\r\ne a presença ou ausência exata de quebra de linha no fim do ficheiro, e nunca aplica o pré-processador deimport.rgdo compilador a ficheiros de dados).load_rg_source : String => Stringé usada exclusivamente pelas ferramentas do compilador ao carregar ficheiros de código-fonte.rgcom junção deimport.read_bytes : String => [Int]ewrite_bytes : String [Int] => ()leem e escrevem fluxos binários de bytes em bruto (0..255, incluindo0x00NUL e0x80..0xFF).
- 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.
- 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 comexpand type SysOs = ...)type SysArch = X86_64 | Arm64(sys arch => SysArch, aberto a extensão comexpand 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 => SysOsesys 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>= 0ou-errno, traduzido para cada sistema operativo alvo)sys open String Int Int => Int(path,flagsnormalizadas da PAL do Ridgun (0= leitura,1= escrita,2= leitura e escrita,64= criar,512= truncar,1024= acrescentar,2048= não bloqueante),mode->fd >= 0ou-errno)sys close Int => Int(fd->0ou-errno)sys read fd Int Int => String(fd,max_bytes-> cadeia lida defd)sys write fd Int String => Int(fd,data-> bytes escritos ou-errno)sys poll Int Int Int => Int(fd,eventsnormalizados da PAL do Ridgun (1=POLLIN/leitura,4=POLLOUT/escrita),timeout_ms-> revents> 0quando estiver pronto,0se o tempo limite expirar,< 0com-errno; usasys remaining msquandotimeout_ms < 0dentro dewith timeout)sys terminate Int Bool => Int(pid,force-> terminação portátil de processos, ordenada ou forçada) esys kill Int Int => Int(pid, sinal normalizado ->0ou-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 => Intesys sleep ms Int => ()(relógio monotónico em milissegundos e espera)sys deadline ms => Intesys remaining ms => Int(prazo léxico ativo e milissegundos restantes, ou-1quando não há limite)- Camada 2 — Quota de execução declarativa
with timeout <duration> do: - Tal como
with region 64_KB dodefine uma quota léxica de bytes,with timeout 500_ms do(ou a forma combinadawith 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
- Consulte
docs/lang/pal-and-system-libraries.mdpara a especificação completa da arquitetura da PAL e das bibliotecas de sistema (.rglib).
- Compilação de bibliotecas reutilizáveis (
.rglibe ELF64.so) estableextensível (FR-COMP-006,FR-TOOL-003):
ridgunc build --lib <file.rg> -o <out.rglib>eridgunc build --so <file.rg> -o <lib.so>(oustable built lib/stable new --lib <name>) compilam módulos sem exigirfn :: main => ()e preservam todas as funções, operadores e tipos exportados.- O
stabledescobre comandos própriosfn :: task <words...> => Stringe extensõesfn :: plugins => [String](plugins/*.rg,.stable/plugins/*.rg), listados comstable tasksestable 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):
- 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 dec, 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)
- 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+1na atribuição (x = expr,xs[3] = a). Numa reatribuição mutável (x = new_expr),new_exprrecebe+1antes de oxantigo receber-1. - Fim da função ou saída do âmbito:
- As variáveis locais internas não devolvidas recebem primeiro
-1: serc == 0, o bloco pai é libertado e propaga-1em cascata, um por um, a cada estrutura filha direta (libertando recursivamente qualquer filha cujorcchegue a0). - Uma variável local interna devolvida (
return a) recebe-1(ou é movida comrg_cmm_move) e, mesmo que o seurcseja0em 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 oc = choose_list cond a bde quem chamou soma+1(1 + 1 = 2, partilhado entreaec).
- 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 deRMM1eRMM2.