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.