it-swarm.it

Schema vs LISP comune: quali caratteristiche hanno fatto la differenza nel tuo progetto?

Non mancano vaghe domande "Schema vs Common LISP" sia su StackOverflow che su questo sito, quindi voglio renderlo più mirato. La domanda è per le persone che hanno codificato in entrambe le lingue:

Durante la programmazione in Scheme, quali elementi specifici della tua esperienza di codifica LISP comune ti sono mancati di più? O, al contrario, durante la codifica in Common LISP, cosa ti sei perso a scrivere in Scheme?

Non intendo necessariamente solo funzionalità linguistiche. Per quanto riguarda la domanda, ci sono tutte le cose valide da perdere:

  • Biblioteche specifiche.
  • Funzionalità specifiche di ambienti di sviluppo come SLIME, DrRacket, ecc.
  • Funzionalità di implementazioni particolari, come la capacità di Gambit di scrivere blocchi di codice C direttamente nel tuo sorgente Scheme.
  • E, naturalmente, funzionalità linguistiche.

Esempi del tipo di risposte che spero:

  • "Stavo cercando di implementare X in Common LISP, e se avessi avuto le continuazioni di prima classe di Scheme, avrei semplicemente fatto Y, ma invece avrei dovuto fare Z, il che era più una seccatura."
  • "Lo scripting del processo di compilazione nel mio progetto Scheme è diventato sempre più doloroso man mano che il mio albero dei sorgenti cresceva e mi collegavo a sempre più librerie C. Per il mio prossimo progetto, sono tornato al Common LISP."
  • "Ho una grande base di codici C++ esistente e, per me, essere in grado di incorporare le chiamate C++ direttamente nel mio codice del Gambit Scheme è valsa la pena qualsiasi carenza che Scheme potrebbe avere rispetto al Common LISP, inclusa la mancanza di supporto SWIG."

Quindi, spero in storie di guerra, piuttosto che in sentimenti generali come "Lo schema è un linguaggio più semplice" ecc.

159
SuperElectric

La mia laurea era in Scienze cognitive e intelligenza artificiale. Da quel momento ho avuto un'introduzione di un corso a LISP. Ho pensato che il linguaggio fosse interessante (come in "elegante") ma non ci ho pensato molto fino a quando non ho incontrato la Decima Regola di Greenspun molto più tardi:

Qualsiasi programma C o Fortran sufficientemente complicato contiene un'implementazione lenta ad hoc, specificata in modo informatico, inficiata da bug, della metà del Common LISP.

Il punto di Greenspun era (in parte) che molti programmi complessi hanno interpreti incorporati. Invece di costruire un interprete in una lingua, ha suggerito che potrebbe avere più senso usare una lingua come LISP che ha già un interprete (o un compilatore) incorporato.

All'epoca lavoravo su un'app piuttosto grande che eseguiva calcoli definiti dall'utente usando un interprete personalizzato per un linguaggio personalizzato. Ho deciso di provare a riscrivere il suo core in LISP come esperimento su larga scala.

Ci sono volute circa sei settimane. Il codice originale era ~ 100.000 righe di Delphi (una variante Pascal). In LISP che è stato ridotto a ~ 10.000 linee. Ancora più sorprendente, tuttavia, è stato il fatto che il motore LISP era 3-6 volte più veloce. E tieni presente che questo era il lavoro di un neofita LISP! Tutta questa esperienza mi ha aperto gli occhi; per la prima volta ho visto la possibilità di combinare prestazioni ed espressività in una sola lingua.

Qualche tempo dopo, quando ho iniziato a lavorare su un progetto basato sul web, ho fatto il provino in diverse lingue. Ho incluso LISP e Scheme nel mix. Alla fine ho selezionato un'implementazione dello Schema - Chez Scheme . Sono stato molto contento dei risultati.

Il progetto basato sul web è un "motore di selezione" ad alte prestazioni . Utilizziamo Scheme in diversi modi, dall'elaborazione dei dati alla query dei dati alla generazione della pagina. In molti punti abbiamo effettivamente iniziato con una lingua diversa, ma abbiamo finito con la migrazione allo Schema per i motivi che descriverò brevemente di seguito.

