← ONTO GitLab

Potenzialità del Paradigma POP — Ambiti Applicativi

Dove ONTO supera la Programmazione a Oggetti


Premessa: perché il confronto ha senso

Il confronto tra POP e OOP non è una gara di paradigmi generici. OOP eccelle in sistemi con identità di lungo periodo, stato condiviso e gerarchie stabili (UI, game engine, middleware). POP supera OOP negli ambiti dove:

  1. Le entità sono descritte meglio da proprietà che da comportamenti
  2. I valori indeterminati non sono eccezioni, ma la norma
  3. La composizione è più naturale dell'ereditarietà
  4. La logica è separabile dai dati per natura del problema

I cinque domini implementati mostrano questi vantaggi in contesti reali.


Dominio 1 — Diagnosi Medica (diagnosi.onto)

Il problema OOP

python
class Paziente:
    def __init__(self):
        self.febbre = None        # None = valore non disponibile
        self.tosse = None
        self.perdita_olfatto = None
    
    def ha_covid(self):
        if self.febbre is None or self.tosse is None:
            return "INCOMPLETE"   # caso speciale hardcoded
        if self.perdita_olfatto is None:
            return "UNCERTAIN"    # altro caso speciale
        return self.febbre and self.tosse and self.perdita_olfatto

Con 8 sintomi, ci sono 2⁸ = 256 combinazioni di null da gestire. La logica medica viene sepolta nei null-check.

La soluzione POP

onto
[ "Mario Rossi"=nome, V=febbre, V=tosse, V=perdita_olfatto ] = mario
[ "Anna Bianchi"=nome, V=febbre, V=tosse, N=perdita_olfatto ] = anna

def profilo_covid { p: element } -> {
    p.febbre ^ p.tosse = asse_resp : bool
    asse_resp ^ p.perdita_olfatto = diagnosi : bool
    return diagnosi
}

profilo_covid{ mario=p } = esito_mario : bool   # V
profilo_covid{ anna=p } = esito_anna : bool     # F: non confermato

Il paziente non è un'istanza di una classe dichiarata prima: è l'aggregato delle sue proprietà. V = sintomo presente, F = assente, N = indeterminato.

La logica trivalente gestisce automaticamente tutti i casi:

  • V ^ N = F → "sintomo presente e altro indeterminato → diagnosi non confermata"
  • F ^ N = N → "sintomo assente e altro indeterminato → quadro indeterminato"
  • V ^ V ^ V = V → "tutti presenti → diagnosi positiva"

Zero null-check. Zero casi speciali hardcoded. La semantica medica è preservata.

Vantaggi quantificati

OOP ONTO
Casi null da gestire 2ⁿ 0 (le tavole trivalenti coprono ogni combinazione)
Aggiungere un sintomo Modifica classe + tutti i metodi Aggiungere una proprietà all'elemento
Significato di "dato mancante" Tecnico (null) Semantico (N = indeterminato, non conoscibile direttamente)
Risultato incerto Caso speciale hardcoded Valore nativo (N)

Altri ambiti medici applicabili

  • Triage ospedaliero: priorità di accesso emergenza per profilo-sintomi
  • Interazioni farmacologiche: ogni farmaco come aggregato di proprietà, controindicazioni come congiunzioni
  • Genetica clinica: varianti genotipiche come proprietà, fenotipo come aggregazione
  • Cartella clinica elettronica: V/F/N per ogni screening — mai più NULL in PostgreSQL

Dominio 2 — Ontologie Semantiche (ontologia_animali.onto)

Il problema OOP: il Platipus

Il platipus è il caso-limite classico della tassonomia OOP:

java
class Mammal extends Animal {
    boolean hasFur() { return true; }
    // ...
}

class Platypus extends Mammal {
    @Override
    boolean laysEggs() { return true; }  // ← VIOLAZIONE LSP
    boolean hasVenom() { return true; }   // ← non previsto in Animal
}

Ogni entità anomala richiede un'override che viola il Liskov Substitution Principle. Le gerarchie crescono per eccezioni finché collassano.

La soluzione POP

onto
[ "Platipus"=nome, V=mammifero, V=depone_uova, V=veleno, V=nuota ] = platipus
[ "Delfino"=nome, V=mammifero, F=depone_uova, F=veleno, V=nuota ] = delfino

