Riferimento
Specifica del linguaggio Ridgun
Panoramica
Ridgun è un linguaggio funzionale, pensato fin dall'inizio per il parallelismo, con operatori in linguaggio naturale e tipizzazione statica forte. La sua sintassi si ispira a Haskell e ai linguaggi della famiglia ML, e introduce costrutti nuovi per il parallelismo e per definire operatori leggibili dalle persone.
Caratteristiche principali:
- Operatori in linguaggio naturale — Gli operatori si leggono come frasi in inglese
- Parallelismo implicito — L'esecuzione parallela è una primitiva sintattica, non una chiamata a una libreria
- Tipizzazione statica forte — Tutti i tipi sono dichiarati nelle firme
- Definizioni per pattern — Funzioni e operatori si definiscono tramite equazioni
Commenti
I commenti su una riga usano --:
-- This is a comment
Stringhe
Ridgun ha due tipi di letterali stringa, con un comportamento di interpolazione diverso.
Stringhe interpolate (virgolette doppie)
Le stringhe tra virgolette doppie supportano l'interpolazione di codice in linea con {expr}. L'espressione tra le parentesi graffe viene valutata e il suo risultato viene inserito nella stringa.
name = "Ridgun"
"Hello, {name}!" -- "Hello, Ridgun!"
"Sum is {sum 3 4}" -- "Sum is 7"
"Nested: {factorial 5} done" -- "Nested: 120 done"
Per includere una parentesi graffa letterale dentro "...", eseguine l'escape con una barra rovesciata (\{ e \}) oppure raddoppiala ({{ 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"
Stringhe raw (virgolette singole)
Le stringhe tra virgolette singole sono raw: non viene eseguita alcuna interpolazione. Tutti i caratteri sono trattati come testo letterale, comprese le parentesi graffe.
'Hello, {name}!' -- "Hello, {name}!"
'{this is just pure chars}' -- "{this is just pure chars}"
'No {interpolation} here' -- "No {interpolation} here"
Riepilogo
| Sintassi | Interpolazione | Caso d'uso |
|---|---|---|
"..." | Sì — {expr} (esegui l'escape delle graffe con \{/\} o {{/}}) | Stringhe dinamiche con espressioni incorporate |
'...' | No — tutto letterale | Testo statico, template, pattern di espressioni regolari |
Struttura di un programma
Tutto il codice eseguibile di Ridgun deve trovarsi all'interno di una funzione o di un operatore. La funzione main è il punto di ingresso obbligatorio per l'esecuzione del programma.
fn :: main => ()
main = echo "Hello, Ridgun!"
Un file sorgente Ridgun può contenere soltanto:
- istruzioni
import - definizioni
type - dichiarazioni e definizioni
fneop - una funzione
maincome punto di ingresso
Non sono ammesse espressioni sparse al livello superiore. Tutto ciò che viene eseguito deve essere chiamato da main.
Uso da riga di comando
ridgunc <command> [options] <file.rg>
| Comando | Stato | Scopo |
|---|---|---|
run <file.rg> | ✅ Attivo | Compila ed esegue un file sorgente Ridgun |
check <file.rg> | ✅ Attivo | Controlla i tipi e analizza le strategie statiche senza eseguire |
compile <file.rg> [-o out.rgc] | ✅ Attivo | Genera un artefatto di bytecode .rgc |
build <file.rg> [-o binary] [--target arch] | ✅ Attivo | Crea un eseguibile nativo ELF64 autonomo |
build --lib <file.rg> [-o out.rglib] | ✅ Attivo | Crea una libreria Ridgun riutilizzabile (.rglib e .so) |
build --so <file.rg> [-o out.so] | ✅ Attivo | Crea una libreria condivisa nativa ELF64 (ET_DYN .so) |
version / --version | ✅ Attivo | Mostra la versione del compilatore e l'architettura di destinazione |
help / --help | ✅ Attivo | Mostra le informazioni d'uso |
Tra le opzioni comuni ci sono --strict / -S (applica le regole rigorose della sintassi canonica di Ridgun) e --target x86_64-linux / arm64-linux. I flussi di lavoro a livello di progetto (build, test, check, format, deps, REPL interattivo) sono forniti da stable.
Funzioni e operatori
Le funzioni e gli operatori infissi o in linguaggio naturale si dichiarano con una firma di tipo che termina con => <return_type>, seguita da una o più equazioni (cases). I prefissi iniziali fn :: e op :: sono facoltativi:
- Forma canonica senza prefisso (inizia alla colonna 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 esplicita con prefisso (
fn ::/op ::, inizia alla colonna 0 con le clausole allineate alla colonna 6):
fn :: sum Int Int => Int
sum a b = a + b
fn :: format app err AppErr => String
format app err (IoError msg) = "IO:{msg}"
- Spazi da linguaggio naturale in
fn ::, nelle funzioni integrate e nei nomi di file (FR-COMP-005): - Le dichiarazioni e le chiamate
fn ::supportano nomi composti da più parole separate da spazi (fn :: format app err AppErr => String,fn :: project name => String), proprio comeop ::e le dichiarazioni senza prefisso. - Le funzioni integrate del runtime e dei test si possono chiamare con gli spazi (
assert equals a b,str len s,read file p,cmm live blocks), mantenendo la compatibilità all'indietro con le forme con_. - I riferimenti di prima classe a funzioni con nomi di più parole (
map double val xs,||map double val xs,filter is even xs,fold add pair 0 xs) e i nomi di file.rgcon spazi (src/test/ridgun/test core language.rg) sono supportati nativamente daridguncestable.
Variabili di tipo polimorfiche (A..Z) e parole chiave contestuali (type, with)
Per garantire zero ambiguità lessicale tra le parole degli operatori e i parametri di tipo, sia nelle firme con prefisso (op ::) sia in quelle senza prefisso:
- Ogni token di tipo in una firma inizia con una lettera maiuscola (oppure con
[...]/(...)): - Le singole lettere maiuscole (
A..Z) indicano variabili di tipo polimorfiche (ad es.[A],(A, B),(A => B),swap A with B => (B, A)). Le occorrenze ripetute di una variabile di tipo tra i parametri vengono unificate a ogni punto di chiamata e sostituite nel tipo di ritorno. - Gli identificatori di più caratteri con l'iniziale maiuscola (
Int,String,Bool,RExpr,Tensor) indicano tipi concreti o definiti dall'utente. - Ogni token minuscolo (o simbolo di operatore
+,-,*,/,%) in una firma di livello superiore fa parte del nome della funzione o dell'operatore mixfix. - Parole chiave contestuali
typeewith:typeintroduce una dichiarazione di tipo al livello superiore solo quando è seguito daUpperName =. In tutti gli altri punti (firmefn ::eop ::, parole degli operatori, pattern dei parametri, variabili locali e nomi dei campi dei record),typeewithsono identificatori o parole validi, senza ambiguità. - Una firma di livello superiore con un solo identificatore minuscolo iniziale seguito soltanto da tipi prima di
=>è una funzione prefissa (fn); qualsiasi firma con due o più parole minuscole o simboli di operatore intercalati ai tipi prima di=>è un operatore mixfix/infisso/postfisso (op).
La dichiarazione e la definizione sono separate:
- Dichiarazione (
fn ::) — specifica il nome, i tipi dei parametri e il tipo di ritorno - Definizione — associa i nomi dei parametri e fornisce l'implementazione
Operatori
Gli operatori sono la caratteristica distintiva di Ridgun. Hanno nomi in linguaggio naturale e si definiscono con op ::. A differenza degli operatori tradizionali (+, -), gli operatori di Ridgun sono espressioni di più parole che si leggono come frasi.
Sintassi
op :: <natural language signature with types> -> <return_type>
<natural language pattern with params> = <body>
Esempio di base
-- Declaration: "add [Int] to [Int]" returns an Int
op :: add Int to Int => Int
add a to b = a + b
L'uso si leggerebbe così: add 3 to 5 → 8
Le parole tra i parametri tipizzati (to, and, in, ecc.) sono parole chiave dell'operatore: fanno parte dell'identità dell'operatore e devono comparire nei punti di chiamata.
Operatori paralleli (||)
Gli operatori possono esprimere l'esecuzione parallela come concetto di prima classe usando ||:
-- Run two configs in parallel with named DAG bindings (|| expr -> var)
op :: run Conf and Conf in parallel => Res, Res
run a and b in parallel = res_a, res_b
|| run my own process a -> res_a
|| run my own process b -> res_b
Elementi chiave:
- Più valori di ritorno —
=> Res, Resdichiara due valori di ritorno - Blocco
|| expr -> var— Associa i sotto-calcoli a nomi di risultato (whereè stato eliminato;||unifica tutti i rami paralleli) ->— Assegna il risultato di un sotto-calcolo parallelo a una variabile (o a una slice di tensore-> out[i, j])- I rami
||indipendenti vengono eseguiti in modo concorrente come DAG parallelo, in base alle dipendenze tra i dati
Blocco pipe parallelo diretto (|| expr)
Quando un'espressione o una funzione restituisce direttamente una tupla di calcoli paralleli senza nomi di variabili intermedi, le righe || compaiono subito dopo =:
-- 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
Ogni riga || viene eseguita come un task parallelo indipendente. I risultati vengono raccolti nell'ordine di dichiarazione.
Overloading degli operatori
Gli operatori esistenti (compresi quelli integrati come +) possono essere sovraccaricati per nuovi tipi:
-- Overload + for Complex numbers
op :: Complex + Complex => Complex
a + b = (real a) + (real b) + complex_i * ((imaginary a) + (imaginary b))
Precedenza
Gli operatori simbolici hanno la precedenza più alta: prima la matematica.
Quando degli operatori simbolici (come +, *) compaiono nel corpo di operatori in linguaggio naturale, si legano più strettamente delle parole chiave dell'operatore circostante. In questo modo le espressioni aritmetiche vengono valutate come previsto, senza parentesi aggiuntive.
| Precedenza | Categoria | Esempi |
|---|---|---|
| Massima | Operatori simbolici | *, /, +, -, % |
| Applicazione di funzione | f x | |
| Confronto | ==, !=, <, > | |
| Logici | and, or, xor, not | |
| Minima | Operatori in linguaggio naturale | add a to b, run a and b in parallel |
Concatenamento di operatori e pipeline idiomatiche da sinistra a destra (->) (FR-COMP-014)
Gli operatori e le funzioni che trasformano valori o sequenze si possono concatenare da sinistra a destra, così i dati scorrono in modo naturale dall'origine alla destinazione, senza parentesi annidate da destra a sinistra (h (g (f x))) né variabili intermedie usa e getta (st1, st2, st3).
1. Lo stile di pipeline idiomatico consigliato in Ridgun (2b)
Ridgun unifica l'invocazione infissa con operando a sinistra e gli stadi espliciti di pipeline -> nello stesso AST. Anche se tutte e quattro le forme qui sotto sono valide ed equivalenti, la forma 2b (che inizia in modo naturale con un soggetto + un'operazione a sinistra, concatena le trasformazioni successive con -> e associa il risultato finale con -> target) è lo stile idiomatico consigliato in 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
Le pipeline a più stadi si possono scrivere anche su righe di continuazione indentate che iniziano con ->:
raw_nums map (x => x * 3)
-> filter (x => x >= 12)
-> fold 0 (acc x => acc + x)
-> total_sum
2. Regola universale del primo argomento con -> e combinatori di collezioni
- Regola universale per funzioni e operatori: qualsiasi valore che arriva da sinistra di
->(o da sinistra di una chiamata infissax op a b) entra come primo argomento della funzione o dell'operatore a destra: x -> f a b$\equiv$x f a b$\equiv$f x a b- Funziona senza attriti sia per le dichiarazioni
fn ::sia per le dichiarazioni mixfixop ::, senza duplicare le firme. - Se una funzione a valle ha bisogno del valore in pipe in una posizione diversa dal primo parametro, un segnaposto esplicito
_colloca il valore in pipe al posto di_(payload -> write file output_path _). - Combinatori di collezioni (
map,||map,filter,||filter,fold): accettano la collezione sia nella posizione prefissa tradizionale (map f xs,filter g xs,fold f init xs) sia da sinistra di uno stadio infisso o di pipeline (xs map f,xs -> map f,xs filter g,xs -> filter g,xs fold init f,xs -> fold init f). - Stadi verso agenti (
-> send to,-> ask to): un pipe verso-> send to ago-> ask to agpassa il valore di sinistra come messaggio ([5, 10, 15] -> sum int list -> send to worker_ag). - Binding di variabile terminale (
-> var): quando un'istruzione di pipeline in un blocco sequenziale termina con-> var(dovevarè il nome di una variabile locale e non di una funzione dichiarata), associavar(var = <pipeline_result>) per il resto del blocco.
3. Maschere di sequenza nelle pipeline (-> (+ _ _ +) ->)
Quando uno stadio restituisce una sequenza o una tupla di più valori, uno stadio maschera di sequenza (-> (+ _ _ +) ->) ne filtra gli elementi (+ mantiene una posizione, _ la scarta) e spacchetta la tupla selezionata direttamente in una funzione a valle con più argomenti:
parse header packet "v0.9.9" -> (+ _ _ +) -> combine code msg -> status_line
4. Istruzioni con effetti collaterali senza _ = e scarto esplicito di unit (-> ())
Nei blocchi sequenziali, le istruzioni con effetti collaterali (echo, write file, write bytes, shell exec, log info, print error lines, sys mkdir, ecc.) si scrivono direttamente, senza il boilerplate _ =:
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 l'espressione finale di una funzione => () chiama un helper che restituisce un valore diverso da () (come shell exec : String => Int), aggiungi -> () per valutare l'espressione, scartarne il valore di ritorno e produrre ():
fn :: exec built run Int => ()
exec built run 0 = shell exec "./target/app" -> ()
exec built run _ = sys exit 1
5. Concatenamento point-free di operatori su sequenze
Anche gli operatori che restituiscono sequenze si possono concatenare in stile 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:
->e=>sono entrambi accettati come separatori del tipo di ritorno nelle intestazionifn ::/op ::. Nelle espressioni,->è l'operatore di pipeline e di binding da sinistra a destra, mentre=>introduce le closure lessicali(x => expr).
Sistema dei tipi
Firme di tipo
Le firme di tipo compaiono nelle dichiarazioni dopo fn :: o op :: (oppure senza prefisso):
fn :: <name> <type1> <type2> ... => <return_type1> <return_type2> ...
op :: <words and types mixed> => <return_type1> <return_type2> ...
- I parametri sono posizionali; i tipi compaiono dove sono attesi i parametri.
- Sintassi unificata dei tipi tupla e sequenza (
FR-COMP-008): - In Ridgun, una lista di argomenti
T1 T2 ... Tne una tupla(T1, T2, ..., Tn)rappresentano lo stesso tipo composto sottostante: una sequenza ordinata, di dimensione fissa, di valori tipizzati (seq). - Per questo i tipi separati da spazi e i tipi tupla separati da virgole sono intercambiabili al 100% nei tipi di ritorno, tra parentesi quadre di lista e nelle tuple tra parentesi tonde:
- Tipi di ritorno dopo
=>/->:
fn :: parse header String => Int Int [String String] String
-- 100% equivalent to:
fn :: parse header String => (Int, Int, [(String, String)], String)
- Liste di tuple (
[T1 T2 ... Tn]):
[String String] -- equivalent to [(String, String)]
[Int String Bool] -- equivalent to [(Int, String, Bool)]
- Tuple tra parentesi tonde (
(T1 T2 ... Tn)):
fn :: fst (A B) => A
fst (a, _) = a
- Perché non c'è alcuna ambiguità sintattica: a differenza di Haskell/ML (dove la giustapposizione
Maybe Intindica un'applicazione di tipo), in Ridgun i tipi parametrizzati usano parentesi tonde esplicite (Tensor(Float, 3, 3)) e le liste usano parentesi quadre ([T]). Inoltre ogni token di tipo inizia con una lettera maiuscola (A..Z,Int,String), con[o con(, mentre le parole di funzioni e operatori sono minuscole. QuindiT1 T2non entra mai in conflitto con costruttori di tipo o parole chiave degli operatori. - La freccia
=>(o->) separa i tipi di input dal tipo di ritorno.
Tipi integrati
| Tipo | Descrizione |
|---|---|
Int | Intero |
Real | Numero reale |
Bool | Booleano |
Char | Singolo carattere Unicode (UTF-8) |
String | Stringa di testo (UTF-8) |
Complex | Numero complesso |
Tensor | Tensore N-dimensionale mutabile (elementi Real, CPU) |
Tensor_GPU | Tensore N-dimensionale mutabile (elementi Real, GPU) |
[!NOTE] In Ridgun tutto il testo è codificato in UTF-8 per impostazione predefinita.
Charrappresenta un singolo code point Unicode,Stringè una sequenza diChar.
Letterali Bool
Bool ha due valori primitivi:
True -- boolean true
False -- boolean false
True e False sono costruttori: si possono usare nelle espressioni, passare come argomenti e usare nel pattern matching:
fn :: is valid Int => Bool
is valid n =
| n > 0 = True
| otherwise = False
Operatori logici
Ridgun usa operatori logici a parole, in minuscolo invece di operatori simbolici:
| Operatore | Significato | Esempio |
|---|---|---|
and | Congiunzione logica | a and b |
or | Disgiunzione logica | a or b |
xor | Or esclusivo | a xor b |
not | Negazione logica | not a |
result = True and False -- False
result = True or False -- True
result = True xor True -- False
result = not False -- True
Sono operatori infissi a parola chiave con la semantica booleana standard.
Tipi di collezione nativi
Liste
Le liste sono un tipo ricorsivo immutabile integrato, con una sintassi dedicata. Non serve definire manualmente un ADT. Le liste sono adatte all'elaborazione funzionale dei dati, al calcolo simbolico e agli algoritmi ricorsivi. Per il calcolo numerico mutabile, vedi i Tensori più avanti.
Sintassi del tipo: [type] — una lista di elementi del tipo indicato.
fn :: sum list [Int] => Int
fn :: reverse [Int] => [Int]
Sintassi letterale: [elem1, elem2, ...] — un letterale di lista. [] è la lista vuota.
fn :: my list => [Int]
my list = [1, 2, 3, 4, 5]
Pattern matching cons: (x:xs) — scompone una lista nella testa x e nella coda xs. [] corrisponde alla lista vuota. Usa _ come jolly che corrisponde senza associare un 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
Concatenazione: :: unisce due liste (o sequenze).
fn :: append [Int] [Int] => [Int]
append a b = a :: b
Tensori
I tensori sono il tipo numerico N-dimensionale mutabile nativo di Ridgun. Generalizzano vettori, matrici e array di dimensione superiore in un unico tipo. Gli elementi sono Real per impostazione predefinita.
Sintassi letterale: Tensor seguito da parentesi quadre annidate. La profondità di annidamento determina il numero di dimensioni.
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]]]
Indicizzazione: sono supportate sia l'indicizzazione concatenata sia quella con indici separati da virgole.
val = m[0][1] -- chained indexing: row 0, col 1
val = m[0, 1] -- comma indexing: equivalent
row = m[0] -- slice: entire first row
Mutazione (sul posto): i tensori sono mutabili: elementi e slice si possono modificare sul posto (stile PyTorch/NumPy).
m[0, 1] = 5.0 -- set element
m[0] = Tensor [10, 20] -- set entire row
Contrazione tensoriale (@): l'operatore @ esegue la sommatoria di Einstein. Gli indici in pedice di ogni operando definiscono la contrazione: gli indici condivisi da tutti gli operandi vengono sommati; gli indici unici sopravvivono nel risultato.
-- A has shape (s, d, f), B has shape (n, d, f)
-- Shared indices: d, f → contracted (summed)
-- Result T has shape (s, n)
T = A_sdf @ B_ndf
-- Equivalent to: np.einsum('sdf,ndf->sn', A, B)
La contrazione funziona su più tensori in un'unica espressione:
-- 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)
Operazioni di Hadamard (+, -, *, /): gli operatori aritmetici con indici in pedice eseguono operazioni elemento per elemento. Gli indici corrispondenti allineano le dimensioni; quelli non corrispondenti vengono propagati in 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
Questo elimina la necessità di unsqueeze, expand_dims o di rimodellamenti manuali: gli indici sono le regole di broadcasting.
Variante GPU: Tensor_GPU colloca il tensore nella memoria della GPU per un calcolo accelerato.
g = Tensor_GPU [[1, 2], [3, 4]]
result = g_ij @ g_jk -- GPU-accelerated matrix multiply
Sequenze (seq)
Le sequenze sono il tipo composto fondamentale di Ridgun. Una seq è una collezione ordinata, di dimensione fissa, di valori: in sostanza una tupla tipizzata. Le liste di parametri di funzioni e operatori sono implicitamente sequenze.
-- These parameter types form implicit sequences:
fn :: sum Int Int => Int -- params: seq(Int, Int)
fn :: process Int Real => Bool -- params: seq(Int, Real)
Binding di sequenza ((name)) — racchiudere il nome di un parametro tra parentesi associa tutti i parametri a un'unica variabile 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
Selettore a maschera (s(+ _)) — estrae elementi tramite una maschera, dove + seleziona una posizione e _ la salta:
s(+ _) -- select 1st, skip 2nd
s(_ +) -- skip 1st, select 2nd
s(+ _ _ _) -- select 1st, skip 2nd-4th
s(_ _ _ +) -- skip 1st-3rd, select 4th
Selettore per indice (s(0 3)) — estrae elementi in base alla loro posizione, contando da zero:
fn :: extreme points Int Int Int Int => Int Int
extreme points (s) = s(0 3)
Unione piatta (:) — concatena due sequenze in un'unica sequenza piatta:
seq1 = a, b -- seq(a, b)
seq2 = c, d -- seq(c, d)
seq1:seq2 -- seq(a, b, c, d)
Annidamento (,) — compone sequenze come elementi distinti (sequenza di sequenze):
seq1, seq2 -- seq(seq(a, b), seq(c, d))
Tipi di dati algebrici (ADT)
Ridgun usa tipi di dati algebrici per i tipi definiti dall'utente. Non esistono classi, metodi né blocchi impl: funzioni e operatori ricevono i tipi come parametri.
Tipi prodotto (record) e sintassi di aggiornamento dei record (with)
Un tipo con campi con nome:
type Conf = host: String, port: Int, tls: Bool
type Point = x: Real, y: Real
Ai campi si accede per nome come funzioni (host c, port c) e si aggiornano in modo funzionale con with e =:
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
Tipi somma (varianti)
Un tipo con più costruttori, separati da |:
type Result = Ok Int | Err String
type Option = Some Int | None
type Color = Red | Green | Blue
ADT somma generici e record prodotto generici (FR-COMP-013)
Gli ADT somma e i record prodotto definiti dall'utente possono dichiarare uno o più parametri di tipo maiuscoli (A..Z o nomi di variabili di tipo con l'iniziale maiuscola) tra il nome del 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
- Le applicazioni di tipi parametrici (
Result Int String,Page User,[Box Int]) si possono usare nelle firmefn ::/op ::, nei campi di record annidati e nei payload dei costruttori di ADT. - I costruttori (
Ok 42,Ok "hello",Page [1, 2] 2), il pattern matching, gli accessor dei campi (items p,total p) e gli aggiornamenti funzionali dei record (p with total = 10) istanziano variabili di tipo nuove a ogni punto di chiamata. type Option A = Some A | Noneè preregistrato come ADT polimorfico integrato quando l'utente non lo ha già dichiarato, quinditry receive from agrestituisceSome voNonesenza bisogno di dichiarazioni boilerplate (FR-COMP-013).
Funzioni anonime e closure lessicali ((x => expr), (a b => expr) — FR-COMP-013)
Le funzioni anonime (lambda) si scrivono tra parentesi, con i parametri separati da spazi seguiti da => e dall'espressione del corpo ((x => expr) o (a b => expr), ad es. map (x => x + offset) xs, fold (acc x => acc + x) 0 xs), e possono catturare variabili locali o parametri dal loro scope lessicale circostante:
fn :: add offset Int [Int] => [Int]
add offset k xs = map (x => x + k) xs
Cinque regole canoniche governano le funzioni anonime:
- Parentesi obbligatorie: ogni lambda deve essere racchiusa tra parentesi
(... => ...). Unx => exprsenza parentesi (comef = x => x + 1) è un errore in fase di compilazione (lambda expression must be enclosed in parentheses: '(x => expr)'). - Lambda senza parametri (
(=> expr)): Ridgun vieta()prima di=>nelle firme di funzione (fn :: main => ()). Coerentemente, una lambda senza parametri si scrive(=> expr)(e si invoca comethunk ()), e il suo tipo nelle firme è(=> Type). Scrivere(() => expr)o(() => Type)è un errore in fase di compilazione. - Più parametri e destrutturazione di tuple a confronto (
(a b => expr)vs.((a b) => expr)):(a b => expr)è una lambda con 2 parametri (comefold (acc x => acc + x) 0 xs), mentre((a b) => expr)è una lambda con 1 parametro che destruttura una tupla (map ((a b) => a + b) [(1, 2), (3, 4)]). Confonderle produce un errore di tipo dedicato che spiega la differenza. - Corpi di lambda su più righe: quando
=>si trova alla fine della riga, il blocco indentato sottostante forma un corpo con più istruzioni (o con guardie|), chiuso da):
map (x =>
doubled = x * 2
doubled + offset
) [1, 2, 3]
- Tipi in maiuscolo e valori in minuscolo:
(Conn => Conn)è un tipo funzione e(conn => conn)è un valore lambda. Scrivere tipi in maiuscolo(Conn => Conn)in un'espressione o parametri in minuscolo(conn => conn)in una firmafn ::viene rifiutato con un chiaro errore in fase di compilazione.
I binding catturati vengono trasformati automaticamente in closure ad applicazione parziale gestite da RMM1/RMM2.
Tipi ricorsivi
I tipi possono fare riferimento a sé stessi:
type List = Empty | Cons Int List
type Tree = Leaf Int | Node Tree Tree
Nomi di costruttore condivisi tra tipi (FR-COMP-005)
Più dichiarazioni type possono riutilizzare gli stessi nomi di costruttori di variante (come Error, Var, Lit, None) con arità uguali o diverse. Il typechecker disambigua i pattern dei costruttori tramite il tipo di parametro atteso nella colonna, e risolve le chiamate ai costruttori in base all'arità e ai tipi degli argomenti e ai tipi di ritorno ADT unione:
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
Estensioni aperte dei tipi (expand type) e fusione automatica delle clausole fn :: (FR-COMP-005)
Gli ADT somma e i record prodotto esistenti si possono estendere con expand type UpperName = ...:
- Estendere un ADT somma: aggiunge nuovi costruttori di variante al tipo esistente.
- Estendere un record prodotto: aggiunge nuovi campi con nome (
field: Type) e genera automaticamente le relative funzioni accessor. - Varianti con nome di tipo senza nomi di costruttore ridondanti: nelle dichiarazioni somma
typeedexpand type, una variante il cui nome è un semplice tipo integrato (String,Int,Real/Float,Bool,Char) senza ulteriori argomenti di tipo rappresenta direttamente i valori di quel tipo (senza dover scrivereInt IntoString String). Chi chiama può passare valori semplici (describe "hej",describe 42) o chiamate esplicite al costruttore (describe (String "hej"),describe (Int 42)), e le equazioni possono corrispondere sia a letterali ("admin",0) sia a pattern tipizzati ((String s),(Int n)). - Fusione automatica delle clausole
fn ::/op ::(senzaexpand fn): ridichiarare unfn ::o unop ::esistente con nuove clausole di pattern fonde automaticamente quelle clausole nella definizione esistente della funzione, collocando le clausole con pattern specifici di costruttore o letterale prima di qualsiasi clausola di ripiego generica (_/ variabile):
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
Namespace dei moduli, accesso qualificato e visibilità rigorosa degli import per modulo (FR-COMP-012)
Ogni file .rg o bundle .rglib definisce un namespace di modulo (derivato dal nome base del file o da un'intestazione esplicita module :: <name>) e può importare altri moduli tramite import <mod> o import <mod> as <alias>.
- Moduli su più file (
module :: <name>):
- Più file che dichiarano la stessa intestazione
module :: <name>(per esempioorm.rgeorm_changeset.rg, che iniziano entrambi conmodule :: orm) condividono un unico namespace logico di moduloorme vedono direttamente i tipi, i costruttori, i campi dei record e le funzioni l'uno dell'altro.
- Accesso qualificato (
mod.Type,mod.Constructor,mod.field,mod.fn):
- I tipi esportati, i costruttori degli ADT somma, gli accessor dei campi dei record prodotto, gli aggiornamenti dei record (
with) e le funzioni si possono referenziare con un prefisso<mod_or_alias>.(http.Config,db.Status,http.Ok 200,http.port cfg,http.check status cfg).
- Risoluzione rigorosa a 3 livelli dei nomi non qualificati per modulo:
- Ogni modulo
M(compreso il modulo applicativo radice) risolve tipi, costruttori, campi di record, funzioni e chiamate di più parole non qualificati esclusivamente in base al grafo degli import diM, con tre livelli di priorità: - Livello 1 — Modulo stesso (
M): le dichiarazioni inMstesso (in tutti i file che condividonomodule :: M) hanno sempre la priorità più alta. - Livello 2 — Import diretti di
M: seMnon definisce il nome,ridgunccerca nei moduli importati direttamente daM(import D). Se un solo import direttoDdefinisce il nome, questo si risolve senza ambiguità nella dichiarazione diD, anche se altri moduli dell'unità di compilazione (dipendenze transitive, moduli fratelli o l'applicazione radice) definiscono lo stesso nome. Se due o più import diretti diMdefiniscono lo stesso nome eMnon lo definisce, i riferimenti non qualificati vengono rifiutati come ambigui (__ambiguous__.<name>) e devono essere qualificati (d1.<name>vsd2.<name>). - Livello 3 — Import transitivi di
M: vengono consultati solo quando né il livello 1 né il livello 2 definiscono il nome. Di conseguenza, un import diretto inM(import json) oscura in modo pulito un helper transitivo (codec.json get str, importato internamente dasolstice) senza costringeresolsticeoMa rinominare o qualificare le proprie chiamate. - Isolamento dai moduli fratelli non importati e dall'applicazione radice: le dichiarazioni definite solo in moduli che
Mnon ha importato (né direttamente né transitivamente), comprese le librerie sorelle importate dall'applicazione radice o le dichiarazioni dell'applicazione radice stessa quando si compila una libreria importataM, sono invisibili alla ricerca non qualificata dentroM(__unimported__.<name>).
Grammatica unificata di = ((none), |, ||) e variabili locali (FR-COMP-003)
Ogni definizione o binding in Ridgun, che sia una clausola di funzione (fn ::), una clausola di operatore (op ::), una variabile locale immutabile (x =), una variabile locale mutabile (mut x =) o un ramo di guardia (| cond = / | otherwise =), termina con = e accetta a destra esattamente le stesse quattro forme di blocco o espressione:
Prefisso dopo = | Semantica di esecuzione | Significato |
|---|---|---|
<expr> in linea | Espressione singola (con eventuali righe || expr -> var sotto) | Valuta direttamente <expr> (risolvendo gli eventuali binding del DAG parallelo || sottostanti). |
(none) (righe indentate) | Sequenziale (dall'alto in basso) | Valuta le istruzioni in ordine (mut, assegnazioni, effetti collaterali); l'ultima espressione è il risultato. |
| <cond> = ... | Condizionale (guardie e alberi di decisione annidati) | Valuta le condizioni booleane dall'alto in basso fino al primo ramo True (o | otherwise =). |
|| <expr> | Parallela (DAG / tupla di green thread) | Valuta i rami || in modo concorrente (|| expr -> var per i nodi del DAG con nome, || expr per i risultati in tupla parallela). |
1. Le guardie (|) seguono sempre = (funzioni, operatori, variabili locali e alberi di decisione annidati)
I blocchi di guardia (| cond = ... e | otherwise = ...) compaiono sempre dopo un =, unificando clausole di funzione, clausole di operatore, definizioni di variabili locali e alberi condizionali annidati:
Clausole di funzione e di operatore con guardie
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
Assegnazione di variabili locali con guardie
Poiché | è valido dopo qualsiasi =, le variabili locali si possono associare in modo condizionale senza bisogno di una parola chiave if/else né di una funzione helper:
discount =
| is_vip = 20
| total > 100 = 10
| otherwise = 0
Blocchi sequenziali su più righe, alberi di decisione profondi e chiusure annidate (|. / ||.)
Ogni ramo di guardia termina con =, quindi il suo corpo può essere a sua volta un blocco sequenziale ((none)), un blocco parallelo (||) o un altro blocco di guardie | annidato, il che consente alberi di decisione arbitrari (profondi 3–4 livelli o più) dentro | cond = e | otherwise =.
- Chiusura dei livelli annidati (
|.e||.): - Nei blocchi semplici a 1 livello o alla fine di una funzione o di un albero, non serve alcun token di chiusura.
- Quando un sotto-blocco
|(o||) annidato termina e l'esecuzione torna a un livello esterno sotto il quale seguono altri rami o istruzioni,|.(per|) o||.(per||) chiude esplicitamente quel livello annidato. - Sono valide sia la collocazione in linea alla fine dell'ultima riga (
| otherwise = val |., lo stile compatto canonico) sia la collocazione sulla riga successiva (|.):
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. Immutabili per impostazione predefinita (x = ...) e variabili mutabili esplicite (mut x = ...)
Nei blocchi sequenziali, le variabili locali seguono l'Opzione 1:
- Binding immutabile (
x = <rhs>): - Dichiara una nuova variabile locale immutabile
x. - Tentare di riassegnare
x = ...più avanti nello stesso scope senza una precedente dichiarazionemut x = ...è un errore in fase di compilazione. - Dichiarazione mutabile (
mut x = <rhs>) e riassegnazione (x = <new_rhs>): mut x = <rhs>dichiaraxcome mutabile nello scope corrente e la inizializza.- Le riassegnazioni successive di
xnello stesso scope si scrivono semplicementex = <new_rhs>(senza ripeteremut). - Con
RMM2, riassegnarex = <new_rhs>incrementa la nuova struttura (+1) prima di decrementare quella vecchia (-1, a cascata sui suoi figli se il suo contatore arriva 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 su letterali e costruttori di ADT
fn :: factorial Int => Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
fn :: unwrap Result => Int
unwrap (Ok n) = n
unwrap (Err msg) = 0
fn :: length List => Int
length Empty = 0
length (Cons _ rest) = 1 + length rest
fn :: sum tree Tree => Int
sum tree (Leaf n) = n
sum tree (Node left right) = sum tree left + sum tree right
Primitive di parallelismo (||)
Ridgun unifica tutta l'esecuzione parallela sotto || (where è stato rimosso):
Binding con nome in un DAG parallelo (|| 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
- Tutte le righe
|| expr -> varsotto un'espressione formano un grafo aciclico diretto (DAG) parallelo. - Il compilatore analizza le dipendenze tra le variabili delle righe
||(xeynon hanno dipendenze e vengono eseguite in parallelo nell'ondata 1;zdipende daxeye viene eseguita nell'ondata 2, prima che venga valutatox + y + z). - Sono supportate anche le scritture parallele su slice di tensore (
|| expr -> out[i, j]).
Blocchi pipe paralleli diretti (|| expr)
op :: run Conf and Conf in parallel => Res, Res
run a and b in parallel =
|| run process a
|| run process b
Ogni riga || viene eseguita come un green thread parallelo separato, e i risultati vengono restituiti come tupla nell'ordine di dichiarazione (oppure attesi con un join quando il blocco si trova dentro un blocco sequenziale).
Canali tipizzati
Per lo streaming di dati e le topologie produttore-consumatore tra green thread, Ridgun fornisce canali MPMC tipizzati (Channel):
| Funzione / operatore integrato | Firma | Scopo |
|---|---|---|
new channel | () => Channel | Crea un canale tipizzato illimitato |
new bounded channel | Int => Channel | Crea un canale limitato con capacità di backpressure |
send through | T Channel => () | Invia un valore in un canale (si blocca se è limitato e pieno) |
receive from | Channel => T | Riceve il valore successivo da un canale |
try receive from | Channel => Option | Polling non bloccante che restituisce Some v o None |
Modello nativo degli agenti (agent, agent sink, agent :: — FR-COMP-009)
Ridgun fornisce agenti tipizzati in stile actor (Agent In Out e SinkAgent In State di tipo fire-and-forget) che elaborano i messaggi in sequenza su uno stato isolato, mentre vengono eseguiti in modo concorrente su thread worker in background:
| Costrutto / funzione integrata | Firma / sintassi | Scopo |
|---|---|---|
agent <fn> | (In => Out) => Agent In Out | Avvia un agente FIFO senza stato |
agent <fn> <init_state> | (S In => S Out) S => Agent In Out | Avvia un agente con stato il cui passo restituisce (new_state, reply) |
agent sink <fn> <init_state> [bounded N drop|block] [drain D] | (S In => S) S => SinkAgent In S | Avvia un agente sink fire-and-forget senza coda di output |
agent :: <name> = agent sink ... | Dichiarazione di livello superiore | Dichiara un agente sink globale con nome, accessibile da tutti i thread |
send <msg> to <ag> | In (Agent In Out) => () | Accoda un messaggio in modo asincrono |
ask <msg> to <ag> | In (Agent In Out) => Out | Invia un messaggio e attende la risposta per questo chiamante |
receive from <ag> | (Agent In Out) => Out | Attende e restituisce la risposta successiva per il thread chiamante |
try receive from <ag> | (Agent In Out) => Option Out | Polling non bloccante che restituisce Some v o None |
stop <ag> / agent alive <ag> | Agent => () / Agent => Bool | Arresta un agente in modo ordinato o controlla se è ancora in esecuzione |
agent flush <ag> / agent state <ag> / agent dropped <ag> | Ispezione e test | Svuota i messaggi in sospeso, legge lo stato corrente o legge il numero di messaggi scartati |
Primitive di I/O
Ridgun fornisce funzioni integrate per l'output sul terminale.
echo
Stampa sul terminale un numero arbitrario di stringhe, separate da spazi e seguite da un a capo.
fn :: echo String... => ()
Il suffisso ... sull'ultimo tipo di parametro lo rende variadico: accetta uno o più argomenti di quel tipo.
echo "hello" -- prints: hello
echo "hello" "world" "!" -- prints: hello world !
echo "result: {result}" -- prints: result: 42
out
Un operatore che scrive una stringa direttamente sul terminale e la restituisce (permettendo il concatenamento).
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
Riepilogo
| Funzione | Tipo | Scopo |
|---|---|---|
echo | String... => () | Stampa stringhe con spazi e a capo |
out | String => String | Stampa la stringa e la restituisce per il concatenamento |
Libreria standard
Le librerie si importano con import:
import test
Unit test (lib/test.rg)
Una semplice libreria di test che usa echo per l'output sul terminale.
Asserzioni:
| Operatore | Scopo |
|---|---|
assert Int equals Int | Uguaglianza tra interi |
assert Real equals Real | Uguaglianza tra reali |
assert String equals String | Uguaglianza tra stringhe |
assert Bool is true | Il booleano è vero |
assert Bool is false | Il booleano è falso |
Struttura dei test:
import test
fn :: main => ()
main =
describe "Arithmetic" does
test "sum adds correctly" does
assert (sum 3 4) equals 7
test "factorial base case" does
assert (factorial 0) equals 1
Output sul terminale:
═══ Arithmetic ═══
● sum adds correctly
✓ PASS
● factorial base case
✓ PASS
Riepilogo degli elementi sintattici
| Elemento | Sintassi | Scopo |
|---|---|---|
| Commento | -- text | Commento su una riga |
| Dichiarazione di funzione | fn :: name types => return | Dichiara la firma di tipo di una funzione |
| Definizione di funzione | name params = body | Definisce l'implementazione di una funzione |
| Dichiarazione di operatore | op :: words types words => return | Dichiara un operatore in linguaggio naturale |
| Definizione di operatore | words params words = body | Definisce l'implementazione di un operatore |
| Più valori di ritorno | => type, type | Dichiara più valori di ritorno |
| Parametri variadici | Type... | Accetta uno o più argomenti di un tipo |
| Letterale di tensore | Tensor [[1, 2], [3, 4]] | Crea un tensore N-dimensionale |
| Indicizzazione di tensore | t[0, 1] o t[0][1] | Accede agli elementi di un tensore |
| Mutazione di tensore | t[0, 1] = 5.0 | Modifica sul posto gli elementi di un tensore |
| Contrazione di Einstein | A_ij @ B_jk | Contrazione tensoriale sugli indici condivisi |
| Operazione di Hadamard | A_ij * B_ij | Aritmetica elemento per elemento con allineamento degli indici |
echo | echo "a" "b" "c" | Stampa stringhe sul terminale |
out | "text" out | Scrive una stringa sul terminale |
| Binding locale immutabile | x = expr | Associa una variabile locale immutabile (= supporta (none), |, ||) |
| Binding locale mutabile | mut x = expr | Dichiara una variabile locale mutabile (riassegnata con x = new_expr) |
| Blocco di guardie | | cond = expr | Ramo di guardia condizionale dopo = (supporta | annidati) |
| Blocco DAG parallelo | || expr -> var | Sotto-calcoli paralleli con risultati del DAG con nome |
| Blocco pipe parallelo | || expr | Esecuzione parallela che restituisce una tupla |
| Overloading degli operatori | op :: type + type => type | Sovraccarica operatori esistenti per nuovi tipi |
| Letterali Bool | True, False | Valori primitivi booleani |
| Operatori logici | and, or, xor, not | Operatori logici a parole, in minuscolo |
| Punto di ingresso | fn :: main => () | Punto di ingresso obbligatorio del programma |
Formattazione canonica del codice sorgente (stable format e strumenti per editor)
Solo estetica, non è un requisito del compilatore (
FR-TOOL-001): il lexer e il parser di Ridgun non sono sensibili all'allineamento orizzontale in colonne. Perridgunc, i file sorgente.rgnon allineati e quelli allineati in colonne sono equivalenti al 100%. Le regole seguenti definiscono il layout estetico canonico applicato dastable formate dalla modalitàridgun-modedi GNU Emacs (C-c C-f).
- Gruppo di allineamento per firma (
fn ::/op ::):
- Ogni dichiarazione
fn ::oop ::apre un gruppo di allineamento che comprende la sua firma, le sue clausole di pattern matching e i suoi binding|| expr -> var, e che termina alla dichiarazione di livello superiore successiva (fn,op,type,import,global,context,shared,hardware,@).
- Allineamento in colonne dei token del lato sinistro ($C_0 = 6$):
fn ::eop ::sono larghi 6 caratteri, quindi il token $0$ del lato sinistro della firma inizia alla colonna $C_0 = 6$.- Ogni slot di token $i \in [0, N-1]$ prima di
=>/->(trattando"...",'...',[...]e(...)come token singoli) ha larghezza $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 inizia alla colonna $C_i = C_{i-1} + W_{i-1} + 1$. I pattern di clausola più larghi di18caratteri sono trattati come valori anomali: non allargano $W_i$ per il resto del gruppo e semplicemente sforano sulla propria riga con 1 spazio. - Le clausole di definizione corrispondenti sono indentate alla colonna $6$ ($C_0$), così il nome della funzione o le parole dell'operatore e ogni pattern degli argomenti si allineano verticalmente sotto gli slot corrispondenti della firma.
- Allineamento verticale di
=>,=e->($C_{\text{arrow}}$):
- Siano $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$.
- Nella firma,
=>si colloca a $C_{\text{arrow}}$ e il tipo di ritorno a $C_{\text{ret}}$. - Nelle clausole su una sola riga (
<lhs> = <rhs>), nelle clausole a blocco su più righe (<lhs> =) e nelle clausole che iniziano con guardie (<lhs> =seguito da| cond = ...),=si colloca a $C_{\text{arrow}}$ (allineato direttamente sotto=>); nelle clausole su una sola riga,<rhs>inizia a $C_{\text{ret}}$. - Nei binding del DAG parallelo (
|| <lhs> -> <rhs>),||inizia alla colonna $8$ (6 + 2),->si colloca a $C_{\text{arrow}}$ e<rhs>inizia a $C_{\text{ret}}$. - Ripiego compatto (
> 4parametri o> 80colonne): se una dichiarazione ha più di4slot di parametro o la sua riga di firma allineata supererebbe gli80caratteri, il riempimento orizzontale in colonne viene disattivato per quel gruppo e i token vengono separati da un solo spazio (" "), mantenendo l'indentazione delle clausole alla colonna 6.
- Indentazione strutturale (scostamento di 2 spazi):
- Dichiarazioni di livello superiore (
fn,op,type,import,global,context,shared,hardware,@): colonna0. - Clausole di funzione o di operatore sotto una firma: colonna
6. - Guardie (
| cond = expr) e pipe paralleli (|| expr/|| expr -> var):prev_indent + 2(colonna8dentro una clausola, oppureindent + 2dentro un binding locale o una guardia annidata). - Righe del corpo di un blocco sequenziale che seguono una riga che termina con
=:prev_indent + 2.
Operazioni di ordine superiore su collezioni, sequenziali e parallele (FR-COMP-001, FR-COMP-014)
Ridgun fornisce i combinatori di collezioni sequenziali standard (map, filter, fold) insieme a combinatori paralleli multicore espliciti con il prefisso || (||map, ||filter). Tutti e cinque i combinatori supportano sia la sintassi idiomatica a pipeline/infissa da sinistra a destra (xs map f -> filter g -> fold 0 h -> res) sia la sintassi prefissa tradizionale (map f xs):
| Funzione integrata | Forme prefisse e di pipeline | Modello di esecuzione |
|---|---|---|
map | xs map f / xs -> map f / map f xs | Sequenziale: valuta f x rigorosamente da sinistra a destra sul thread chiamante. |
| ` | map` | |
filter | xs filter p / xs -> filter p / filter p xs | Sequenziale: valuta il predicato p x rigorosamente da sinistra a destra sul thread chiamante. |
| ` | filter` | |
fold | xs fold init f / xs -> fold init f / fold f init xs | Fold sinistro sequenziale: accumula acc = f acc x da sinistra a destra partendo da init. |
I/O di file verbatim, flussi di byte e funzioni POSIX integrate (FR-VM-001, FR-VM-002)
- I/O verbatim di file e flussi di byte (
FR-VM-001):
read file : String => Stringlegge il contenuto dei file verbatim (preservando\r\ne l'esatta presenza o assenza dell'a capo finale, e senza mai eseguire il preprocessoreimport.rgdel compilatore sui file di dati).load rg source : String => Stringè usato esclusivamente dalla toolchain del compilatore quando carica file sorgente.rgcon l'inserimento degliimport.read bytes : String => [Int]ewrite bytes : String [Int] => ()leggono e scrivono flussi di byte binari grezzi (0..255, compresi0x00NUL e0x80..0xFF).
- Funzioni integrate POSIX per filesystem, processi e sicurezza (
FR-VM-002):
- Filesystem:
list dir,file lstat,sha256 file,sys mkdir,sys chmod. - Processi e ambiente:
sys exec,sys chdir,sys getcwd,sys getenv,sys setenv,sys exit. - Sicurezza e separazione dei privilegi:
sys getuid,sys getgid,sys username,sys user groups,sys lookup uid gid,sys setresgid,sys setgroups,sys setresuid,sys execve,sys read pass.
- I/O PAL multipiattaforma, polling, primitive di processo, ADT di destinazione e quote dichiarative
with timeout(FR-COMP-007):
- ADT predichiarati della piattaforma di destinazione:
type SysOs = Linux | MacOS | Windows(sys os => SysOs, estendibile tramiteexpand type SysOs = ...)type SysArch = X86_64 | Arm64(sys arch => SysArch, estendibile tramiteexpand type SysArch = ...)- Letterali con unità di durata: i letterali interi accettano i suffissi
_ms(* 1),_s(* 1000) e_min(* 60000) (ad es.500_ms,5_s,2_min), che si affiancano ai suffissi delle unità di byte (_KB,_MB,_GB). - Livello 1 — Primitive PAL multipiattaforma per le librerie di sistema
.rglib(posix.rglib/io.rglib): sys os => SysOsesys arch => SysArch(ADT somma del sistema operativo e dell'architettura di destinazione, in fase di compilazione)sys open mode String String => Int(path, modalità portabile"r","w","a","rw","w+","a+"-> descrittore/handle>= 0o-errno, tradotta per ogni sistema operativo di destinazione)sys open String Int Int => Int(path,flagsnormalizzati della PAL di Ridgun (0= read,1= write,2= rdwr,64= create,512= trunc,1024= append,2048= nonblock),mode->fd >= 0o-errno)sys close Int => Int(fd->0o-errno)sys read fd Int Int => String(fd,max_bytes-> stringa letta dafd)sys write fd Int String => Int(fd,data-> byte scritti o-errno)sys poll Int Int Int => Int(fd,eventsnormalizzati della PAL di Ridgun (1=POLLIN/lettura,4=POLLOUT/scrittura),timeout_ms-> revents> 0quando è pronto,0allo scadere del timeout,< 0in caso di-errno; usasys remaining msquandotimeout_ms < 0dentrowith timeout)sys terminate Int Bool => Int(pid,force-> terminazione portabile del processo, ordinata o forzata) esys kill Int Int => Int(pid, segnale normalizzato ->0o-errno)sys call Int Int... => Int(nrpiù0..6argomentia1..a6-> via di fuga di basso livello verso le syscall dell'ABI del sistema operativo di destinazione)sys time ms => Intesys sleep ms Int => ()(orologio monotono in millisecondi e attesa)sys deadline ms => Intesys remaining ms => Int(scadenza del timeout lessicale attivo e millisecondi rimanenti, oppure-1quando non c'è limite)- Livello 2 — Quota di esecuzione dichiarativa
with timeout <duration> do: - Come
with region 64_KB doimposta una quota lessicale di byte,with timeout 500_ms do(o la forma combinatawith region 64_KB, timeout 500_ms do) imposta una scadenza lessicale di esecuzione (sys time ms + duration_ms) sul contesto del thread corrente e ripristina la scadenza precedente all'uscita dal blocco:
with timeout 500_ms do
rem = sys remaining ms
rc = sys poll fd 1 rem
rc
- Consulta
docs/lang/pal-and-system-libraries.mdper la specifica completa dell'architettura della PAL e delle librerie di sistema (.rglib).
- Compilazione di librerie riutilizzabili (
.rglibe ELF64.so) estableestendibile (FR-COMP-006,FR-TOOL-003):
ridgunc build --lib <file.rg> -o <out.rglib>eridgunc build --so <file.rg> -o <lib.so>(oppurestable built lib/stable new --lib <name>) compilano moduli senza richiederefn :: main => ()e conservano tutte le funzioni, gli operatori e i tipi esportati.stablerileva i comandi personalizzatifn :: task <words...> => Stringe le estensionifn :: plugins => [String](plugins/*.rg,.stable/plugins/*.rg), elencati tramitestable tasksestable plugins.
Gestione della memoria di Ridgun (RMM): RMM1 (CMM) e RMM2 (FR-COMP-002)
Ridgun gestisce la memoria heap (String, [a], (a, b), ADT, Tensor) in modo deterministico, senza un garbage collector (GC) a tracciamento, tramite la Ridgun Memory Management (RMM):
- Politica
RMM1— Gestione della memoria compilata (CMM lineare + condizionale):
- Si applica quando il flusso di controllo e le funzioni chiamate sono noti staticamente in fase di compilazione, su binding non annidati:
- CMM lineare (
rg_cmm_move/rg_cmm_drop): il compilatore determina in fase di compilazione l'istruzione esatta dell'ultimo uso di ogni variabile locale, sposta le variabili restituite (rg_cmm_move(&mut lv_x)) ed emette una deallocazione incondizionata (rg_cmm_drop(&mut lv_x)) subito dopo l'ultimo uso di una variabile, quando non restano alias possibili ancora vivi. - CMM condizionale (
rg_cmm_drop_unless/ptr(a) != ptr(c)): quando una chiamata con diramazione statica (c = choose list cond a b) restituisce uno tra diversi input candidati e uno o più candidati restano vivi insieme ac, il compilatore costruisce la tabella degli alias possibili degli antenati (RCmmAlias) ed emette una guardia di identità dei puntatori da 1 ciclo nell'istruzione statica dell'ultimo uso di ogni variabile:
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)
- Politica
RMM2— Conteggio sulle assegnazioni e a cascata sulle strutture (politica attiva):
- Unifica la gestione della memoria tra funzioni, ritorni con diramazione (
choose list), contenitori annidati (xs = [a, b, ..., z],xs[3] = a), variabili locali mutabili (mut x = ...) ed esecuzione multi-thread: - Scalari primitivi (
Int,Real,Bool,()): non vengono mai contati; si copiano per valore sullo stack o nei registri. - Argomenti in ingresso della funzione (
pats): non vengono mai toccati (+0all'ingresso,-0all'uscita). - Variabili locali interne (
x = .../mut x = ...): ricevono+1all'assegnazione (x = expr,xs[3] = a). Nella riassegnazione mutabile (x = new_expr),new_exprriceve+1prima che la vecchiaxriceva-1. - Fine della funzione / uscita dallo scope:
- Le variabili locali interne non restituite ricevono prima
-1: serc == 0, il blocco padre viene liberato e propaga a cascata-1, una alla volta, a ciascuna struttura figlia diretta (liberando ricorsivamente ogni figlia il cuircarriva a0). - Una variabile locale interna restituita (
return a) riceve-1(oppure viene spostata tramiterg_cmm_move) e, anche se il suorcè0durante il transito, viene risparmiata, così l'assegnazione del chiamante (c = make pair ()) aggiunge+1(0 + 1 = 1). Quando viene restituito un argomento in ingresso (choose list cond a b), questo non è stato toccato dentro la funzione (rc = 1), quindi l'istruzionec = choose list cond a bdel chiamante aggiunge+1(1 + 1 = 2, condiviso traaec).
- Politica
RMM3(riservata):
- Riservata a una successiva politica aggressiva di ottimizzazione statica.
- Consulta
docs/etc/cmm_architecture.md(nel repository di Ridgun) per la specifica completa diRMM1eRMM2.