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{ 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.
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 = 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.
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.
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, mediana di 7 esecuzioni.
Riproducibili con onto-cli bench.
| Query (500k record) | ONTO | SQLite | scan naive | sled (KV) | ONTO vs SQLite |
|---|---|---|---|---|---|
| Costruzione indice | 1789 | 3508 | 193 | 484 | 2,0× più veloce |
| Q1: scan singolo (febbre=V) | 3,87 | 22,4 | 48,4 | 8,2 | 5,8× |
| Q2: AND di 3 proprietà | 1,40 | 57,3 | 50,6 | 69,3 | 41× |
| Q3: AND di 6 proprietà (profilo) | 1,10 | 55,9 | 43,9 | 88,7 | 51× |
| Q4: AND trivalente (lazy) | 3,39 | 188,4 | 56,8 | 157,1 | 56× |
| Q5: distribuzione V/F/N | 0,017 | 15,8 | 55,4 | 11,0 | ~950× |
| Q7: top-20 per peso epistemico | 8,36 | 155,9 | 37,9 | 1089,0 | 19× |
| Q8: scan a soglia di peso epistemico | 3,76 | 408,6 | 38,4 | 589,5 | 109× |
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.
Parti dalla prima domanda e segui i rami: la foglia è la raccomandazione.
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.
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.
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.--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.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.Guida all'uso, riferimento del linguaggio, logica ontologica e fondamenti teorici.
Compilazione, primo script, comandi della CLI e della REPL.
Riferimento della sintassi: valori, connettivi, elementi, classi, funzioni, interrogazioni del motore.
Tavole canoniche, principio di identità, leggi a confronto con la logica booleana, metalogica e modalità.
Moduli, indice bitset trivalente, interprete, persistenza.
Metodologia e risultati completi del confronto con SQLite, sled e scan lineare.
Il saggio sul paradigma orientato alle proprietà e i suoi ambiti applicativi.