Oh amico, sono entusiasta di provare a rispondere a questa domanda nel miglior modo possibile. Spero di riuscire a mettere correttamente in ordine i miei pensieri.
Come accennato da @Doval e sottolineato dall'interrogatore (anche se sgarbatamente), non hai davvero un sistema di tipi. Hai un sistema di controlli dinamici che usa i tag, che è generalmente molto più debole e anche molto meno interessante.
La questione di "cos'è un sistema di tipi" può essere piuttosto filosofica e potremmo riempire un libro con diversi punti di vista sull'argomento. Tuttavia, poiché questo è un sito per programmatori, cercherò di mantenere la mia risposta il più pratica possibile (e in realtà, i tipi sono estremamente pratici nella programmazione, nonostante ciò che alcuni potrebbero pensare).
Panoramica
Cominciamo con una comprensione dei vantaggi di un sistema di tipo, prima di immergerci nelle basi più formali. Un sistema di tipo impone la struttura ai nostri programmi . Ci dicono come possiamo collegare varie funzioni ed espressioni insieme. Senza struttura, i programmi sono insostenibili e selvaggiamente complessi, pronti a causare danni al minimo errore del programmatore.
Scrivere programmi con un sistema di tipi è come guidare una cura in perfette condizioni: i freni funzionano, le portiere si chiudono in sicurezza, il motore è oliato, ecc. Scrivere programmi senza un sistema di tipi è come guidare un motociclo senza casco e con ruote fatte fuori dagli spaghetti. Non hai assolutamente alcun controllo sul tuo.
Per fondare la discussione, diciamo che abbiamo un linguaggio con espressione letterale num[n]
e str[s]
che rappresenta il numero n e la stringa s, rispettivamente, e le funzioni primitive plus
e concat
, con il significato previsto. Chiaramente, non vuoi essere in grado di scrivere qualcosa di simile plus "hello" "world"
o concat 2 4
. Ma come possiamo impedirlo? A priori , non esiste un metodo per distinguere il numero 2 dal "mondo" letterale stringa. Quello che vorremmo dire è che queste espressioni dovrebbero essere usate in contesti diversi; hanno diversi tipi.
Lingue e tipi
Facciamo un passo indietro: cos'è un linguaggio di programmazione? In generale, possiamo dividere un linguaggio di programmazione in due livelli: la sintassi e la semantica. Questi sono anche chiamati statica e dinamica , rispettivamente. Si scopre che il sistema di tipi è necessario per mediare l'interazione tra queste due parti.
Sintassi
Un programma è un albero. Non lasciarti ingannare dalle righe di testo che scrivi su un computer; queste sono solo le rappresentazioni leggibili dall'uomo di un programma. Il programma stesso è un albero di sintassi astratto . Ad esempio, in C potremmo scrivere:
int square(int x) {
return x * x;
}
Questa è la sintassi concreta per il programma (frammento). La rappresentazione dell'albero è:
function square
/ | \
int int x return
|
times
/ \
x x
Un linguaggio di programmazione fornisce una grammatica che definisce gli alberi validi di quel linguaggio (è possibile utilizzare una sintassi concreta o astratta). Questo di solito viene fatto usando qualcosa come la notazione BNF. Suppongo che tu l'abbia fatto per la lingua che hai creato.
Semantica
OK, sappiamo cos'è un programma, ma è solo una struttura ad albero statico. Presumibilmente, vogliamo che il nostro programma calcoli effettivamente qualcosa. Abbiamo bisogno della semantica.
La semantica dei linguaggi di programmazione è un ricco campo di studio. In generale, ci sono due approcci: semantica denotazionale e semantica operativa . La semantica denotazionale descrive un programma mappandolo in una struttura matematica sottostante (ad es. Numeri naturali, funzioni continue, ecc.). che fornisce significato al nostro programma. La semantica operativa, al contrario, definisce un programma descrivendo in dettaglio come viene eseguito. Secondo me, la semantica operativa è più intuitiva per i programmatori (incluso me stesso), quindi continuiamo con questo.
Non esaminerò come definire una semantica operativa formale (i dettagli sono un po 'coinvolti), ma fondamentalmente vogliamo regole come le seguenti:
num[n]
è un valore
str[s]
è un valore
- Se
num[n1]
e num[n2]
valuta numeri interi n_1$ and $n_2$, then
più (num [n1], num [n2]) `restituisce l'intero $ n_1 + n_2 $.
- If
str[s1]
e restituisce str[s2]
le stringhe s1 e s2, quindi concat(str[s1], str[s2])
restituisce la stringa s1s2.
Ecc. Le regole sono in pratica molto più formali, ma hai capito bene. Tuttavia, presto incontriamo un problema. Cosa succede quando scriviamo quanto segue:
concat(num[5], str[hello])
Hm. Questo è un vero enigma. Non abbiamo definito una regola da nessuna parte per come concatenare un numero con una stringa. Potremmo tentare di creare una tale regola, ma sappiamo intuitivamente che questa operazione non ha senso. Non vogliamo che questo programma sia valido. E così siamo condotti inesorabilmente ai tipi.
tipi
Un programma è un albero definito dalla grammatica di una lingua. Ai programmi viene dato un significato dalle regole di esecuzione. Ma alcuni programmi non possono essere eseguiti; cioè alcuni programmi non hanno senso . Questi programmi sono mal digitati. Pertanto, la digitazione caratterizza programmi significativi in una lingua. Se un programma è ben scritto, possiamo eseguirlo.
Facciamo alcuni esempi. Ancora una volta, come per le regole di valutazione, presenterò le regole di battitura in modo informale, ma possono essere rese rigorose. Ecco alcune regole:
- Un token del modulo
num[n]
ha tipo nat
.
- Un token del modulo
str[s]
ha tipo str
.
- Se espressione
e1
ha tipo nat
e espressione e2
ha tipo nat
, quindi espressione plus(e1, e2)
ha tipo nat
.
- Se espressione
e1
ha tipo str
e espressione e2
ha tipo str
, quindi espressione concat(e1, e2)
ha tipo str
.
Pertanto, secondo queste regole, esiste plus(num[5], num[2])
un tipo nat
, ma non è possibile assegnare un tipo a plus(num[5], str["hello"])
. Diciamo che un programma (o espressione) è ben tipizzato se possiamo assegnarlo a qualsiasi tipo, e altrimenti è mal digitato. Un sistema di tipi è valido se tutti i programmi ben digitati possono essere eseguiti. Haskell è sano; C non lo è.
Conclusione
Esistono altre visualizzazioni sui tipi. I tipi in qualche modo corrispondono alla logica intuizionista e possono anche essere visti come oggetti nella teoria delle categorie. Comprendere queste connessioni è affascinante, ma non è essenziale se si vuole semplicemente scrivere o progettare un linguaggio di programmazione. Tuttavia, comprendere i tipi come strumento per il controllo delle formazioni di programmi è essenziale per la programmazione e lo sviluppo del linguaggio. Ho solo graffiato la superficie di ciò che i tipi possono esprimere. Spero che tu valga la pena di incorporarli nella tua lingua.