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.
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.
Una classe è un selettore intensionale sul motore: ogni elemento che manifesta quelle proprietà ne è membro — anche se inserito dopo la definizione della classe.
Le classi sono elementi first-class: si aggregano con altre classi e proprietà, formando aggregati mereologici senza limite di profondità.
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.
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.
// 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");
# 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.
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
[ 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.
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"
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.
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.
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.
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.
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) | ONTO | SQLite | scan naive | sled (KV) | ONTO vs SQLite |
|---|---|---|---|---|---|
| Costruzione indice | 1805 | 3323 | 119 | 446 | 1,8× più veloce |
| Q1 — scan singolo (febbre=V) | 3,85 | 20,2 | 47,5 | 6,59 | 5,3× |
| Q2 — AND di 3 proprietà | 1,36 | 56,2 | 59,9 | 57,6 | 41× |
| Q3 — AND di 6 proprietà (profilo) | 1,08 | 64,3 | 53,7 | 74,3 | 60× |
| Q4 — propagazione lazy di N | 2,10 | 127,1 | 48,8 | 90,8 | 60× |
| Q5 — distribuzione V/F/N | 0,013 | 14,5 | 49,8 | 10,0 | ~1100× |
| Q7 — top-20 per punteggio | 6,38 | 147,4 | 36,0 | 1006,9 | 23× |
| Q8 — scan a soglia di punteggio | 2,96 | 393,3 | 29,8 | 566,3 | 133× |
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.
Parti dalla prima domanda e segui i rami: la foglia è la raccomandazione.
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.
Qdrant, pgvector, Milvus — indicizzazione ANN su embedding.
Cache e strutture dati in memoria con protocollo di rete.
MongoDB o equivalenti.
Embedded, transazionale, onnipresente.
ONTO non è un RDBMS generalista: è un motore specializzato. Sceglierlo per il compito sbagliato è un errore — il diagramma sopra esiste per questo.
.campo, non interrogabili nel motore).