Quali sono i vantaggi pratici di LISP come la sintassi che Clojure utilizza su Java come la sintassi di Scala?

2

Ho passato un paio di mesi a imparare Scala e sono stato sopraffatto dal numero di diversi costrutti che aveva, Dopo aver esaminato le funzioni parziali, le funzioni parzialmente applicate, la corrispondenza dei pattern, la sintassi degli attori, Ho pensato di imparare Clojure che non ha molto in termini di sintassi, ma considerando come viene gestita l'interoperabilità di Java, sembra molto difficile da usare.

per esempio: cose come doto, new and .

Ogni volta che inizio a digitare una funzione, ci sono volte in cui scrivo (1 + 2) invece di (+ 1 2).

Sembra che dovrò dimenticare completamente come scrivo programmi regolari per usare questa sintassi.

Trovando anche difficile gestire gli effetti collaterali che fanno e lasciano che la forma abbia. Non sono simili a quelli che funzionano con il tipo void?

Cose come registrare un callback, cose normali I / O, in che modo queste cose possono essere prive di effetti collaterali.

Fino ad ora tutto ciò che mi è veramente piaciuto di Functional Programming è che, l'immutabilità semplifica la concorrenza quando si tratta di dati condivisi, funziona come oggetti di prima classe e con funzioni di ordine superiore, applicare, mappare e ridurre con funzioni ed elenchi .

Volevo sapere, ci sono dei vantaggi pratici che otterrò una volta che sarò abituato a questa sintassi? Continuo ad imparare Scala ed evitare l'uso di var, e usare sempre ricorsione invece di loop. Posso fare tutto quanto sopra e scrivere ancora codice comprensibile.

    
posta Amogh Talpallikar 04.04.2013 - 12:52
fonte

4 risposte

14

La sintassi Lisp è proprio questo: sintassi. Non ha nulla a che fare con la semantica. In altre parole, la sintassi (idealmente) non dovrebbe influire sulla modalità di scrittura dei programmi; se senti di dover "dimenticare completamente come scrivi programmi regolari", sembra che tu abbia difficoltà con la semantica, non con la sintassi.

I vantaggi pratici della sintassi Lisp includono:

  • coerenza. Funzione / macro / forma speciale seguita dai suoi argomenti. Nessun problema di precedenza degli operatori. Il raggruppamento è in genere esplicito.

  • facile da analizzare e non approfondito. Ciò semplifica la scrittura di strumenti del codice sorgente, come analizzatori statici e browser di codici.

  • estensibilità

    • macro definite dall'utente. Gli utenti possono estendere la sintassi del loro sistema Lisp.
    • sintassi definita dal sistema. Nuovi moduli speciali possono essere aggiunti a un sistema Lisp senza doversi preoccupare di come la loro sintassi interferisce con i moduli esistenti.

Hai anche lanciato un paio di domande sugli effetti collaterali e sulla programmazione funzionale. Quelle non sono correlate alla sintassi del Lisp e dovrebbero essere poste in una domanda separata.

    
risposta data 04.04.2013 - 13:27
fonte
8

Sintassi

Anche in questo caso la sintassi mi ha buttato fuori, ma dopo aver lavorato con esso e averlo letto mi sono reso conto di alcune cose:

1. Lisp non ha operatori, ha solo funzioni, quindi smetti di pensare a +-*/ come operatori e questo ti aiuterà con le cose.

Non pensare:

(1 + 2 + 3 + 4)

o, anche se è corretto:

(+  1 2 3 4)

ma

(sum  1 2 3 4)

e si sentirà più naturale.

Allo stesso modo con - , pensa di più sulla falsariga di:

(subtract-from  x  y1 y2 y3)

e questo dovrebbe aiutare.

2. Le chiamate ai metodi non sono poi così diverse!

