GitLab

V · F · N

ONTO è un linguaggio e un motore database in cui non esistono classi dichiarate a priori: esistono solo proprietà. Gli elementi si aggregano mereologicamente e formano le classi, che sono a loro volta elementi, aggregabili senza limite. Con logica trivalente nativa.
V = veroF = falsoN = non apofantico
Repository GitLab Vedi la sintassi Documentazione

Il paradigmamereologia applicata ai dati

Nella programmazione orientata agli oggetti la classe esiste prima degli elementi e li vincola. In ONTO è il contrario: la classe è il risultato di un'aggregazione di proprietà, e la membership si calcola sul motore, non si dichiara.

[ V=febbre, "Mario"=nome ]

Elementi, non record

Un elemento è un aggregato libero di proprietà: nasce dalle sue proprietà, non da uno schema. I campi con valore V/F/N entrano automaticamente nell'indice trivalente.

class { V=febbre ^ V=tosse }

Classi formate, non dichiarate

Una classe è un selettore intensionale sul motore: ogni elemento che manifesta quelle proprietà ne è membro, anche se inserito dopo la definizione della classe.

class { A ^ B } = C

Mereologia di ordine superiore

Le classi sono elementi first-class: si aggregano con altre classi e proprietà, formando aggregati mereologici senza limite di profondità.

OOP: la classe viene prima

  • Classe dichiarata a priori con attributi
  • L'oggetto è un'istanza di (instanceof)
  • Il dato mancante diventa null o default
  • Ogni nuova categoria richiede una migrazione di schema
⟲

ONTO: gli elementi vengono prima

  • Solo proprietà: niente schema, niente attributi
  • L'elemento entra nella classe se le proprietà lo sostengono
  • Il valore indeterminato è N ed entra nel ragionamento (V ^ N = F, F ^ N = N)
  • Nuova categoria = nuovo selettore, i dati non si toccano

La sintassidichiarazione rovesciata: valore = nome

Si dichiara prima cosa è la cosa, poi come si chiama, come in matematica.

# Elemento: un aggregato libero di proprietà
[ V=febbre, V=tosse, "Mario"=nome ] = mario
[ V=febbre, F=tosse ] = anna

# Classe: si forma dagli elementi, dinamica (lazy)
class { V=febbre ^ V=tosse } = Febbricitanti

# Congelamento esplicito quando serve uno snapshot
class fixed { V=febbre } = FebbreIniziale
# La classe è un elemento: aggregazione di classi
class { Febbricitanti ^ F=dispnea } = PortatoriSani

# Membership e iterazione
if (mario in Febbricitanti) { "febbricitante" : print }
for id in Febbricitanti { id : print }

# Interrogazione del motore trivalente
count{ Febbricitanti=class } = n : int
search_profile{ V=febbre, V=tosse } = ids : list
top_k{ 20=k } = migliori : list

La negazione è asimmetrica (!!V = N ≠ V): l'incertezza, una volta introdotta, non si annulla automaticamente. La condizione N in un if non esegue nessun ramo: non è che manchino dati da raccogliere, è che quella proprietà non è del tipo valutabile come vero/falso, non è esaminabile in linea di principio.

La congiunzione è una compressione: il vero si ottiene solo da V ^ V. Un valore indeterminato non conferma (V ^ N = F), mentre un requisito mancato insieme a uno indeterminato resta in verifica (F ^ N = N). Oltre a ^ e v il linguaggio ha l'implicazione =>, la coimplicazione <=> e la disgiunzione esclusiva xor, tutte con le tavole della logica ontologica.

Quando serve ragionare come in logica classica c'è la negazione stretta ~ (~V = F, ~F = V, ~N = V): la logica classica è una porzione di quella ontologica. Tavole, leggi e proprietà del sistema sono nella documentazione sulla logica; la sintassi completa nel riferimento del linguaggio.

Usato come libreria Rust, il motore espone anche la mutazione dei record: set_property aggiorna una singola proprietà, retract è una cancellazione logica che toglie l'elemento da ricerche, classifiche e classi. search_any esegue l'OR deduplicato tra più profili, sempre sui bitset.

Dalla classe alla proprietàtre scenari, riga per riga

Stessa esigenza, due modi di scriverla. Passa il mouse (o tocca, su schermo touch) su una riga evidenziabile: si accende anche la riga corrispondente dall'altra parte.

OOP (TypeScript)
// 1. si dichiara PRIMA la forma...
class Paziente {
  febbre: boolean;
  tosse: boolean;
  nome: string;
  constructor(f, t, n) {
    this.febbre = f; this.tosse = t; this.nome = n;
  }
}
// 2. ...POI si crea l'istanza
const mario = new Paziente(true, true, "Mario");
⇒
ONTO
# un'unica riga: forma e valore nascono assieme
[ V=febbre,
  V=tosse,
  "Mario"=nome ] = mario