Ora posso rispondere alla tua domanda (almeno in parte).

Durante l'audizione abbiamo esaminato una varietà di implementazioni di LISP e Scheme. Sul lato LISP abbiamo esaminato (credo) Allegro CL, CMUCL, SBCL e LispWorks. Per quanto riguarda lo Scheme abbiamo esaminato (credo) Bigloo, Chicken, Chez, Gambit. (La selezione della lingua è stata molto tempo fa; ecco perché sono un po 'confuso. Posso prendere appunti se è importante.)

Stavamo cercando a) thread nativi eb) supporto Linux, Mac e Windows. Queste due condizioni messe insieme hanno messo tutti al tappeto ma (penso) Allegro e Chez - quindi per continuare la valutazione abbiamo dovuto allentare il requisito del multi-threading.

Abbiamo messo insieme una serie di piccoli programmi e li abbiamo usati per valutazione e test. Ciò ha rivelato una serie di problemi. Ad esempio: alcune implementazioni presentavano difetti che impedivano l'esecuzione di alcuni test fino al completamento; alcune implementazioni non sono riuscite a compilare il codice in fase di esecuzione; alcune implementazioni non potevano facilmente integrare il codice compilato di runtime con il codice precompilato; alcune implementazioni avevano dei bidoni della spazzatura che erano chiaramente migliori (o chiaramente peggiori) di altri; eccetera.

Per le nostre esigenze solo le tre implementazioni commerciali - Allegro, Chez e Lispworks - hanno superato i nostri test primari. Dei tre solo Chez ha superato tutti i test a pieni voti. All'epoca penso che Lispworks non avesse thread nativi su nessuna piattaforma (penso che lo facciano ora) e penso che Allegro avesse solo thread nativi su alcune piattaforme. Inoltre, Allegro aveva una tassa di licenza di runtime "chiamaci" che non mi piaceva molto. Credo che Lispworks non avesse alcun costo di runtime e Chez avesse un accordo semplice (e molto ragionevole) (e si è avviato solo se hai usato il compilatore in fase di runtime).

Avendo prodotto blocchi di codice piuttosto significativi sia in LISP che in Scheme, ecco alcuni punti di confronto e contrasto:

  • Gli ambienti LISP sono molto più maturi. Ottieni molto di più per il dollaro. (Detto questo, più codice equivale anche a più bug.)

  • Gli ambienti LISP sono molto più difficili da imparare. Hai bisogno di molto più tempo per diventare esperto; LISP comune è un linguaggio enorme - e questo è prima di arrivare alle librerie che le implementazioni commerciali aggiungono sopra di esso. (Detto questo, il caso della sintassi di Scheme è molto più sottile e complicato di qualsiasi cosa in LISP.)

  • Gli ambienti LISP possono essere in qualche modo più difficili da produrre binari. È necessario "scuotere" l'immagine per rimuovere i bit non necessari e, se non si esercita correttamente il programma durante tale processo, si potrebbero finire con errori di runtime in seguito . Al contrario, con Chez compiliamo un file di livello superiore che include tutti gli altri file necessari e il gioco è fatto.

Ho detto prima che abbiamo finito per usare Scheme in un numero di posti che inizialmente non intendevamo. Perché? Riesco a pensare a tre motivi dalla cima della mia testa.

Innanzitutto, abbiamo imparato a fidarci di Chez (e del suo sviluppatore, Cadence). Abbiamo chiesto molto dallo strumento ed è stato consegnato costantemente. Ad esempio, Chez ha storicamente avuto un numero banalmente piccolo di difetti e il suo gestore della memoria è stato molto, molto buono.

In secondo luogo, abbiamo imparato ad amare la performance che abbiamo avuto da Chez. Stavamo usando qualcosa che sembrava un linguaggio di scripting e ne stavamo ottenendo la velocità in codice nativo. Per alcune cose che non avevano importanza, ma non faceva mai male, e talvolta aiutava moltissimo.

In terzo luogo, abbiamo imparato ad amare l'astrazione che lo Schema potrebbe fornire. A proposito, non intendo solo macro; Intendo cose come chiusure, lambda, chiamate di coda, ecc. Una volta che inizi a pensare in questi termini, altre lingue sembrano piuttosto limitate al confronto.

