È possibile una variante Lisp completa di tipo statico?


107

È possibile una variante Lisp completa di tipo statico? Ha senso che esista qualcosa di simile? Credo che una delle virtù di un linguaggio Lisp sia la semplicità della sua definizione. La digitazione statica comprometterebbe questo principio fondamentale?


10
Mi piacciono le macro a mano libera di Lisp, ma mi piace la robustezza del sistema di tipi di Haskell. Mi piacerebbe vedere come appare un Lisp tipizzato staticamente.
mcandre

4
Buona domanda! Credo che shenlanguage.org lo faccia. Vorrei che diventasse più mainstream.
Hamish Grubijan


Come esegui il calcolo simbolico con Haskell? (risolve 'x' (= (+ xy) (* xy))). Se lo metti in una stringa non c'è controllo (a differenza di Lisp che può usare le macro per aggiungere il controllo). Se usi tipi di dati o elenchi algebrici ... Sarà molto prolisso: risolvere (Sym "x") (Eq (Plus (Sym "x") (Sym "y")) (Mult (Sym "x") (Sym "y")))
aoeu256

Risposte:


57

Sì, è molto possibile, sebbene un sistema di tipi in stile HM standard sia solitamente la scelta sbagliata per il codice Lisp / Scheme più idiomatico. Vedi Typed Racket per un linguaggio recente che è un "Full Lisp" (più simile a Scheme, in realtà) con digitazione statica.


1
Il problema qui è: qual è il tipo di elenco che costituisce l'intero codice sorgente di un programma di racchetta digitato?
Zorf

18
Di solito lo sarebbe Sexpr.
Eli Barzilay

Ma poi posso scrivere coerce :: a->bin termini di valutazione. Dov'è la protezione dai tipi?
domenica

2
@ssice: quando stai usando una funzione non tipizzata come evaldevi testare il risultato per vedere cosa viene fuori, che non è una novità in Typed Racked (stesso affare di una funzione che accetta un tipo di unione Stringe Number). Un modo implicito per vedere che questo può essere fatto è il fatto che puoi scrivere e usare un linguaggio tipizzato dinamicamente in un linguaggio tipizzato staticamente HM.
Eli Barzilay

37

Se tutto ciò che volevi fosse un linguaggio tipizzato staticamente che assomigliasse al Lisp, potresti farlo piuttosto facilmente, definendo un albero di sintassi astratto che rappresenta la tua lingua e quindi mappando quell'AST su S-espressioni. Tuttavia, non credo che chiamerei il risultato Lisp.

Se vuoi qualcosa che abbia effettivamente le caratteristiche Lisp-y oltre alla sintassi, è possibile farlo con un linguaggio tipizzato staticamente. Tuttavia, ci sono molte caratteristiche del Lisp dalle quali è difficile ottenere una tipizzazione statica molto utile. Per illustrare, diamo uno sguardo alla struttura dell'elenco stessa, chiamata contro , che costituisce l'elemento costitutivo principale di Lisp.