In OOP la forma (la classe) precede sempre il valore (l'istanza): due dichiarazioni separate, uno schema da mantenere. In ONTO non c'è schema da dichiarare: il nome della proprietà è il suo stesso valore rovesciato, e l'elemento nasce nell'atto di elencare ciò che ha.

OOP (TypeScript)
class Animale {}
class Mammifero extends Animale { pelo = true; }
class Cane extends Mammifero { abbaia = true; }
const fido = new Cane();
// fido instanceof Animale: FISSATO per sempre
// alla dichiarazione della classe, non ai dati
⇒
ONTO
[ V=pelo, V=abbaia ] = fido

class { V=pelo } = Mammiferi
class { V=abbaia } = Cani
# la classe-aggregato: si forma DA altre classi,
# non le contiene ad albero, le interseca
class { Mammiferi ^ Cani } = CaniDomestici

In OOP l'albero Animale → Mammifero → Cane è una catena di contenimento a genitore unico, scritta una volta per sempre. In ONTO CaniDomestici non "contiene" Mammiferi e Cani: è ciò che risulta dalla loro intersezione, e può a sua volta intersecarsi con altro. È lo stesso principio illustrato nel diagramma qui sotto.

OOP (filtro imperativo)
const febbricitanti = pazienti.filter(p =>
  p.febbre === true && p.tosse === true
);
// se p.febbre è undefined, il confronto
// === true è falso: l'incertezza SPARISCE,
// diventa indistinguibile da "no"
⇒
ONTO
class { V=febbre ^ V=tosse } = Febbricitanti
for id in Febbricitanti { id : print }

# se febbre è N (non esaminabile, non "non ancora esaminata"):
# N ^ V = F: la congiunzione non è confermata,
# ma l'elemento conserva N, e F ^ N = N:
# "no" e "indeterminato" restano due casi

Un filtro booleano a due valori confonde "risulta falso" con "non è nemmeno valutabile": undefined, null e false finiscono nello stesso ramo. In ONTO il dato non esaminato non conferma la congiunzione (V ^ N = F), ma non si perde: l'elemento conserva N sulla proprietà, e F ^ N = N lascia in verifica ciò che un booleano darebbe per negato.

Aggregati mereologiciil modello a semivettori intersecanti

Ogni proprietà è rappresentata come un semivettore: un segmento con un'origine etichettata (il nome della proprietà) che punta verso un punto d'incontro. Quando due semivettori si intersecano, il punto d'incontro è una nuova proprietà, non un contenitore delle due, ma il loro aggregato. Da lì riparte un nuovo semivettore, pronto a intersecarsi con il successivo. Nessun passaggio ha bisogno di sapere quanti ce ne saranno dopo.

L'intersezione non è un AND logico (∧): un AND si limita a chiedere se entrambe le proprietà valgono, senza produrre nulla di nuovo. L'aggregazione mereologica (⊕) è mediata da un metodo, nello stesso senso in cui, in fisica, il risultato di un processo non è la somma grezza delle risorse impiegate, ma dipende da come quelle risorse vengono combinate. Il punto d'incontro non risponde a "valgono entrambe?": è ciò che il metodo produce combinandole.

Il concetto che emerge si scrive con ▷, «è aggregato da»: palla ▷ (la ^ rossa). Non esistono insiemi che contengono elementi, esistono proprietà che si aggregano. È il MOVP, Mappatore Ontologico Vettoriale di Proprietà: C = MOVP({Pᵢ}) = ⨁ᵢ Pᵢ, dove le Pᵢ sono i semivettori e C il concetto.

Diagramma di aggregazione mereologica a semivettori Due proprietà iniziali si intersecano formando "Febbricitanti"; il risultato si interseca con "F=dispnea" formando "PortatoriSani"; il risultato si interseca con "V=fedele" formando "ClientiPremium", con una freccia tratteggiata che indica come la catena possa proseguire senza limite. V=febbre V=tosse Febbricitanti F=dispnea PortatoriSani V=fedele ClientiPremium e così via…

Albero (OOP)

Animale Mammifero Uccello Cane Gatto

Un solo genitore per nodo, fissato alla dichiarazione. Un ramo non può "salire" verso due antenati né fondersi con un ramo vicino: la forma è decisa una volta e vincola tutto ciò che verrà dopo.

Catena mereologica (ONTO)

A B C A⊕B A⊕B⊕C

Nessun nodo è "il genitore": ogni intersezione è alla pari con le altre, mediata da un metodo, non da un AND, e il suo risultato è a sua volta un ingrediente pronto per la prossima. La catena si estende quanto serve, senza dover essere prevista in anticipo.

BenchmarkN = 500 000 record · 8 proprietà trivalenti

Dataset sintetico (55% V / 30% F / 15% N, seed fisso), motore ONTO in memoria contro SQLite (rusqlite), scan naive e sled (proxy di Redis). Tempi in millisecondi, mediana di 7 esecuzioni. Riproducibili con onto-cli bench.

Query (500k record)ONTOSQLitescan naivesled (KV)ONTO vs SQLite
Costruzione indice178935081934842,0× più veloce
Q1: scan singolo (febbre=V)3,8722,448,48,25,8×
Q2: AND di 3 proprietà1,4057,350,669,341×
Q3: AND di 6 proprietà (profilo)1,1055,943,988,751×
Q4: AND trivalente (lazy)3,39188,456,8157,156×
Q5: distribuzione V/F/N0,01715,855,411,0~950×
Q7: top-20 per peso epistemico8,36155,937,91089,019×
Q8: scan a soglia di peso epistemico3,76408,638,4589,5109×

ONTO vince su tutte le query, a tutte le N testate (1k → 500k)  Il vantaggio cresce con il numero di proprietà congiunte e con N: l'AND trivalente è una operazione bitwise su bitset (O(n/64)), il ranking usa scoring incrementale. Su Q7/Q8 sled crolla (1,1 s / 0,6 s) perché senza bitset posizionali deve materializzare gli insiemi lato applicazione.

Quando sceglierlodiagramma decisionale

Parti dalla prima domanda e segui i rami: la foglia è la raccomandazione.

Domanda 1
I dati hanno valori mancanti o indeterminati che devono entrare nel ragionamento invece di essere forzati a un default?
SÌ
Domanda 2
Le query sono congiunzioni su molte proprietà booleane o profili, anche con ranking per peso epistemico?
SÌ
→ ONTO

Motore trivalente in memoria. AND multi-proprietà e ranking 19–109× più veloci di SQLite a 500k record, distribuzione V/F/N ~950×, membership lazy senza migrazioni di schema. Casi: triage con referti parziali, scoring con segnali incerti, classificazioni dinamiche di inventario, knowledge graph di relazioni tipate.

servono vincoli relazionali tra tabelle e molte scritture concorrenti sugli stessi dati? → PostgreSQL / MySQL
i dati sono sempre definiti → continua qui sotto
Domanda 3
Serve ricerca per similarità semantica su vettori o embedding?
SÌ
→ Motori vettoriali

Qdrant, pgvector, Milvus. Indicizzazione ANN su embedding.

no → continua
Domanda 4
Serve solo una cache chiave-valore ad altissima velocità, con TTL e scadenze?
SÌ
→ Redis

Cache e strutture dati in memoria con protocollo di rete.

no → continua
Domanda 5
Dati fortemente gerarchici o documenti JSON con schema flessibile e ricerca su campi annidati?
SÌ
→ Database documentale

MongoDB o equivalenti.

no → continua
Domanda 6
Dataset piccolo, un solo processo, query semplici, zero infrastruttura?
SÌ
→ SQLite

Embedded, transazionale, onnipresente.

no → PostgreSQL / MySQL (RDBMS generalista con ACID, rete e concorrenza)

Limiti onesticosa ONTO non è

ONTO non è un RDBMS generalista: è un motore specializzato. Sceglierlo per il compito sbagliato è un errore. Il diagramma sopra esiste per questo.

Dati ripartiti: in un join una sola tabella resta dov'èonto route ripartisce i dati tra gruppi di nodi: scritture «tutti o nessuno» che sopravvivono alla caduta di un nodo, campi unici tra tutti i gruppi, e onto split che aggiunge un gruppo senza fermare il servizio. In un join la tabella più grande resta nei gruppi, che la uniscono alle altre e restituiscono solo il risultato; le altre tabelle vanno raccolte e date a ogni gruppo.
Oltre la RAM: una lettura sparsa costa un accesso al discoCon --memory-file la memoria sta su un file e il limite diventa il disco. Con 175 MB di dati e 35 MB di RAM il carico impiega 32 secondi invece di 21 e scorrere tutta una tabella 6 secondi. Un elemento che non sta in memoria va letto dal disco: circa 0,08 secondi l'uno, se letti in punti sparsi.
SQL con tabelle, viste e chiavi esterne; non le procedure di PostgreSQLONTO esegue SQL con join, sottointerrogazioni, WITH RECURSIVE, funzioni a finestra, liste e transazioni, anche dai client di PostgreSQL. CREATE TABLE impone tipi, NOT NULL, chiavi e REFERENCES; ci sono le viste, e in psql funzionano \d, \dt e \dv. Mancano chiavi su più colonne, vincoli CHECK, procedure ed estensioni; date e orari sono testi.

Documentazioneguida, logica, teoria

Guida all'uso, riferimento del linguaggio, logica ontologica e fondamenti teorici.

Indice completo della documentazione →