Scheme è perfetto? No; è un compromesso. Innanzitutto, consente ai singoli sviluppatori di essere più efficaci, ma è più difficile per gli sviluppatori fare a vicenda il codice reciproco perché nello Scheme mancano le indicazioni che la maggior parte delle lingue (ad esempio per i loop) mancano (ad esempio, ci sono milioni di modi per fare a per ciclo). In secondo luogo, c'è un pool molto più piccolo di sviluppatori con cui parlare, assumere, prendere in prestito, ecc.

Per riassumere, penso che direi: LISP e Scheme offrono alcune funzionalità non ampiamente disponibili altrove. Questa capacità è un compromesso, quindi sarebbe meglio che abbia senso nel tuo caso particolare. Nel nostro caso, i fattori determinanti tra l'adesione a LISP o Scheme hanno avuto più a che fare con funzionalità molto fondamentali (supporto della piattaforma, thread della piattaforma, compilazione di runtime, licenze di runtime) rispetto a quanto facessero con le funzionalità del linguaggio o della libreria. Anche in questo caso, anche questo è stato un compromesso: con Chez abbiamo ottenuto le funzionalità di base che volevamo, ma abbiamo perso le vaste librerie degli ambienti commerciali LISP.

Inoltre, solo per ribadire: abbiamo esaminato i vari Lisps e Schemi molto tempo fa; da allora si sono tutti evoluti e migliorati.

102
Michael Lenaghan

Di solito non mi piace incollare un link come risposta, ma ho scritto un articolo di blog proprio su questa cosa. Non è esaustivo, ma ottiene alcuni dei punti principali.

http://symbo1ics.com/blog/?p=729