def è_anomalia_tassonomica { a: element } -> {
    a.mammifero ^ a.depone_uova = risultato : bool
    return risultato
}

è_anomalia_tassonomica{ platipus=a } = anomalia : bool   # V

Il platipus è semplicemente un aggregato di proprietà che non cade in nessuna categoria precostruita — e questo è corretto. Non è un'eccezione da gestire: è un'entità con una combinazione di proprietà inusuale. La classificazione emerge dalla congiunzione delle proprietà, non da una gerarchia.

Output reale

Aquila      → Uccello
Cobra       → Rettile
Delfino     → Mammifero acquatico
Lupo        → Mammifero terrestre
Platipus    → ANOMALIA TASSONOMICA (mammifero oviparo)
Pipistrello → Mammifero terrestre

Vantaggi per la knowledge engineering

  • Semantic Web / OWL: POP è il modello naturale per ontologie OWL — le classi OWL sono già definite per proprietà, non per ereditarietà
  • Knowledge graphs: entità come aggregati di (proprietà, valore), relazioni tipate tra elementi
  • Motori di ricerca semantici: ricerca per proprietà anziché per parola chiave
  • Cataloghi prodotti e-commerce: filtri multipli come congiunzione di proprietà

Interrogazione come proprietà

onto
[ "Platipus"=nome, V=mammifero, V=depone_uova, V=veleno, V=nuota ] = platipus
[ "Delfino"=nome, V=mammifero, F=depone_uova, F=veleno, V=nuota ] = delfino
[ platipus, delfino ] = fauna

# Chi ha veleno?
for animale in fauna {
    if (animale.veleno == V) { "{animale.nome}" : print }
}

# Chi nuota E è mammifero?
for animale in fauna {
    animale.nuota ^ animale.mammifero = crit : bool
    if (crit == V) { "{animale.nome}" : print }
}

In OOP, ogni query richiede un metodo. In ONTO, la query è un'espressione logica inline — le domande sono proprietà dei dati.


Dominio 3 — Gestione Configurazione (configurazione.onto)

Il problema OOP

La gestione di configurazione a strati in OOP richiede inevitabilmente uno o più pattern architetturali:

  • Builder per costruire config layer per layer
  • Prototype per clonare config base
  • Strategy per la logica di selezione
  • Factory per la creazione condizionale

Risultato: 4 pattern, 8-12 classi, centinaia di righe di boilerplate.

La soluzione POP

onto
# Ogni strato è un aggregato completo e indipendente
[ "localhost"=host, 8080=porta, V=debug, 10=max_connessioni ] = config_base
[ "api.produzione.it"=host, 443=porta, F=debug, 200=max_connessioni ] = config_prod

# Validazione come funzione sulle proprietà
def sicura_per_produzione { c: element } -> {
    if (c.debug == V) { return F }
    return V
}

sicura_per_produzione{ config_prod=c } = ok : bool   # V

# Composizione dal basso: l'aggregato nasce dalle proprietà presenti
"cache.interna"=host_cache : str
V = cache_abilitata : bool
aggregate config_cache ▷ host_cache ^ cache_abilitata

Dati (elementi) e logica (funzioni) sono separati per costruzione. Non serve un pattern: è la natura del paradigma. L'aggregazione ▷ compone e sovrascrive dal basso, senza ereditarietà.

Vantaggi per DevOps e Infrastructure as Code

  • Kubernetes ConfigMaps: ogni ConfigMap è un aggregato di proprietà
  • Terraform variables: moduli come aggregati, override come aggregati più specifici
  • Feature flags: ogni flag è una proprietà con valore V/F/N (attivo, inattivo, non deciso)
  • A/B testing: la variante B è un aggregato che sovrascrive le proprietà della variante A

Il valore di N nelle configurazioni

N come valore di una proprietà di configurazione non è un "null da gestire": dice che il valore non è determinabile dalla configurazione stessa. Una configurazione con debug=N dichiara che quella decisione non è presa a questo livello, invece di lasciare un riferimento nullo in attesa di un errore.


Dominio 4 — Analisi del Rischio di Credito (rischio_credito.onto)

Il problema OOP

