15

OK, non imbrogliare ora.Il Clojure funzionale o imperativo Groovy è più leggibile?

No, davvero, prendi un minuto o due e provalo.

Cosa fanno le "posizioni"?

Modifica: semplificata secondo il suggerimento di cgrand.

(defn redux [[current next] flag] [(if flag current next) (inc next)]) 

(defn positions [coll] 
    (map first (reductions redux [1 2] (map = coll (rest coll))))) 

Ora, che ne dici di questa versione?

def positions(coll) { 
    def (current, next) = [1, 1] 
    def previous = coll[0] 
    coll.collect { 
    current = (it == previous) ? current : next 
    next++ 
    previous = it 
    current 
    } 
} 

sto imparando Clojure e me ne sono innamorato, perché mi è sempre piaciuto programmazione funzionale. Mi ci è voluto più tempo per trovare la soluzione Clojure, ma mi sono divertito a dover pensare a di una soluzione elegante. La soluzione Groovy va bene, ma io sono al punto in cui trovo questo tipo di programmazione imperativa noioso e meccanico. Dopo 12 anni di Java, mi sento in un solco e la programmazione funzionale con Clojure è la spinta di cui avevo bisogno.

Giusto, arriva al punto. Bene, devo essere onesto e dire che mi chiedo se capirò il codice Clojure quando tornerò dopo mesi. Certo, potrei commentare il diavolo, ma non ho bisogno di commentare il mio codice Java per capirlo.

Quindi la mia domanda è: si tratta di essere più abituati ai modelli di programmazione funzionale? I guru della programmazione funzionale stanno leggendo questo codice e trovandolo un gioco da ragazzi per capire? Quale versione ha è più facile da capire??

Modifica: ciò che fa questo codice è calcolare le posizioni dei giocatori in base ai loro punti, mentre tiene traccia di coloro che sono legati. Ad esempio:


Pos Points 
1. 36 
1. 36 
1. 36 
4. 34 
5. 32 
5. 32 
5. 32 
8. 30 

risposta

23

Non penso che esista una leggibilità intrinseca. C'è ciò a cui sei abituato e ciò a cui non sei abituato. Sono stato in grado di leggere entrambe le versioni del tuo codice OK. Potrei davvero leggere la tua versione di Groovy più facilmente, anche se non conosco Groovy, perché anch'io ho passato un decennio a guardare C e Java e solo un anno a guardare Clojure. Questo non dice nulla delle lingue, dice solo qualcosa su di me.

Analogamente, posso leggere l'inglese più facilmente dello spagnolo, ma ciò non dice nulla sull'intrinseca leggibilità di quelle lingue. (Lo spagnolo in realtà è probabilmente la lingua "più leggibile" dei due in termini di semplicità e coerenza, ma non riesco ancora a leggerlo). Sto imparando il giapponese in questo momento e ho un brutto momento, ma i madrelingua giapponesi parlano lo stesso dell'inglese.

Se trascorri la maggior parte della tua vita leggendo Java, ovviamente le cose che assomigliano a Java saranno più facili da leggere rispetto alle cose che non lo sono. Finché non trascorri più tempo a guardare le lingue di Lispy guardando ai linguaggi di tipo C, questo probabilmente rimarrà vero.