Le funzioni sono la prima cosa in ogni forma Lisp (la roba tra () ); poiché i metodi sono funzioni, vengono prima! In realtà non è quello diverso dalla sintassi di Java: tutto ciò che stai facendo è spostare il paren iniziale in primo piano e scambiare le posizioni dell'oggetto e del metodo:

Java:

objectInstance.someMethod(arg1, arg2, arg3);

Clojure:

(.someMethod objectInstance arg1 arg2 arg3)

(Perderai anche le virgole e il punto e virgola!)

Il (.someMethod objectInstance ...) è un modulo speciale che viene espanso in un modulo utilizzando . : (. objectInstance someMethod ...) . Quest'ultima sensazione è strana perché non siamo abituati a pensare a . come a fare qualcosa quando chiamiamo un metodo / proprietà in Java; il primo è comunque il modo idiomatico di fare le cose in Clojure ( reference ).

Nuovo è simile:

new namespace.Object(); // Java

(new namespace.Object)  ; Clojure

"Cose difficili"

Le "cose difficili" che hai menzionato sono effettivamente lì per semplificarti la vita !!

In un commento hai detto che era fastidioso tenere traccia delle cose quando si annidano le espressioni all'interno delle espressioni; i moduli do e let sono creati appositamente per facilitare questa difficoltà.

Iniziamo con do

do ti consente di chiamare le cose in modo imperativo, uno dopo l'altro, senza nidificazione:

(do (.someMethod objectInstance arg1 arg2)
    (.someOtherMethod objectInstance "foo" "bar" "baz")
    (.aMethod anotherObjectInstance 0))

Il comportamento di do è tale che ciascuna di queste chiamate di metodo verrà eseguita nell'ordine in cui vengono fornite e il risultato di ritorno dell'ultima espressione (in questo caso il risultato di ritorno di (.aMethod anotherObjectInstance 0) ) è cosa viene restituito dal blocco do stesso.