python
class CreditScorer:
    def score(self, applicant):
        if applicant.income is None:
            return RiskLevel.UNKNOWN    # caso 1
        if applicant.credit_history is None:
            if applicant.collateral is None:
                return RiskLevel.UNKNOWN  # caso 2
            return RiskLevel.CONDITIONAL  # caso 3
        # ... 58 altri casi con 6 fattori nullable

Con n fattori con null, il codice ha 2ⁿ rami potenziali. Con 6 fattori: 64 rami. Con 10 fattori (reale in ambito bancario): 1024 rami. La logica di business viene soffocata dalla gestione del null.

La soluzione POP

onto
[ "Sofia"=nome, V=reddito_stabile, N=storico_pagamenti_ok, V=rapporto_debito_ok, V=garanzie_reali ] = sofia
[ "Ahmed"=nome, N=reddito_stabile, N=storico_pagamenti_ok, N=rapporto_debito_ok, F=garanzie_reali ] = ahmed

def rischio_primario { p: element } -> {
    p.reddito_stabile ^ p.storico_pagamenti_ok = base : bool
    base ^ p.rapporto_debito_ok = rischio : bool
    return rischio
}

def valuta_domanda { p: element } -> {
    rischio_primario{ p=p } = rp : bool
    p.garanzie_reali ^ p.reddito_stabile = rs : bool
    if (rp == V) { return "APPROVATO" }
    if (rp == N) { return "IN VERIFICA" }       # distinto dal rifiuto
    if (rs == V) { return "CONDIZIONATO" }      # decidono le garanzie
    return "RIFIUTATO"
}

valuta_domanda{ sofia=p } = esito_sofia : str   # CONDIZIONATO
valuta_domanda{ ahmed=p } = esito_ahmed : str   # IN VERIFICA

La distinzione semantica che cambia tutto

Situazione OOP (null) ONTO (N)
Reddito stabile, storico indeterminato null → spesso trattato come rifiuto V ^ N = F: il test primario non è affermato, decidono le garanzie
Cliente nuovo, nessun requisito determinabile null → rifiuto per dati mancanti (N ^ N) ^ N = N: la domanda resta in verifica
Tutti i requisiti affermati approvazione V ^ V = V: approvazione

Un richiedente senza storico non è rifiutato per il dato mancante. Se gli altri requisiti reggono passa al test sulle garanzie; se nessun requisito è determinabile resta in verifica, che è un esito diverso dal rifiuto. I tre casi sono distinti dalle tavole, senza rami scritti a mano.

Applicazioni reali nel settore finanziario

  • AML (Anti-Money Laundering): transazioni con proprietà N = sotto monitoraggio, non bloccate a priori
  • KYC (Know Your Customer): ogni documento verifica è una proprietà V/F/N
  • Insurtech: underwriting di polizze con dati parziali del cliente
  • Microfinance: scoring per clienti non bankable in mercati emergenti
  • ESG scoring: fattori ambientali/sociali spesso non misurabili → N nativo

Dominio 5 — Sistema di Raccomandazione (raccomandazione.onto)

Il problema OOP

python
class RecommendationEngine:
    def similarity(self, user: User, item: Item) -> float:
        vector_user = user.to_vector()   # [1, 0, 1, 1, ...]
        vector_item = item.to_vector()   # [1, 1, 0, 1, ...]
        return cosine_similarity(vector_user, vector_item)

Aggiungere una nuova dimensione (breve, in_italiano, con_mentore) richiede:

  1. Modificare User.to_vector()
  2. Modificare Item.to_vector()
  3. Ritraining del modello se ML-based
  4. Migrazione dati esistenti

La soluzione POP

onto
[ "Docker in 5 ore"=titolo, V=pratico, V=online, V=gratuito ] = corso_docker
[ "Algoritmi Avanzati"=titolo, F=pratico, V=online, F=gratuito ] = corso_algoritmi
[ "Luca"=nome, V=vuole_pratico, N=vuole_online, V=vuole_gratuito ] = luca

def criterio { preferenza: bool, proprieta: bool } -> {
    # una preferenza affermata chiede la proprietà; le altre non vincolano
    if (preferenza == V) { return proprieta }
    return V
}