Per capire una lingua, tra le altre cose è necessario avere familiarità con:

  • sintassi ([vector] vs. (list), hyphens-in-names)
  • vocabolario (cosa vuol dire reductions Come/dove si può osservare? su?)
  • regole di valutazione (fa trattare le funzioni come oggetti di lavoro? E 'un errore nella maggior parte delle lingue.)
  • idiomi, come (map first (some set of reductions with extra accumulated values))

Tutti questi prendere tempo e la pratica e la ripetizione di imparare e interiorizzare. Ma se trascorri i prossimi 6 mesi leggendo e scrivendo un sacco di Clojure, non solo sarai in grado di capire quel codice Clojure tra 6 mesi, probabilmente lo capirai meglio di te ora, e forse anche essere in grado di semplificare esso. Che ne dite di questo:

(use 'clojure.contrib.seq-utils)          ;;' 
(defn positions [coll] 
    (mapcat #(repeat (count %) (inc (ffirst %))) 
      (partition-by second (indexed coll)))) 

Guardando il codice Clojure ho scritto un anno fa, sono inorridito quanto è fatto male, ma posso leggerlo OK. (Non sto dicendo che il tuo codice Clojure sia orribile, non ho avuto problemi a leggerlo e io non sono un guru.)

+0

Ottima risposta. Adoro la tua versione. Devo ammettere che mi ci è voluto un po 'per averlo. È abbastanza intelligente! Grazie. – foxdonut

8

modifica: potrebbe non essere più pertinente.

Il Clojure è contorto per me. Contiene più astrazioni che devono essere capite. Questo è il prezzo dell'uso di funzioni di ordine superiore, devi sapere cosa significano. Quindi in un caso isolato, l'imperativo richiede meno conoscenza. Ma il potere delle astrazioni è nei loro mezzi di combinazione. Ogni ciclo imperativo deve essere letto e compreso, mentre le astrazioni di sequenza consentono di rimuovere la complessità di un ciclo e combinare potenti opperazioni.

Vorrei inoltre sostenere che la versione di Groovy è almeno parzialmente funzionale in quanto utilizza collect, che è in realtà una mappa, una funzione di ordine superiore. Ha anche qualche stato in esso.

Ecco come avrei scritto la versione Clojure:

(defn positions2 [coll] 
    (let [current (atom 1) 
     if-same #(if (= %1 %2) @current (reset! current (inc %3)))] 
    (map if-same (cons (first coll) coll) coll (range (count coll))))) 

Questo è molto simile alla versione Groovy in quanto utilizza una "corrente" mutevole, ma si differenzia in quanto non dispone di una prossima/prev variabile - invece utilizza sequenze immutabili per quelli. Come Brian lo ha detto in modo inequivocabile, la leggibilità non è intrinseca. Questa versione è la mia preferenza per questo caso particolare, e sembra di stare da qualche parte nel mezzo.

+0

Ho modificato il codice. Non penso che sia troppo complicato ora. – foxdonut

+0

Cool :) Non era una critica al tuo codice, stavo solo rispondendo alla domanda su quale stile fosse più facile da capire. Ora i pali della porta si sono mossi! Hehehe. –

+1

Beh, non l'ho preso come critica, e inoltre, avevi ragione, la prima versione/era/contorta. :-) – foxdonut

8

Sono d'accordo con Timothy: introduci troppe astrazioni. Ho rielaborato il codice e si è conclusa con:

(defn positions [coll] 
    (reductions (fn [[_ prev-score :as prev] [_ score :as curr]] 
       (if (= prev-score score) prev curr)) 
    (map vector (iterate inc 1) coll))) 

proposito il codice,

(defn use-prev [[a b]] (= a b)) 
(defn pairs [coll] (partition 2 1 coll)) 
(map use-prev (pairs coll)) 

può essere semplicemente refactoring come:

(map = coll (rest coll)) 
+0

Grazie, ho modificato sopra in base al tuo suggerimento per il refactoring. – foxdonut

3

troppo sto imparando Clojure e amarla. Ma in questa fase del mio sviluppo, la versione di Groovy era più facile da capire. Quello che mi piace di Clojure è leggere il codice e avere "Aha!" esperienza quando finalmente "ottieni" cosa sta succedendo. Cosa mi piace è l'esperienza simile che si verifica pochi minuti dopo quando ti rendi conto di tutti i modi in cui il codice potrebbe essere applicato ad altri tipi di dati senza modifiche al codice. Ho perso il conto del numero di volte in cui ho elaborato un codice numerico in Clojure e poi, poco dopo, ho pensato a come utilizzare lo stesso codice con stringhe, simboli, widget, ...

L'analogia che uso riguarda l'apprendimento dei colori. Ricordi quando sei stato presentato al colore rosso? L'hai capito abbastanza velocemente - c'è tutta questa roba rossa nel mondo.Poi hai sentito il termine magenta e sei stato perso per un po '. Ma di nuovo, dopo un po 'più di esposizione, hai capito il concetto e hai un modo molto più specifico per descrivere un particolare colore. Devi interiorizzare il concetto, avere un po 'più di informazioni nella tua testa, ma alla fine ottieni qualcosa di più potente e conciso.

4

Il Clojure è più contorto a prima vista; anche se forse è più elegante. OO è il risultato per rendere il linguaggio più "relazionabile" a livello superiore. I linguaggi funzionali sembrano avere un aspetto più "algoritmico" (primitivo/elementare). Questo è proprio quello che ho sentito al momento. Forse cambierà quando avrò più esperienza con il clojure.

Ho paura di decifrare nel gioco il linguaggio più conciso o risolvere un problema nella minima riga di codice.

Il problema sono 2 pieghe per me:

  1. Quanto facile a prima vista per avere un'idea di ciò che il codice sta facendo ?. Questo è importante per i manutentori del codice.

  2. Quanto è facile indovinare la logica del codice ?. Troppo prolisso/prolisso ?. Troppo terso?

"Rendi tutto il più semplice possibile, ma non più semplice."

Albert Einstein

+0

Non penso che riguardi le meno righe di codice. Almeno non è mia intenzione. I problemi sono piuttosto quelli che enumerate elegantemente. Ad esempio, preferisco la mia versione del codice piuttosto che la versione più breve, ma meno leggibile (IMHO) di "gnud" (nei commenti espansi) in questo script: http://stackoverflow.com/questions/188162/ what-is-the-più-utile-script-avete-scritto-per-tutti i giorni-vita/245724 # 245724 – foxdonut

3

Groovy supporta diversi stili di risolvere anche questo problema:

coll.groupBy{it}.inject([]){ c, n -> c + [c.size() + 1] * n.value.size() } 

sicuramente non riscritta essere abbastanza, ma non troppo difficile da capire.

3

So che questa non è una risposta alla domanda, ma sarò in grado di "capire" il codice molto meglio se ci sono test, come ad esempio:

assert positions([1]) == [1] 
assert positions([2, 1]) == [1, 2] 
assert positions([2, 2, 1]) == [1, 1, 3] 
assert positions([3, 2, 1]) == [1, 2, 3] 
assert positions([2, 2, 2, 1]) == [1, 1, 1, 4] 

Che mi dirà, un anno dalla ora, ciò che il codice dovrebbe fare. Molto meglio di qualsiasi versione eccellente del codice che ho visto qui.

Am I veramente off topic?

L'altra cosa è, penso che la "leggibilità" dipenda dal contesto. Dipende chi manterrà il codice. Ad esempio, al fine di mantenere la versione "funzionale" del codice Groovy (comunque brillante), saranno necessari non solo i programmatori Groovy, ma i programmatori Groovy funzionali ... L'altro esempio più rilevante è: se poche righe il codice lo rende più facile da capire per i programmatori Clojure "principianti", quindi il codice sarà nel complesso più leggibile perché sarà compreso da una comunità più ampia: non è necessario aver studiato Clojure per tre anni per essere in grado di cogliere il codice e fare modifiche ad esso.

Problemi correlati