Tipi in Lisp e Scheme


10

Vedo ora che la racchetta ha tipi. A prima vista sembra quasi identico alla digitazione di Haskell. Ma il CLOS di Lisp copre alcuni dei tipi di spazio di Haskell? La creazione di un tipo Haskell molto rigoroso e un oggetto in qualsiasi linguaggio OO sembra vagamente simile. È solo che ho bevuto un po 'del kool-aid di Haskell e sono totalmente paranoico che se percorro la strada di Lisp, sarò fregato a causa della digitazione dinamica.

Risposte:


6

Il sistema di tipi CL è più espressivo di quello di Haskell, ad esempio è possibile avere un tipo (or (integer 1 10) (integer 20 30))per un valore 1,2,...9,10,20,21,...,30.

Tuttavia, i compilatori Lisp non impongono la comprensione della sicurezza dei tipi in gola, quindi puoi ignorare le loro "note" - a tuo rischio e pericolo .

Questo significa che puoi scrivere Haskell in Lisp (per così dire) dichiarando tutti i tipi di valore e assicurandoti attentamente che tutti i tipi necessari siano dedotti, ma poi è più facile usare Haskell in primo luogo.

Fondamentalmente, se vuoi una forte tipizzazione statica, usa Haskell o OCaml, se vuoi una forte tipizzazione dinamica, usa Lisp. Se si desidera una digitazione statica debole, utilizzare C, se si desidera una digitazione dinamica debole, utilizzare Perl / Python. Ogni percorso ha i suoi vantaggi (e fanatici) e svantaggi (e detrattori), quindi trarrai beneficio dall'apprendimento di tutti.


19
Chiunque usi termini come "forzare la sicurezza del tipo in gola" non capisce che tipo di sicurezza sia o perché sia ​​utile.
Mason Wheeler,

11
@MasonWheeler: chiunque stia tracciando una conclusione ampia da una sola frase si troverà a sbagliarsi più spesso del contrario. Ad esempio, in questo caso.
sabato

4
Poiché l'argomento è le lingue, il termine "in gola" è un'immagine appropriata e appropriata.
Luser droog

1
@KChaloux: intendevo "espressivo", come chiarito dall'esempio.
sd

4
Hai tutto al contrario. La tipizzazione dinamica è un caso speciale di tipizzazione statica, in cui si forza il programmatore a utilizzare 1 tipo per tutto. Posso fare la stessa cosa (verbalmente) nella maggior parte dei linguaggi tipicamente dichiarati dichiarando ogni variabile come tipo Objecto qualunque sia la radice dell'albero dei tipi. Questo è meno espressivo, perché sei privato della possibilità di dire che certe variabili possono contenere solo determinati valori.
Doval,

5

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 Maybeo 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 Lefte inserisce false Righte 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 Eitheristanza 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.


1
In economia, questa è conosciuta come la legge di Gresham : i soldi cattivi scacciano il bene. Tende a trovare applicazioni anche in ingegneria. Quando hai due sottosistemi teoricamente equivalenti nel tuo sistema, troppo spesso il peggiore causerà troppi problemi per rendere il migliore da usare, come stiamo vedendo qui.
Mason Wheeler,

"un esempio di progettazione di un sistema di tipi che": questa frase non è completa
coredump

@MasonWheeler Il peggio è, fammi indovinare, il controllo dinamico del tipo. Quindi non appena lo introduci, non vale la pena eseguire alcune analisi statiche?
coredump,

1
@coredump - Ne vale la pena digitare gradualmente, a meno che tu non interagisca male con il codice non tipizzato. Il tipo Any in TypeScript, ad esempio, dice semplicemente al typechecker di rinunciare, fondamentalmente eliminando i vantaggi del sistema di tipi perché può causare la propagazione di un valore errato attraverso grandi quantità di codice controllato staticamente. La racchetta tipizzata utilizza contratti e limiti del modulo per evitare questo problema.
Jack,

1
@coredump Leggi l'articolo di Clojure. Avere la tipizzazione dinamica in giro, in particolare con tutto il codice di terze parti che non aveva tipi statici disponibili, ha davvero rovinato le cose per i loro tentativi di migliorare la loro base di codice con tipi statici.
Mason Wheeler,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.