Ciascuno di questi metodi può o non può avere effetti collaterali, e può o non può restituire un valore (tutti i quali tranne l'ultimo saranno ignorati).

Let

Il form let fa la stessa cosa di do ma ti dà anche la possibilità di aggiungere variabili locali al mix:

(let [foo "foo"
      bar "bar"
      arg1 (generateValueFrom foo)
      arg2 (generateValueFrom bar)]
  (.someMethod objectInstance arg1 arg2)
  (.someOtherMethod objectInstance foo bar "baz")
  (.aMethod anotherObjectInstance 0))

Ancora una volta, le chiamate al metodo verranno eseguite passo dopo passo nell'ordine, solo che questa volta usano le variabili locali definite nei bind di let (le cose tra [] ) e il valore di ritorno dell'ultima chiamata verrà restituito come valore di ritorno di let .

let è utile per rompere un gruppo di chiamate annidate in qualcosa di più sensato; per esempio:.

(reduce someCombiner {} (filter keepXs (map someTransform ys)))

diventa:

(let [txd (map someTransform ys)
      xs  (filter keepXs txd)]
  ; do the last call in the let's body so that
  ; its return value is what gets returned
  (reduce someCombiner {} xs))

Il Synatx vale la pena?

Scommetti che è il tuo dolce bippy!

La sintassi Lisp consente alla lingua di avere macro. Le macro sono funzioni eseguite in fase di compilazione che riscrivono il codice. Ciò significa che puoi prendere codice ripetitivo o complesso e scrivere una macro che è più semplice da chiamare e che la macro scriverà per te la versione complessa.

Ad esempio, supponiamo di avere il seguente blocco do :

(do (.someMethod      objectInstance arg1 arg2)
    (.aSillyMethod    objectInstance "foo" "bar" "baz")
    (.aSadMethod      objectInstance "alpha")
    (.anHappyMethod   objectInstance 1 2 3)
    (.someOtherMethod objectInstance \a \b \c)
    (.aMethod         objectInstance 0))

Dobbiamo ripetere la stessa istanza dell'oggetto per sei volte! Questo è fastidioso. La macro doto può rendere le cose un po 'più leggere per noi in termini di digitazione:

(doto objectInstance
  (.someMethod      arg1 arg2)
  (.aSillyMethod    "foo" "bar" "baz")
  (.aSadMethod      "alpha")
  (.anHappyMethod   1 2 3)
  (.someOtherMethod \a \b \c)
  (.aMethod         0))

La macro doto ci consente di specificare l'istanza dell'oggetto una volta , in primo piano, anziché dover ripeterci nel codice in un secondo momento - riscrive le espressioni che diamo in do blocco mostrato sopra. (Questa particolare macro è simile alla sintassi with del supporto per alcune lingue.)

Ora, questo è un esempio molto semplice, ma immagina solo quali tipi di attività di codifica potresti automatizzare! Ed è qui che entra in gioco la potenza dei macro di Lisp: puoi scrivere funzioni per far sì che il linguaggio di programmazione scriva il tuo codice per te!

Effetti collaterali

Puoi ancora avere effetti collaterali nel codice Clojure, specialmente se stai facendo interop con Java OOP visto che l'OOP ha effetti collaterali. Se utilizzi più codice core Clojure di Java, non avrai tanti effetti collaterali perché sarà più puramente funzionale.

Molto di quello che ho trovato nella mia esperienza di programmazione funzionale è che si tratta di partizionare il codice che causa effetti collaterali dal codice puramente funzionale. Puoi farlo praticamente in qualunque lingua tu stia usando (anche Java).

Confronto con Scala

Non ho usato Scala, quindi non posso aiutarti lì. Ma spero che quanto sopra è stato informativo.

    
risposta data 04.04.2013 - 23:04
fonte
2

Queste sono questioni di gusto. Mi sono convertito gradualmente nel corso degli anni a preferire la sintassi Lisp sulla sintassi Java. Perché? Buona domanda. Non riesco a individuare una sola ragione, ma qui ci sono alcuni pensieri.

La sintassi del Lisp è molto semplice, quasi banale, anche se forse un po 'strana all'inizio (prima di disimparare alcune vecchie abitudini). La sintassi banale semplifica la lettura programmata del codice Lisp. Il codice è dato è codice. Rendi le macro e DSL più semplici ed efficaci. "Lisp è un linguaggio di programmazione programmabile."

Questo diventa un po 'filosofico, ma sento strongmente che la sintassi Lisp è più vicina alla "vera natura dei programmi". La struttura dei programmi non è lineare: è più simile a un albero oa una rete. Il punto di vista Java è più lineare, orientato alla linea. Lisp POV è gerarchico e ad albero. Quella parentesi all'interno delle paraniti è solo un modo per scrivere alberi.

Pensare più in modo orientato agli alberi (piuttosto che orientato alla linea) consente nuovi modi di pensare, modificare e lavorare con il codice. Ad esempio alcuni potenti strumenti come Paredit , che dimostrano alcuni concetti che potrebbero non avere molto senso nella sintassi di Java, come barf e slurp :

slurp:
(foo (bar |baz) quux zot) -->  (foo (bar |baz quux) zot)

barf:
(foo (bar |baz quux) zot) -->  (foo (bar |baz) quux zot)

Il codice è un albero! :)

    
risposta data 04.04.2013 - 15:35
fonte
0

I principi del linguaggio funzionale come lisp o clojure sono molto utili per scrivere codice auto modificante.

Quindi per aggiungere alcuni valori devi solo generare un'istruzione con + all'inizio e i numeri dopo, in una lingua imperativa devi aggiungere ogni valore aggiuntivo manualmente.

Il fatto è che linguaggi come lisp o clojure non hanno alcuna sintassi, è solo una descrizione di una struttura ad albero come testo.

    
risposta data 04.04.2013 - 13:16
fonte

Leggi altre domande sui tag