73 votes

Comment lire mentalement Lisp/Clojure code

Un grand merci pour toutes les belles réponses! Ne peut pas marquer un seul comme correct

Remarque: Déjà un wiki

Je suis nouveau à la programmation fonctionnelle et bien que je puisse lire les simples fonctions en programmation Fonctionnelle, par exemple, pour le calcul de la factorielle d'un nombre, je trouve qu'il est difficile de lire les grandes fonctions. Partie de la raison est je pense à cause de mon incapacité à comprendre les petits blocs de code à l'intérieur d'une définition de fonction, et aussi en partie parce qu'il est de plus en plus difficile pour moi de match () dans le code.

Il serait génial si quelqu'un pouvait me guider à travers la lecture du code et de me donner quelques conseils sur la façon de rapidement déchiffrer une partie du code.

Note: je peux comprendre ce code si je regarde pendant 10 minutes, mais je doute que ce même code a été écrit en Java, il me faudrait 10 minutes. Donc, je pense que pour se sentir à l'aise dans Lisp style code, je dois le faire plus vite

Note: je sais que c'est une question subjective. Et je ne suis pas à la recherche d'un quelconque prouvable bonne réponse ici. Juste des commentaires sur la façon dont vous aller à ce sujet la lecture de ce code, serait la bienvenue, et très utile

(defn concat
  ([] (lazy-seq nil))
  ([x] (lazy-seq x))
  ([x y]
    (lazy-seq
      (let [s (seq x)]
        (if s
          (if (chunked-seq? s)
            (chunk-cons (chunk-first s) (concat (chunk-rest s) y))
            (cons (first s) (concat (rest s) y)))
          y))))
  ([x y & zs]
     (let [cat (fn cat [xys zs]
                 (lazy-seq
                   (let [xys (seq xys)]
                     (if xys
                       (if (chunked-seq? xys)
                         (chunk-cons (chunk-first xys)
                                     (cat (chunk-rest xys) zs))
                         (cons (first xys) (cat (rest xys) zs)))
                       (when zs
                         (cat (first zs) (next zs)))))))]
       (cat (concat x y) zs))))

57voto

Brian Carper Points 40078

Je pense que concat est un mauvais exemple pour essayer de comprendre. C'est une fonction de base et c'est à un niveau plus bas que le code que vous auriez normalement écrire vous-même, parce qu'il s'efforce d'être efficace.

Une autre chose à garder à l'esprit est que Clojure code est extrêmement dense par rapport à du code Java. Un peu de Clojure code est beaucoup de travail. Le même code en Java ne serait pas 23 lignes. Il serait probablement plusieurs classes et interfaces, un grand nombre de méthodes, beaucoup de locaux temporaires jeter variables et mal à l'aise les boucles et généralement toutes sortes de passe-partout.

