2012-04-24 10 views
9

Ho iniziato a studiare Clojure di recente. Generalmente sembra interessante, ma non riesco ad abituarmi ad alcuni inconvenienti sintattici (rispetto alla precedente esperienza di Ruby/C#).Transizione da infisso a notazione prefisso

Notazione di prefisso per espressioni nidificate. In Ruby mi abituo a scrivere espressioni complesse con concatenamento/piping da sinistra a destra: some_object.map { some_expression }.select { another_expression }. È davvero utile quando passi dal valore di input al risultato passo dopo passo, puoi concentrarti su una singola trasformazione e non è necessario spostare il cursore durante la digitazione. Contrariamente a ciò, quando scrivo espressioni annidate in Clojure, scrivo il codice dall'espressione interna a quella esterna e devo spostare il cursore costantemente. Rallenta e distrae. Conosco i macro -> e ->> ma ho notato che non è un idioma. Hai avuto lo stesso problema quando hai iniziato a scrivere in Clojure/Haskell ecc? come l'hai risolto?

+0

Penso che sia solo un idioma perché è quello a cui sei abituato. La notazione del prefisso è molto più coerente di infisso e quindi può essere più facile da leggere una volta che ci si abitua. –

+0

Mike, considera che è necessario scrivere espressione '(filtro # (expr1) (mappa # (expr2) expr3))'. Cominceresti a scrivere da expr3 interno o da filtro? – Alexey

+3

Di solito parto dall'esterno e mi faccio strada dentro. Quindi penso a quello che voglio e semplicemente cammino indietro da quello. –

risposta

3

ho davvero visto lo stesso ostacolo quando ho iniziato con la lisca ed è stato davvero fastidioso fino a quando ho visto i modi che rende il codice più semplice e più chiaro, una volta ho capito al rialzo il fastidio sbiadito

initial + scale + offset 

divenne

(+ initial scale offset) 

e quindi provare (+) prefisso notazione permette funzioni per specificare i valori della propria identità

user> (*) 
1 
user> (+) 
0 

Ci sono molti altri esempi e il mio punto è NON difendere la notazione del prefisso. Spero solo di trasmettere che la curva di apprendimento si appiattisce (emotivamente) mentre i lati positivi diventano evidenti.

Naturalmente quando si avvia , la macro di scrittura quindi la notazione del prefisso diventa un must-have invece di una comodità.


per affrontare la seconda parte della sua domanda, il filo prima e filo ultimi macro sono in qualsiasi momento idiomatica rendono il codice più chiaro :) sono più spesso utilizzati nelle funzioni chiamate di pura aritmetica se nessuno andrà in errore voi per usarli quando rendono l'equazione più appetibile.


ps: (.. object object2 object3) ->object().object2().object3();