Modifica: Ecco i punti principali:

  1. [~ # ~] esistenza [~ # ~] : Entrambi i lisps sono arrivati ​​dopo un mucchio di altri lisps. Lo schema ha preso la via assiomatica minima. CL prese la via barocca.
  2. [~ # ~] case [~ # ~] : in genere lo schema fa distinzione tra maiuscole e minuscole. CL non lo è (sebbene possa essere). Questo a volte è mancato, ma la sua praticità è discussa (da me).
  3. [~ # ~] names [~ # ~] : I nomi dei simboli in CL sono molte volte strani e confusi. TERPRI, PROGN, ecc. Lo schema di solito ha nomi molto sensibili. Questo è qualcosa che manca in CL.
  4. [~ # ~] funzioni [~ # ~] : CL ha uno spazio dei nomi di funzione separato. Questo è no mancato nello Schema. Avere un singolo spazio dei nomi di solito consente una programmazione funzionale molto pulita, che è spesso difficile o scomoda in CL. Ma ha un costo --- a volte devi offuscare nomi come "list" a "lst" in Scheme.
  5. Macro [~ # ~] [~ # ~] : Mi mancano maggiormente le macro sporche di basso livello in Scheme. Si, syntax-rules va tutto bene e dandy fino a quando non vuoi davvero hackerare alcune cose. D'altra parte, le macro igieniche a volte mancano in CL. Non avere un modo standard per farli significa reinventare la ruota.
  6. [~ # ~] portabilità [~ # ~] : Spesso il CL è più portatile, nonostante entrambe le lingue siano standardizzate. CL è più grande e quindi ci sono più funzionalità standard da usare senza librerie esterne. Significa anche che più cose dipendenti dall'implementazione possono essere fatte in modo portabile. Inoltre, Scheme soffre di avere trilioni di implementazioni, la maggior parte delle quali sono in qualche modo incompatibili. Questo rende CL molto desiderabile.
  7. [~ # ~] librerie [~ # ~] : molto legato al mio ultimo punto. Lo schema ha SRFI ma non è universalmente riconosciuto. Non esiste un modo portatile per lavorare con le biblioteche. CL d'altra parte ha dei modi. E Quicklisp è un dono di god (Xach) --- una sorta di repository di librerie da usare.
  8. [~ # ~] implementazioni [~ # ~] : lo schema soffre di avere così tante implementazioni. Non esiste una vera implementazione canonica. CL d'altra parte ha alcune implementazioni molto belle ad alte prestazioni o uso specifico (alte prestazioni: SBCL, commerciale: Allegro, incorporato: ECL, portatile: CLISP, Java: ABCL, ...).

Mentre ho parlato solo in prima persona un po 'sopra, dovrebbe essere chiaro cosa mi manca e cosa no.

[Mi scuso se sono troppo generici. Sembra che potresti desiderare dettagli molto più specifici. Ci sono alcuni dettagli nel post.]

37
Quadrescence

Di recente ho avviato un progetto home utilizzando una libreria con una versione C e una Java. Volevo usare LISP per il progetto e ho trascorso circa un mese vacillando tra l'utilizzo di Common LISP, Scheme o Clojure. Ho una certa esperienza con tutti e tre, ma solo progetti di giocattoli. Ti parlerò un po 'della mia esperienza con ciascuno di essi prima di dirti quale ho finito per scegliere.

La racchetta PLT ha un Nice IDE che non solo ti consente di valutare le espressioni dall'editor, ma ti consente anche di digitare parentesi anziché parentesi, riportandole in parentesi ove appropriato. La racchetta ha anche un set di grandi dimensioni di librerie con l'installazione e ancora più disponibili per il download. Anche il visual debugger è utile.

La mia implementazione LISP comune (SBCL) non ha un IDE, ma è consuetudine con implementazioni CL open-source per utilizzare Emacs e SLIME. Questa combinazione può essere molto efficiente. Oltre alla possibilità di valutare le espressioni mentre le digiti nel file sorgente, c'è anche un REPL che ha tutti i comandi di modifica di emacs disponibili, quindi la copia del codice può andare in modo efficiente in entrambi i modi. oggetti visualizzati nel REPL può essere copiato e incollato. Alt+( e Alt+) sono efficaci per gestire parentesi e rientri corrispondenti.

Tutte le funzionalità di Emacs sopra sono disponibili anche per Clojure. La mia esperienza di editing con Clojure è simile a quella di LISP. L'interoperabilità Java ha funzionato bene e mi piacerebbe fare un progetto Clojure una volta maturato.

Sono stato in grado di accedere alla libreria usando tutti e tre (Common LISP, Racket e Clojure), ma ho finito per scegliere Common LISP per il progetto. Il fattore decisivo è stato che FFI era molto più facile da usare in Common LISP. CFFI ha un ottimo manuale con codice di esempio e spiegazioni dettagliate di ciascun metodo. Sono stato in grado di racchiudere 20 funzioni C in un pomeriggio e da allora non ho più dovuto toccare il codice.

L'altro fattore era che ho più familiarità con Common LISP che con Clojure o R6RS Scheme. Ho letto la maggior parte dei libri di Practical Common LISP e Graham e sono a mio agio con Hyperspec. Non è ancora un codice molto "lispy", ma sono sicuro che cambierà man mano che acquisirò più esperienza.

25
Larry Coleman

Programmo sia in CL che in Racket.

Sto sviluppando un sito Web ora in Common LISP e ho scritto una suite di programmi interni per il mio precedente datore di lavoro in Racket.

Per il codice interno, ho scelto Racket (allora noto come Schema PLT) perché il datore di lavoro era un negozio di Windows e non riuscivo a farli pagare per LispWorks. L'unica buona implementazione CL open-source per Windows era (ed è tuttora) CCL, che richiede SSE supporto nel processore. Il datore di lavoro, essendo economico, stava usando Hardware dell'età della pietra: anche se il datore di lavoro disponeva di hardware decente, l'unica libreria di conseguenza della GUI in Common LISP è McCLIM, che funziona solo su Unix. Racket ha una buona libreria di GUI che funziona sia su Unix che su Windows, il che è stato fondamentale per il mio successo del progetto.

Ho trascorso più di un anno a sopportare l'editore primitivo DrRacket. EMACS non è riuscito a trasformare la versione GUI di Racket, allora nota come MrEd, in un LISP inferiore su Windows. Ho dovuto fare a meno di essere in grado di valutare l'espressione sul cursore con un solo tasto. Invece, ho dovuto selezionare manualmente l'espressione S, copiarlo, fare clic sulla finestra REPL (perché non è necessario premere il tasto per passare a esso), quindi incollare l'espressione S. dovuto fare a meno di un editor che potesse mostrarmi gli argomenti attesi della funzione o della macro che stavo usando. DrRacket non può sostituire SLIME.

Il datore di lavoro utilizzava un database proprietario con un'API XML complessa che richiedeva un sacco di informazioni apparentemente non necessarie per poter rispondere alla sua versione di una query SELECT. Ho deciso di utilizzare HTMLPrag sia per emettere XML a questa API, sia per analizzare le risposte. Ha funzionato alla grande.

Ho dovuto imparare il sistema macro "caso sintassi" troppo complicato di Racket per scrivere una macro che mi permettesse di interagire con l'API XML troppo complicata digitando moduli che sembravano SQL. Questa parte sarebbe stata molto più semplice se avessi DEFMACRO a mia disposizione. Tuttavia, il risultato finale è stato ancora senza soluzione di continuità, anche se ci sono voluti più sforzi per raggiungere.

Inoltre, ho dovuto fare a meno della macro LOOP di Common LISP. Racket ha iniziato a fornire un'alternativa solo dopo che avevo scritto la maggior parte del codice e l'alternativa fa ancora schifo rispetto a LOOP (anche se il team di sviluppo di Racket insiste sul fatto che è meglio - hanno semplicemente torto). Ho finito per scrivere un sacco di moduli LET che utilizzavano "car" e "cdr" per scorrere le liste.

Parlando di auto e cddr, niente è più frustrante dell'interpretazione di Scheme di (car '()) come errore. Ho sfruttato la distinzione tra maiuscole e minuscole di Racket e ho implementato CAR e CDR, che hanno la semantica di Common LISP. Tuttavia, la separazione di '() e #f rende molto meno utile restituire' () come valore predefinito.

Ho anche finito di reimplementare UNWIND-PROTECT e ho inventato il mio sistema di riavvio per colmare il vuoto lasciato da Racket. La community di Racket deve imparare che i riavvii sono molto utili e facili da implementare.

La forma dei valori let di Racket era eccessivamente dettagliata, quindi ho implementato MULTIPLE-VALUE-BIND. Questo era assolutamente necessario, perché Racket richiede devi ricevere tutti i valori che vengono generati, che tu li usi o meno.

Successivamente, ho tentato di scrivere un client API XML eBay in Common LISP, solo per scoprire che non ha niente come HTMLPrag. HTMLPrag è estremamente utile. Ho finito per fare quel progetto in Racket. Ho sperimentato le strutture di programmazione letterale di Racket, solo per scoprire che sono l'unico programmatore sulla Terra che trova il codice letterato correttamente scritto più difficile da modificare rispetto al codice ordinario, o codice letterale "commenti eccessivi" scritto in modo improprio.

Il mio nuovo progetto è stato realizzato in Common LISP, che è stata la scelta giusta perché la community di Racket non crede nel parallelismo, che è essenziale per questo progetto. L'unica cosa che pensavo di potermi perdere da Racket erano le continuazioni. Tuttavia, sono stato in grado di fare ciò di cui avevo bisogno usando il riavvio e, in retrospettiva, probabilmente avrei potuto farlo con una semplice chiusura.

21
Racketeer

Lo schema è progettato pensando a una compilation separata. Di conseguenza, una potenza delle sue macro è spesso fortemente limitata, anche con le estensioni che consentono un defmacro in stile LISP comune invece di un macrosistema igienico limitato e limitante. Non è sempre possibile definire una macro che definisce un'altra macro, destinata ad un uso immediato in una riga di codice successiva. E tale possibilità è essenziale per l'implementazione di compilatori eDSL efficienti.

Inutile dire che le implementazioni di Scheme con solo macro igieniche R5RS sono a malapena utili per me, poiché il mio stile di metaprogrammazione non può essere adeguatamente tradotto in igiene.

Fortunatamente, ci sono implementazioni di Scheme (ad es. Racket) che non hanno questa limitazione.

5
SK-logic