La racchetta digitata è molto diversa da quella di Haskell. I sistemi di tipo in Lisp e Scheme, e in effetti i sistemi di tipo in ecosistemi linguistici tradizionalmente non tipizzati in generale, hanno un obiettivo fondamentale che altri sistemi di tipo non interagiscono con il codice non tipizzato esistente . La racchetta digitata, ad esempio, ha introdotto regole di battitura completamente nuove per adattarsi ai vari modi di usare la racchetta. Considera questa funzione:
(define (first some-list)
(if (empty? some-list)
#f
(car some-list)))
Per elenchi non vuoti, questo restituisce il primo elemento. Per elenchi vuoti, questo restituisce false. Questo è comune nelle lingue non tipizzate; un linguaggio tipizzato userebbe un tipo di wrapper simile Maybe
o genererebbe un errore nel caso vuoto. Se volessimo aggiungere un tipo a questa funzione, quale tipo dovrebbe essere usato? Non lo è [a] -> a
(nella notazione di Haskell), perché può restituire false. Inoltre non lo è [a] -> Either a Boolean
, perché (1) restituisce sempre false nel caso vuoto, non un valore booleano arbitrario e (2) un tipo O avvolge gli elementi Left
e inserisce false Right
e richiede di "scartare entrambi" per raggiungere l'elemento reale. Al contrario, il valore restituisce una vera unione- non ci sono costruttori di wrapping, restituisce semplicemente un tipo in alcuni casi e un altro tipo in altri casi. In Typed Racket, questo è rappresentato dal costruttore del tipo di unione:
(: first (All (A) (-> (Listof A) (U A #f))))
(define (first some-list)
(if (empty? some-list)
#f
(car some-list)))
Il tipo (U A #f)
indica che la funzione potrebbe restituire un elemento dell'elenco o false senza alcuna Either
istanza di wrapping . Il controllo del tipo può dedurre che some-list
è di tipo (Pair A (Listof A))
o l'elenco vuoto, e inoltre deduce che nei due rami dell'istruzione if è noto quale di questi è il caso . Il verificatore di tipo sa che (car some-list)
nell'espressione l'elenco deve avere il tipo (Pair A (Listof A))
perché la condizione if lo assicura. Questo si chiama tipizzazione di occorrenza ed è progettato per facilitare la transizione dal codice non tipizzato al codice digitato.
Il problema è la migrazione. C'è un sacco di codice di racchetta non tipizzato là fuori, e la racchetta tipizzata non può semplicemente costringerti ad abbandonare tutte le tue librerie non tipizzate preferite e passare un mese ad aggiungere tipi al tuo codebase se vuoi usarlo. Questo problema si applica ogni volta che aggiungi tipi gradualmente a una base di codice esistente, vedi TypeScript e il suo tipo Any per un'applicazione javascript di queste idee.
Un sistema di tipi graduale deve fornire strumenti per gestire i linguaggi non tipizzati comuni e interagire con il codice non tipizzato esistente. Usarlo sarà piuttosto doloroso altrimenti, vedi "Perché non stiamo più usando Core.typed" per un esempio di Clojure.