(doto my-object 
    (setX 4) 
    (sety 5)` 
+0

Arthur, quindi la cosa che la notazione del prefisso è comoda per scrivere il codice come invocazione infetta di '.methods' con punto, o è davvero meno conveniente ma ha altri vantaggi? – Alexey

+1

una volta trovata la macro '..' trovo la notazione inflix in jave molto ingombrante e difficile da leggere e scrivere. (questo è solo il mio modesto parere) –

+1

@ArthurUlfeldt, puoi approfondire "ovviamente quando inizi a scrivere macro, quindi la notazione inflix diventa un must-have invece di una comodità"? O intendevi dire prefisso qui? – ivant

4

A mia conoscenza -> e ->> sono idiomatica in Clojure. Li uso sempre e, a mio parere, di solito portano a un codice molto più leggibile.

Ecco alcuni esempi di queste macro utilizzati in progetti popolari provenienti da tutto il "ecosistema" Clojure:

Prova con l'esempio :)

9

Ho sentito lo stesso circa Lisps inizialmente così sento il tuo dolore :-)

Tuttavia, la buona notizia è che con un po 'di tempo e un uso regolare probabilmente inizierai ad apprezzare la notazione del prefisso. Infatti, ad eccezione delle espressioni matematiche, ora preferisco lo stile infisso.

ragioni per come notazione prefissa:

  • Coerenza con funzioni - la maggior parte delle lingue utilizzano un mix di infisso (operatori matematici) e il prefisso notazione (chiamata funzionale). In Lisps è tutto coerente che ha una certa eleganza se si considera che gli operatori matematici siano funzioni
  • Macro - diventa molto più sano se la chiamata di funzione è sempre nella prima posizione.
  • Varargs - È bello poter disporre di un numero variabile di parametri per quasi tutti gli operatori. (+ 1 2 3 4 5) è IMHO più bello di 1 + 2 + 3 + 4 + 5

Un trucco è quindi di utilizzare -> e ->> librerally quando ha senso logico per strutturare il codice in questo modo. Ciò è in genere utile quando si gestiscono operazioni successive su oggetti o raccolte, ad es.

(->> 
    "Hello World" 
    distinct 
    sort 
    (take 3)) 

==> (\space \H \W) 

Il trucco finale ho trovato molto utile quando si lavora in stile prefisso è quello di fare buon uso di indentazione durante la creazione di espressioni più complesse. Se si rientra correttamente, allora ci si accorge che la notazione prefisso è in realtà abbastanza chiaro da leggere:

(defn add-foobars [x y] 
    (+ 
    (bar x y) 
    (foo y) 
    (foo x))) 
+0

L'incostanza con '->' e '- >>' è che ne hai due. Alcune funzioni ('cons',' map', 'apply') prendono l'oggetto composito come ultimo argomento, e altre (' conj', 'update-in') come prima e non puoi incatenarle insieme con singolo' -> 'o '- >>'. Post scriptumNei puri linguaggi OOP come la notazione infissa di Ruby & Smalltalk è completamente coerente: '1. + (2), [1] .zip ([2])' e l'oggetto composito è sempre l'argomento prima del punto. – Alexey

+3

@Alexey: C'è un motivo per '->' e '- >>' e l'ordine di argomenti differenti. È dovuta la distinzione tra l'astrazione e le collezioni seq. Penso che questo confonda perché una collezione viene spesso trattata come un seq. Mi riferisco spesso alla spiegazione di Rich Hickey: http://groups.google.com/group/clojure/msg/a8866d34b601ff43 –

+2

Inoltre, se lo si desidera veramente, la libreria swiss-arrows fornisce la macro con la bacchetta di diamante: http: // stackoverflow .com/q/10068398/148578 –

3

Se si dispone di una catena lunga espressione, utilizzare let. Lunghe espressioni in fuga o espressioni profondamente annidate non sono particolarmente leggibili in nessuna lingua. Questo è un male:

(do-something (map :id (filter #(> (:age %) 19) (fetch-data :people)))) 

Questo è marginalmente migliore:

(do-something (map :id 
        (filter #(> (:age %) 19) 
          (fetch-data :people)))) 

Ma questo è anche un male:

fetch_data(:people).select{|x| x.age > 19}.map{|x| x.id}.do_something 

Se stai leggendo questo, che cosa abbiamo bisogno di sapere? Stiamo chiamando do_something su alcuni attributi di alcuni sottoinsiemi di people. Questo codice è difficile da leggere perché c'è così tanta distanza tra il primo e l'ultimo, che dimentichiamo quello che stiamo guardando nel momento in cui viaggiamo tra di loro.

Nel caso di Ruby, do_something (o qualsiasi altra cosa produca il nostro risultato finale) è perso alla fine della riga, quindi è difficile dire cosa stiamo facendo al nostro people. Nel caso di Clojure, è ovvio che lo do-something è quello che stiamo facendo, ma è difficile dire a cosa stiamo andando senza leggere l'intera cosa all'interno.

Qualsiasi codice più complesso di questo semplice esempio diventerà piuttosto doloroso. Se tutto il tuo codice è simile a questo, il tuo collo si stancherà a scrutare avanti e indietro su tutte queste linee di spaghetti.

Preferirei qualcosa di simile:

(let [people (fetch-data :people) 
     adults (filter #(> (:age %) 19) people) 
     ids (map :id adults)] 
    (do-something ids)) 

Ora è ovvio: comincio con people, ho goof intorno, e poi ho do-something a loro.

E si potrebbe ottenere via con questo:

fetch_data(:people).select{|x| 
    x.age > 19 
}.map{|x| 
    x.id 
}.do_something 

Ma probabilmente sarei piuttosto fare questo, per lo meno:

adults = fetch_data(:people).select{|x| x.age > 19} 
do_something(adults.map{|x| x.id}) 

Non è anche inaudito di utilizzare let anche quando le tue espressioni intermedie non hanno un buon nome. (Questo stile è talvolta usato nel codice sorgente del proprio Clojure, ad esempio, il codice sorgente per defmacro)

(let [x (complex-expr-1 x) 
     x (complex-expr-2 x) 
     x (complex-expr-3 x) 
     ... 
     x (complex-expr-n x)] 
    (do-something x)) 

questo può essere di grande aiuto nel debugging, in quanto è possibile controllare le cose in qualsiasi punto facendo:

(let [x (complex-expr-1 x) 
     x (complex-expr-2 x) 
     _ (prn x) 
     x (complex-expr-3 x) 
     ... 
     x (complex-expr-n x)] 
    (do-something x)) 
Problemi correlati