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 esaminabile
Repository GitLab Vedi la sintassi

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 dato mancante è N e si propaga (V ^ 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{ k=20 } = 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.

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 = N — resta "non esaminabile",
# non collassa comunque a "no"

Un filtro booleano a due valori confonde "risulta falso" con "non è nemmeno valutabile": undefined, null e false finiscono nello stesso ramo. La AND trivalente di ONTO tiene i tre casi separati fino in fondo, per costruzione.

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.

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, mediati. Riproducibili con onto-cli bench.

Query (500k record)ONTOSQLitescan naivesled (KV)ONTO vs SQLite
Costruzione indice180533231194461,8× più veloce
Q1 — scan singolo (febbre=V)3,8520,247,56,595,3×
Q2 — AND di 3 proprietà1,3656,259,957,641×
Q3 — AND di 6 proprietà (profilo)1,0864,353,774,360×
Q4 — propagazione lazy di N2,10127,148,890,860×
Q5 — distribuzione V/F/N0,01314,549,810,0~1100×
Q7 — top-20 per punteggio6,38147,436,01006,923×
Q8 — scan a soglia di punteggio2,96393,329,8566,3133×

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 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 propagarsi invece di essere forzati a un default?
Domanda 2
Le query sono congiunzioni su molte proprietà booleane o profili, anche con ranking per punteggio?
→ ONTO

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

serve SQL con join, ACID multi-operazione, accesso da più client? → PostgreSQL / MySQL
i dati sono sempre definiti → continua qui sotto
Domanda 3
Serve ricerca per similarità semantica su vettori o embedding?
→ 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?
→ 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?
→ Database documentale

MongoDB o equivalenti.

no → continua
Domanda 6
Dataset piccolo, un solo processo, query semplici, zero infrastruttura?
→ 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.

Nessuna persistenza su discoMotore in-memory: i dati vivono nel processo. Per la persistenza serve un servizio che lo wrappi (embed come libreria Rust, snapshot esterno).
Single-thread, una macchinaNessun protocollo di rete, nessuna concorrenza multi-client: scalabilità verticale, dataset che stanno in RAM.
Niente transazioni ACID né SQLNessun join generalista, nessuna garanzia multi-operazione: per transazioni bancarie o relazioni complesse → PostgreSQL.
Indice solo su V/F/NLe proprietà trivalenti sono indicizzate; stringhe e numeri restano nel linguaggio (accessibili con .campo, non interrogabili nel motore).