Certains conseils généraux...

  1. Essayez d'ignorer les parens la plupart du temps. Utilisez l'indentation à la place (comme Nathan Sanders suggère). par exemple

    (if s
      (if (chunked-seq? s)
        (chunk-cons (chunk-first s) (concat (chunk-rest s) y))
        (cons (first s) (concat (rest s) y)))
      y))))
    

    Quand je regarde que mon cerveau voit:

    if foo
      then if bar
        then baz
        else quux
      else blarf
    
  2. Si vous placez votre curseur sur une parenthèse et votre éditeur de texte n'est pas de la syntaxe mettre en évidence la correspondance, je vous suggère de trouver un nouvel éditeur.

  3. Parfois, ça aide à lire le code à l'intérieur. Clojure code tend à être profondément imbriqués.

    (let [xs (range 10)]
      (reverse (map #(/ % 17) (filter (complement even?) xs))))
    

    Mauvais: "nous commençons Donc avec les nombres de 1 à 10. Ensuite, nous sommes de l'inversion de l'ordre de la cartographie du filtrage de l'effectif de l'attente j'ai oublié de quoi je parle."

    Bon: "OK, nous sommes donc à prendre quelques xs. (complement even?) signifie le contraire de même, si "bizarre". Nous sommes donc le filtrage de certains collection de sorte que seules les nombres impairs à gauche. Ensuite, nous sommes les divisant tout par 17. Ensuite, nous sommes de l'inversion de l'ordre. Et l' xs en question sont de 1 à 10, gotcha."

    Parfois, il contribue à le faire explicitement. Prendre les résultats intermédiaires, les jeter dans un let et de leur donner un nom afin de vous comprendre. Le REPL est fait pour jouer comme ça. Exécuter les résultats intermédiaires et voir ce que chaque étape de la démarche.

    (let [xs (range 10)
          odd? (complement even?)
          odd-xs (filter odd? xs)
          odd-xs-over-17 (map #(/ % 17) odd-xs)
          reversed-xs (reverse odd-xs-over-17)]
      reversed-xs)
    

    Bientôt, vous serez en mesure de faire ce genre de chose mentalement sans effort.

  4. Faire un usage libéral de l' (doc). L'utilité de disposer de la documentation disponible à la REPL ne peut pas être surestimée. Si vous utilisez clojure.contrib.repl-utils et ont votre .clj fichiers sur le chemin de la classe, vous pouvez le faire (source some-function) et de voir tout le code source. Vous pouvez le faire (show some-java-class) et voir une description de toutes les méthodes. Et ainsi de suite.

Être capable de lire quelque chose rapidement, ne vient qu'avec l'expérience. Lisp n'est pas plus difficile à lire que toute autre langue. Il se trouve que la plupart des langues ressembler à C, et la plupart des programmeurs passent la plupart de leur temps à la lecture, donc il semble que la syntaxe C est plus facile à lire. Pratique, pratique, pratique.

48voto

Nathan Sanders Points 10641

Code Lisp, en particulier, est encore plus difficile à lire que les autres langages fonctionnels en raison de la syntaxe normale. Wojciech donne une bonne réponse pour l'amélioration de votre compréhension sémantique. Voici un peu d'aide sur la syntaxe.

Tout d'abord, lors de la lecture du code, ne vous inquiétez pas à propos de parenthèses. Vous soucier de l'indentation. La règle générale est que les choses au même niveau d'indentation sont liées. Donc:

      (if (chunked-seq? s)
        (chunk-cons (chunk-first s) (concat (chunk-rest s) y))
        (cons (first s) (concat (rest s) y)))

Deuxièmement, si tout ne peut pas tenir sur une seule ligne, tiret à la ligne suivante d'un petit montant. C'est presque toujours de deux espaces:

(defn concat
  ([] (lazy-seq nil))  ; these two fit
  ([x] (lazy-seq x))   ; so no wrapping
  ([x y]               ; but here
    (lazy-seq          ; (lazy-seq indents two spaces
      (let [s (seq x)] ; as does (let [s (seq x)]

Troisièmement, si plusieurs arguments d'une fonction ne peut pas tenir sur une seule ligne, la ligne de la deuxième, troisième, etc arguments sous le premier départ de la parenthèse. De nombreuses macros ont une règle similaire avec des variations pour permettre les parties importantes apparaissent en premier.

; fits on one line
(chunk-cons (chunk-first s) (concat (chunk-rest s) y))

; has to wrap: line up (cat ...) underneath first ( of (chunk-first xys)
                     (chunk-cons (chunk-first xys)
                                 (cat (chunk-rest xys) zs))

; if you write a C-for macro, put the first three arguments on one line
; then the rest indented two spaces
(c-for (i 0) (< i 100) (add1 i)
  (side-effects!)
  (side-effects!)
  (get-your (side-effects!) here))


Ces règles vous aider à trouver les blocs dans le code: si vous voyez

(chunk-cons (chunk-first s)

Ne comptez pas les parenthèses! Vérifiez la ligne suivante:

(chunk-cons (chunk-first s)
            (concat (chunk-rest s) y))

Vous savez que la première ligne n'est pas une expression complète parce que la ligne suivante est échancré en dessous.

Si vous voyez l' defn concat à partir de ci-dessus, vous le savez, vous avez trois blocs, car il y a trois choses sur le même niveau. Mais tout en dessous de la troisième ligne est en retrait sous elle, de sorte que le reste appartient à cette troisième bloc.

Voici un guide de style pour le Régime. Je ne sais pas Clojure, mais la plupart de ces règles doivent être les mêmes puisque aucun des autres Lisps varient beaucoup.

7voto

Rappelez-vous que le programme fonctionnel se compose d'expressions, non pas des déclarations. Par exemple, la forme (if condition expr1 expr2) prend son 1er arg comme une condition pour tester le booléen falue, les évalue, et si il eval ed à vrai, alors il évalue et retourne expr1, sinon évalue et retourne expr2. Lors de chaque formulaire renvoie une expression de certains de la syntaxe habituelle des constructions comme PUIS ou d'AUTRE mots-clés peuvent tout simplement disparaître. Notez qu'ici, if lui-même évalue une expression.

Maintenant, au sujet de l'évaluation: En Clojure (et d'autres Lisps) la plupart des formes que vous rencontrez sont des appels de fonction de la forme (f a1 a2 ...), où tous les arguments d' f sont évaluées avant même l'appel de la fonction; mais les formes peuvent être aussi des macros ou des formes particulières qui ne sont pas d'évaluer une partie (ou la totalité) de ses arguments. En cas de doute, consultez la documentation (doc f) ou simplement vérifier dans REPL:

user=> apply fonction

d'une macro.

Ces deux règles:

  • nous avons des expressions, non pas des déclarations
  • l'évaluation d'un sous-formulaire peut se produire ou non, en fonction de la façon dont forme extérieure se comporte

devrait faciliter votre groking de programmes Lisp, esp. si ils ont de belles indentation comme dans l'exemple que vous avez donné.

Espérons que cette aide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X