def match_corso_utente { c: element, u: element } -> {
    criterio{ u.vuole_pratico=preferenza, c.pratico=proprieta } = m1 : bool
    criterio{ u.vuole_online=preferenza, c.online=proprieta } = m2 : bool
    criterio{ u.vuole_gratuito=preferenza, c.gratuito=proprieta } = m3 : bool
    m1 ^ m2 = parziale : bool
    parziale ^ m3 = esito : bool
    return esito
}

match_corso_utente{ corso_docker=c, luca=u } = m_docker : bool        # V
match_corso_utente{ corso_algoritmi=c, luca=u } = m_algoritmi : bool  # F

L'utente e il corso sono aggregati di proprietà: è l'aggregazione delle proprietà a formare il concetto, come nel MOVP (Mappatore Ontologico Vettoriale di Proprietà). Aggiungere una dimensione significa aggiungere una proprietà agli elementi e un criterio alla funzione.

Il significato del risultato

Esito Significato
V (adatto) Tutte le preferenze affermate sono soddisfatte
F (non adatto) Almeno una preferenza affermata non è soddisfatta

Una preferenza N non vincola: non conta come richiesta e non conta come rifiuto.

Vantaggio chiave: zero cold start problem

In OOP/ML, un utente senza storico non può essere raccomandato (cold start). In ONTO, le preferenze N non penalizzano: il match si basa solo sulle preferenze affermate. Un utente nuovo con una sola preferenza ottiene raccomandazioni semanticamente corrette dal primo giorno.

Applicazioni

  • E-learning: corsi per profili misti (senior + junior, teoria + pratica)
  • Hiring tech: candidati descritti per skill (V=ha, F=non ha, N=indeterminato)
  • Real estate: immobili per proprietà (piano, esposizione, box, metratura)
  • Farmaceutica: molecole per proprietà biochimiche, drug matching per target
  • Content discovery: articoli, podcast, video filtrati per proprietà editoriali

Tabella sinottica: ONTO vs OOP

Caratteristica OOP ONTO/POP
Valori indeterminati null/Optional → if-chain N nativo, con tavole proprie
Composizione Ereditarietà (top-down) Aggregazione mereologica (bottom-up)
Aggiunta di dimensioni Refactor di classe + test Una proprietà in più nell'elemento, zero cascata
Interrogazione Metodo nella classe Espressione logica inline
Casi anomali Override + LSP violation Combinazione naturale di proprietà
Separazione dati/logica Richiede pattern (Strategy, etc.) Nativa (elemento = dato, funzione = logica)
Incertezza Tipo speciale (Optional, Maybe) Valore primitivo (N)
Cold start Problema architetturale Zero: N non penalizza
Testabilità Test per ogni branch null Test per ogni valore TV (3 casi)

Dove OOP rimane superiore

Per completezza, i domini dove OOP mantiene il vantaggio:

Dominio Motivo
UI/Frontend (React, Qt) L'identità di un componente nel tempo è fondamentale
Game engine Stato mutable condiviso, ciclo di vita degli oggetti
Sistemi operativi / Driver Accesso diretto alla memoria, lifetime management
Transazioni distribuite ACID semantics richiedono identità forte degli oggetti
Serializzazione/Deserializzazione La struttura OOP mappa direttamente su JSON/XML

Conclusione: la regola del pollice

Usa POP/ONTO quando:

  • Le tue entità sono descrivibili come aggregati di proprietà con valori V/F/N
  • Il dominio ha indeterminazione strutturale (valori non conoscibili direttamente, informazioni non disponibili)
  • Aggiungi spesso nuove dimensioni ai tuoi dati
  • La logica di analisi è separata dalla struttura dati
  • Non ti serve lo stato mutabile a lungo termine

Usa OOP quando:

  • Le tue entità hanno un ciclo di vita con stato interno che cambia nel tempo
  • Il polimorfismo comportamentale è centrale (diversi oggetti rispondono diversamente allo stesso messaggio)
  • L'identità dell'entità è più importante delle sue proprietà correnti

Il paradigma POP non è un sostituto universale dell'OOP: è uno strumento per una classe di problemi dove il modello ontologico è più vicino alla realtà del dominio. Nei cinque ambiti mostrati, ONTO non solo semplifica il codice — cambia la semantica di cosa significa un valore mancante, di come si compongono le entità, e di cosa vuol dire "classificare" un'entità. Queste non sono ottimizzazioni: sono correzioni concettuali.

Fonte: potenzialita_POP.md