web-dev-qa-db-ja.com

精神的にLISP / Clojureコードを読む方法

すべての美しい答えをありがとう!1つだけを正しいとマークすることはできません

注:すでにwiki

私は関数型プログラミングに不慣れで、関数型プログラミングの簡単な関数を読むことができます。数の階乗を計算していると、大きな関数を読むのが難しいと感じています。その理由の1つは、関数定義内のコードの小さなブロックを理解できないことと、コード内の( )を一致させることが難しくなっていることです。

誰かが私にいくつかのコードを読んでもらい、いくつかのコードをすばやく解読する方法についてのヒントを教えてくれるといいですね。

注:このコードを10分間見つめると理解できますが、同じコードがJavaで記述されていたとしたら、10分かかるとは思えません。したがって、LISPスタイルで快適に感じると思います。コード、私はそれをより速くしなければなりません

注:これは主観的な質問です。ここでは、証明できる正しい答えを求めていません。このコードの読み方についてコメントするだけで、歓迎され、非常に役立ちます

(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))))
72
user855

特にLISPコードは、通常の構文のため、他の関数型言語よりも読みにくいです。 Wojciechはあなたの意味理解を改善するための良い答えを与えます。ここに構文に関するいくつかのヘルプがあります。

まず、コードを読むときは、括弧について心配する必要はありません。インデントが心配です。原則として、同じインデントレベルのものは関連しています。そう:

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

次に、すべてを1行に収めることができない場合は、次の行を少しインデントします。これはほぼ常に2つのスペースです:

(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)]

第3に、関数への複数の引数が1行に収まらない場合は、最初の開始括弧の下に2番目、3番目などの引数を並べます。多くのマクロには、重要な部分が最初に表示されるようにバリエーションのある同様のルールがあります。

; 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))

これらのルールは、コード内のブロックを見つけるのに役立ちます。

(chunk-cons (chunk-first s)

括弧は数えないでください!次の行を確認してください。

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

次の行はその下にインデントされているため、最初の行は完全な式ではないことがわかります。

上からdefn concatを見ると、同じレベルに3つのものがあるため、3つのブロックがあることがわかります。ただし、3行目より下のすべてはその下にインデントされているため、残りはその3番目のブロックに属します。

これがSchemeのスタイルガイドです 。 Clojureはわかりませんが、他のLispはどれも大きく変化しないため、ほとんどのルールは同じである必要があります。

concatは理解しようとする悪い例だと思います。これはコア機能であり、効率を高めるように努めているため、通常自分で作成するコードよりも低レベルです。

もう1つ覚えておくべきことは、ClojureコードはJavaコードに比べて非常に密度が高いことです。小さなClojureコードは多くの作業を行います。Java = 23行ではないでしょう。複数のクラスとインターフェース、非常に多くのメソッド、多くのローカルの一時的な使い捨て変数、厄介なループ構造、そして一般的にすべての種類の定型文である可能性があります。

しかし、いくつかの一般的なヒント...

  1. ほとんどの場合、親を無視するようにしてください。代わりにインデントを使用してください(Nathan Sandersが示唆しているように)。例えば.

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

    私がそれを見ると、私の脳は次のように見えます。

    if foo
      then if bar
        then baz
        else quux
      else blarf
    
  2. カーソルを親に置いても、テキストエディタが構文に対応していない場合は、一致するエディタを強調表示して、新しいエディタを見つけることをお勧めします。

  3. コードを裏返しに読むと役立つ場合があります。 Clojureコードは深くネストされる傾向があります。

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

    悪い例:"1から10までの数字から始めます。次に、待機の補数のフィルタリングのマッピングの順序を逆にします。私が話していることを忘れました。"

    良い例:"OK、xsを取得しています。(complement even?)は偶数の反対を意味するため、「奇数」です。したがって、一部のコレクションをフィルタリングして、奇数のみを残します。次に、それらをすべて17で除算します。次に、それらの順序を逆にします。問題のxsは1から10です。 "

    これを明示的に行うと役立つ場合があります。中間結果を取得し、それらをletに入れて、理解できるように名前を付けます。 REPLは、このように遊ぶために作られています。中間結果を実行して、各ステップで何が得られるかを確認してください。

    (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)
    

    すぐにあなたは努力なしで精神的にこの種のことをすることができるでしょう。

  4. (doc)を自由に使用してください。 REPLでドキュメントを利用できることの有用性は誇張することはできません。clojure.contrib.repl-utilsを使用し、クラスパスに.cljファイルがある場合は、(source some-function)を実行して、 (show some-Java-class)を実行して、その中のすべてのメソッドの説明を表示できます。

何かをすばやく読むことができるのは、経験が必要です。 LISPは他のどの言語よりも読みにくいものではありません。ほとんどの言語がCのように見えるのは偶然であり、ほとんどのプログラマーはほとんどの時間をそれを読むことに費やしているので、C構文の方が読みやすいようです。練習練習練習。

57
Brian Carper

まず、関数型プログラムはステートメントではなく式で構成されていることを思い出してください。たとえば、フォーム(if condition expr1 expr2)は、ブール値の誤りをテストする条件として最初の引数を取り、それを評価します。trueと評価された場合は、expr1を評価して返します。それ以外の場合は、expr2を評価して返します。すべてのフォームが式を返すと、THENやELSEキーワードなどの通常の構文構造の一部が消えてしまう場合があります。ここで、if自体も式に評価されることに注意してください。

評価について:Clojure(および他のLisp)で遭遇するほとんどのフォームは、(f a1 a2 ...)形式の関数呼び出しであり、fへのすべての引数は実際の関数呼び出しの前に評価されます。ただし、フォームは、引数の一部(またはすべて)を評価しないマクロまたは特殊なフォームにすることもできます。疑わしい場合は、ドキュメント(doc f)を参照するか、REPLをチェックインしてください。

user=> apply
#<core$apply__3243 clojure.core$apply__3243@19bb5c09>
関数
user=> doseq
Java.lang.Exception: Can't take value of a macro: #'clojure.core/doseq
マクロ。

これらの2つのルール:

  • ステートメントではなく式があります
  • サブフォームの評価は、外部フォームの動作に応じて発生する場合と発生しない場合があります

特に、LISPプログラムのうなり声を和らげるはずです。あなたが与えた例のように彼らが素敵なインデントを持っているなら。

お役に立てれば。

7