Chiamare i contro una lista, anche se (1 2 3)sembra uno, è un po 'improprio. Ad esempio, non è affatto paragonabile a un elenco digitato staticamente, come l'elenco di C ++ std::listo di Haskell. Questi sono elenchi collegati unidimensionali in cui tutte le celle sono dello stesso tipo. Lisp lo consente felicemente (1 "abc" #\d 'foo). Inoltre, anche se estendi gli elenchi di tipo statico per coprire elenchi di elenchi, il tipo di questi oggetti richiede che ogni elemento dell'elenco sia un sottolista. Come rappresenteresti ((1 2) 3 4)in loro?

Lisp conses forma un albero binario, con foglie (atomi) e rami (conses). Inoltre, le foglie di un albero del genere possono contenere qualsiasi tipo Lisp atomico (non contro)! La flessibilità di questa struttura è ciò che rende Lisp così bravo a gestire calcoli simbolici, AST e trasformare il codice Lisp stesso!

Quindi come modellereste una struttura del genere in un linguaggio tipizzato staticamente? Proviamolo in Haskell, che ha un sistema di tipo statico estremamente potente e preciso:

type Symbol = String
data Atom = ASymbol Symbol | AInt Int | AString String | Nil
data Cons = CCons Cons Cons 
            | CAtom Atom

Il tuo primo problema sarà l'ambito del tipo Atom. Chiaramente, non abbiamo scelto un tipo Atom di flessibilità sufficiente per coprire tutti i tipi di oggetti che vogliamo lanciare in giro. Invece di cercare di estendere la struttura dei dati Atom come elencato sopra (che puoi vedere chiaramente è fragile), diciamo che abbiamo avuto una classe di tipi magici Atomicche distingueva tutti i tipi che volevamo rendere atomici. Quindi potremmo provare:

class Atomic a where ?????
data Atomic a => Cons a = CCons Cons Cons 
                          | CAtom a

Ma questo non funzionerà perché richiede che tutti gli atomi dell'albero siano dello stesso tipo. Vogliamo che siano in grado di differire da foglia a foglia. Un approccio migliore richiede l'uso dei quantificatori esistenziali di Haskell :

class Atomic a where ?????
data Cons = CCons Cons Cons 
            | forall a. Atomic a => CAtom a 

Ma ora arrivi al nocciolo della questione. Cosa puoi fare con gli atomi in questo tipo di struttura? Quale struttura hanno in comune con cui si potrebbe modellare Atomic a? Quale livello di sicurezza del tipo ti viene garantito con un tale tipo? Nota che non abbiamo aggiunto alcuna funzione alla nostra classe di tipo, e c'è una buona ragione: gli atomi non condividono nulla in comune in Lisp. Il loro supertipo in Lisp è semplicemente chiamato t(cioè top).

Per usarli, dovresti inventare meccanismi per forzare dinamicamente il valore di un atomo a qualcosa che puoi effettivamente usare. E a quel punto, hai praticamente implementato un sottosistema tipizzato dinamicamente all'interno del tuo linguaggio tipizzato staticamente! (Non si può fare a meno di notare un possibile corollario alla Decima regola di programmazione di Greenspun .)

Si noti che Haskell fornisce il supporto solo per un sottosistema dinamico di questo Objtipo con un tipo, utilizzato insieme a un Dynamictipo e una classe Typeable per sostituire la nostra Atomicclasse, che consentono di memorizzare valori arbitrari con i loro tipi e la coercizione esplicita da quei tipi. Questo è il tipo di sistema che dovresti usare per lavorare con le strutture contro Lisp nella loro piena generalità.

Quello che puoi fare è anche andare dall'altra parte e incorporare un sottosistema tipizzato staticamente all'interno di un linguaggio tipizzato essenzialmente dinamicamente. Ciò consente il vantaggio del controllo del tipo statico per le parti del programma che possono trarre vantaggio da requisiti di tipo più rigorosi. Questo sembra essere l'approccio adottato nella forma limitata di controllo del tipo preciso della CMUCL , ad esempio.

Infine, c'è la possibilità di avere due sottosistemi separati, tipizzati dinamicamente e staticamente, che utilizzano la programmazione in stile contratto per aiutare a navigare nella transizione tra i due. In questo modo il linguaggio può adattarsi agli usi del Lisp in cui il controllo del tipo statico sarebbe più un ostacolo che un aiuto, così come gli usi in cui il controllo del tipo statico sarebbe vantaggioso. Questo è l'approccio adottato da Typed Racket , come vedrai dai commenti che seguono.


16
Questa risposta soffre di un problema fondamentale: stai assumendo che i sistemi di tipo statico debbano essere in stile HM. Il concetto di base che non può essere espresso qui, ed è una caratteristica importante del codice Lisp, è il sottotipo. Se dai un'occhiata alla racchetta digitata, vedrai che può facilmente esprimere qualsiasi tipo di elenco, comprese cose come (Listof Integer)e (Listof Any). Ovviamente, sospetteresti che quest'ultimo sia inutile perché non sai nulla del tipo, ma in TR, puoi usarlo in seguito (if (integer? x) ...)e il sistema saprà che xè un numero intero nel primo ramo.
Eli Barzilay

5
Oh, ed è una cattiva caratterizzazione della racchetta digitata (che è diversa dai sistemi di tipo non corretto che potresti trovare in alcuni punti). Typed Racket è un linguaggio tipizzato staticamente , senza sovraccarico di runtime per il codice digitato. Racket consente ancora di scrivere un po 'di codice in TR e un po' nel solito linguaggio non tipizzato - e per questi casi i contratti (controlli dinamici) vengono utilizzati per proteggere il codice digitato dal codice non tipizzato che potrebbe comportarsi male.
Eli Barzilay

1
@Eli Barzilay: Ho mentito, ci sono quattro parti: 4. È interessante per me come lo stile di codifica C ++ accettato dal settore si sia spostato gradualmente dalla sottotipizzazione verso i generici. Il punto debole è che il linguaggio non fornisce aiuto per dichiarare l'interfaccia che utilizzerà una funzione generica, qualcosa con cui le classi di tipo potrebbero sicuramente aiutare. Inoltre, C ++ 0x potrebbe aggiungere l'inferenza del tipo. Non HM, suppongo, ma strisciando in quella direzione?
Owen S.

1
Owen: (1) il punto principale è che hai bisogno di sottotipi per esprimere il tipo di codice che i lispers scrivono - e non puoi averlo con i sistemi HM, quindi sei costretto a tipi e costruttori personalizzati per ogni uso, che rende il tutto molto più scomodo da usare. Nella racchetta tipizzata, l'utilizzo di un sistema con sottotipi era un corollario di una decisione progettuale intenzionale: il risultato doveva essere in grado di esprimere i tipi di tale codice senza modificare il codice o creare tipi personalizzati.
Eli Barzilay

1
(2) Sì, i dynamictipi stanno diventando popolari nei linguaggi statici come una sorta di soluzione alternativa per ottenere alcuni dei vantaggi dei linguaggi tipizzati dinamicamente, con il solito compromesso di questi valori racchiusi in un modo che rende i tipi identificabili. Ma anche qui la racchetta tipizzata sta facendo un ottimo lavoro nel renderla comoda all'interno del linguaggio: il controllo del tipo utilizza le occorrenze dei predicati per saperne di più sui tipi. Ad esempio, vedere l'esempio digitato nella pagina della racchetta e vedere come string?"riduce" un elenco di stringhe e numeri a un elenco di stringhe.
Eli Barzilay

10

La mia risposta, senza un alto grado di fiducia è probabilmente . Se guardi un linguaggio come SML, ad esempio, e lo confronti con Lisp, il nucleo funzionale di ciascuno è quasi identico. Di conseguenza, non sembra che avresti molti problemi ad applicare una sorta di tipizzazione statica al nucleo di Lisp (applicazione di funzioni e valori primitivi).

La tua domanda però dice piena , e dove vedo alcuni dei problemi in arrivo è l'approccio del codice come dati. I tipi esistono a un livello più astratto delle espressioni. Lisp non ha questa distinzione: tutto è "piatto" nella struttura. Se consideriamo un'espressione E: T (dove T è una rappresentazione del suo tipo), e quindi consideriamo questa espressione come semplici dati, allora qual è esattamente il tipo di T qui? Bene, è un tipo! Un tipo è un tipo di ordine superiore, quindi andiamo avanti e diciamo qualcosa al riguardo nel nostro codice:

E : T :: K

Potresti vedere dove sto andando con questo. Sono sicuro che separando le informazioni sul tipo dal codice sarebbe possibile evitare questo tipo di autoreferenzialità dei tipi, tuttavia ciò renderebbe i tipi poco "lisp" nel loro sapore. Ci sono probabilmente molti modi per aggirare questo problema, anche se non è ovvio per me quale sarebbe il migliore.

EDIT: Oh, quindi con un po 'di googling, ho trovato Qi , che sembra essere molto simile a Lisp tranne che è tipizzato staticamente. Forse è un buon punto di partenza per vedere dove hanno apportato modifiche per ottenere la digitazione statica lì.


Sembra che la prossima iterazione dopo Qi sia Shen , sviluppato dalla stessa persona.
Diagon

4

Il collegamento è morto. Ma in ogni caso, Dylan non è tipizzato staticamente.
Björn Lindqvist

@ BjörnLindqvist: quel collegamento era a una tesi sull'aggiunta graduale della digitazione a Dylan.
Rainer Joswig

1
@ BjörnLindqvist: ho collegato a un documento di sintesi.
Rainer Joswig

Ma la digitazione graduale non conta come digitazione statica. In tal caso, Pypy sarebbe Python tipizzato staticamente poiché utilizza anche la digitazione graduale.
Björn Lindqvist

2
@ BjörnLindqvist: se aggiungiamo tipi statici tramite la digitazione graduale e questi vengono controllati durante la compilazione, allora questa è una digitazione statica. Non è solo che l'intero programma sia tipizzato staticamente, ma parti / regioni. homes.sice.indiana.edu/jsiek/what-is-gradual-typing 'La digitazione graduale è un sistema di tipi che ho sviluppato con Walid Taha nel 2006 che consente di digitare dinamicamente parti di un programma e di altre parti di essere digitate staticamente.'
Rainer Joswig
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.