Cách đọc mã Lisp / Clojure tinh thần


76

Cảm ơn rất nhiều cho tất cả các câu trả lời tuyệt vời! Không thể chỉ đánh dấu một là đúng

Lưu ý: Đã là wiki

Tôi chưa quen với lập trình hàm và trong khi tôi có thể đọc các hàm đơn giản trong lập trình Hàm, chẳng hạn như tính giai thừa của một số, tôi thấy khó đọc các hàm lớn. Một phần lý do là tôi nghĩ vì tôi không có khả năng tìm ra các khối mã nhỏ hơn trong một định nghĩa hàm và một phần nữa là vì tôi đang trở nên khó khớp ( )trong mã.

Sẽ thật tuyệt nếu ai đó có thể hướng dẫn tôi đọc một số mã và cho tôi một số mẹo về cách nhanh chóng giải mã một số mã.

Lưu ý: Tôi có thể hiểu mã này nếu tôi nhìn chằm chằm vào nó trong 10 phút, nhưng tôi nghi ngờ nếu cùng một mã này được viết bằng Java, tôi sẽ mất 10 phút. Vì vậy, tôi nghĩ để cảm thấy thoải mái với mã kiểu Lisp, tôi phải làm điều đó nhanh hơn

Lưu ý: Tôi biết đây là một câu hỏi chủ quan. Và tôi không tìm kiếm bất kỳ câu trả lời chính xác nào ở đây. Chỉ nhận xét về cách bạn đọc mã này, sẽ được hoan nghênh và rất hữu ích

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

3
Kinh nghiệm? Một bạn đã quen đọc mã Lisp thì sẽ nhanh hơn. Tuy nhiên, một trong những phàn nàn chính về Lisp là nó khó đọc, vì vậy đừng mong đợi nó trở nên trực quan cho bạn nhanh chóng.
CK Nate

10
Đây không phải là một chức năng dễ dàng. Nếu bạn có thể hiểu hết nó sau 10 phút, bạn vẫn ổn.
Michiel de Mare

Câu trả lời:


49

Đặc biệt, mã Lisp thậm chí còn khó đọc hơn các ngôn ngữ chức năng khác vì cú pháp thông thường. Wojciech đưa ra một câu trả lời tốt để cải thiện sự hiểu biết ngữ nghĩa của bạn. Đây là một số trợ giúp về cú pháp.

Đầu tiên, khi đọc mã, đừng lo lắng về dấu ngoặc đơn. Lo lắng về vết lõm. Nguyên tắc chung là những thứ ở cùng mức thụt lề có liên quan với nhau. Vì thế:

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

Thứ hai, nếu bạn không thể xếp mọi thứ vào một dòng, hãy thụt lề dòng tiếp theo một lượng nhỏ. Đây hầu như luôn luôn là hai không gian:

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

Thứ ba, nếu nhiều đối số cho một hàm không thể vừa trên một dòng, hãy xếp hàng đối số thứ hai, thứ ba, v.v. bên dưới dấu ngoặc đơn bắt đầu của hàm đầu tiên. Nhiều macro có quy tắc tương tự với các biến thể để cho phép các phần quan trọng xuất hiện trước.

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

Các quy tắc này giúp bạn tìm thấy các khối trong mã: nếu bạn thấy

(chunk-cons (chunk-first s)

Không tính dấu ngoặc đơn! Kiểm tra dòng tiếp theo:

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

Bạn biết rằng dòng đầu tiên không phải là một biểu thức hoàn chỉnh vì dòng tiếp theo được thụt vào bên dưới nó.

Nếu bạn nhìn thấy defn concattừ trên cao, bạn biết bạn có ba khối, bởi vì có ba thứ ở cùng một cấp. Nhưng mọi thứ bên dưới dòng thứ ba được thụt vào bên dưới nó, vì vậy phần còn lại thuộc về khối thứ ba đó.

Đây là hướng dẫn kiểu cho Scheme . Tôi không biết Clojure, nhưng hầu hết các quy tắc phải giống nhau vì không có Lisps nào khác thay đổi nhiều.


Trong mẫu mã thứ hai của bạn, làm thế nào để kiểm soát việc thụt đầu dòng của dòng thứ năm, đó là (lười biếng-seq, trong Emacs Theo mặc định, nó phù hợp với 'y' trên dòng trước đó?.
Wei Hu

Xin lỗi, tôi không biết. Tôi không sử dụng clojure và ví dụ đó không dịch chính xác sang Scheme. Bạn có thể kiểm tra một biến như clojure-indent-offset. Ví dụ: đối với Haskell, tôi phải thêm '(haskell-indent-offset 2) vào các biến tập hợp tùy chỉnh của mình.
Nathan Shively-Sanders

59

Tôi nghĩ đó concatlà một ví dụ xấu để cố gắng hiểu. Đó là một chức năng cốt lõi và nó ở cấp độ thấp hơn so với mã bạn thường tự viết, bởi vì nó cố gắng trở nên hiệu quả.

Một điều cần lưu ý nữa là mã Clojure cực kỳ dày đặc so với mã Java. Một mã Clojure nhỏ sẽ làm được nhiều việc. Mã tương tự trong Java sẽ không dài 23 dòng. Nó có thể là nhiều lớp và nhiều giao diện, rất nhiều phương thức, rất nhiều biến tạm thời cục bộ và các cấu trúc lặp khó xử và nói chung là tất cả các loại boilerplate.

Tuy nhiên, một số mẹo chung ...

  1. Cố gắng bỏ qua những điểm yếu trong hầu hết thời gian. Thay vào đó, hãy sử dụng thụt lề (như Nathan Sanders gợi ý). ví dụ

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

    Khi tôi nhìn vào điều đó, não của tôi sẽ thấy:

    if foo
      then if bar
        then baz
        else quux
      else blarf
    
  2. Nếu bạn đặt con trỏ trên dấu ngoặc và trình soạn thảo văn bản của bạn không đánh dấu cú pháp phù hợp, tôi khuyên bạn nên tìm một trình soạn thảo mới.

  3. Đôi khi nó giúp đọc mã từ trong ra ngoài. Mã Clojure có xu hướng được lồng sâu vào nhau.

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

    Bad: "Vì vậy, chúng tôi bắt đầu với các số từ 1 đến 10. Sau đó, chúng tôi đang đảo ngược thứ tự ánh xạ của việc lọc phần bổ sung của sự chờ đợi. Tôi quên mất tôi đang nói về điều gì."

    Good: "OK, vì vậy chúng tôi đang lấy một số xs. Có (complement even?)nghĩa là ngược lại với chẵn, vì vậy" lẻ ". Vì vậy, chúng tôi đang lọc một số tập hợp để chỉ còn lại các số lẻ. Sau đó, chúng tôi chia tất cả chúng cho 17. Sau đó chúng tôi đang đảo ngược thứ tự của chúng. Và xscâu hỏi là 1 đến 10, gotcha. "

    Đôi khi nó giúp làm điều này một cách rõ ràng. Lấy các kết quả trung gian, ném chúng vào a letvà đặt tên cho chúng để bạn hiểu. REPL được tạo ra để chơi xung quanh như thế này. Thực hiện các kết quả trung gian và xem mỗi bước mang lại cho bạn những gì.

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

    Chẳng bao lâu bạn sẽ có thể làm những việc này bằng tinh thần mà không cần nỗ lực.

  4. Sử dụng tự do (doc). Không thể phóng đại sự hữu ích của việc cung cấp tài liệu ngay tại REPL. Nếu bạn sử dụng clojure.contrib.repl-utilsvà có các tệp .clj của mình trên classpath, bạn có thể thực hiện (source some-function)và xem tất cả mã nguồn của nó. Bạn có thể làm (show some-java-class)và xem mô tả về tất cả các phương pháp trong đó. Và như thế.

Có thể đọc một cái gì đó nhanh chóng chỉ đi kèm với kinh nghiệm. Lisp không khó đọc hơn bất kỳ ngôn ngữ nào khác. Điều này xảy ra là hầu hết các ngôn ngữ trông giống như C và hầu hết các lập trình viên dành phần lớn thời gian của họ để đọc nó, vì vậy có vẻ như cú pháp C dễ đọc hơn. Thực hành thực hành thực hành.


3
+1 vì đã đề cập đến chức năng 'nguồn', bit thông tin đó khó có hơn mức cần thiết. Bây giờ hãy xem qua phần còn lại của clojure.contrib.repl-utils để xem những gì tôi đã bỏ lỡ.
vedang

3
1 cho ném kết quả trung gian trong một let, thực hiện các mã nhiều hơn nữa có thể đọc được (cho một newbie Clojure)
Greg K

Ai đó thực sự nên thực hiện một số bài báo hoặc thậm chí một cuốn sách về cách học LISP như một lập trình viên không phải LISP (ví dụ: đến từ Python). Có vẻ như nó quá khác biệt nên chúng tôi không phải là LISPers cần phải học cách học LISP. Sau lời giải thích này, có vẻ như không thể xuyên thủng được. Trước đây, tôi đã xem qua mã LISP và nghĩ, "ích lợi của việc thử là gì?".
bob

Có ai biết về bất kỳ cuốn sách nào như vậy mà họ có thể gợi ý không?
bob

7

Đầu tiên hãy nhớ rằng chương trình chức năng bao gồm các biểu thức, không phải các câu lệnh. Ví dụ: biểu mẫu (if condition expr1 expr2) lấy đối số thứ nhất của nó làm điều kiện để kiểm tra falue boolean, đánh giá nó và nếu nó được đánh giá là true thì nó sẽ đánh giá và trả về expr1, ngược lại thì đánh giá và trả về expr2. Khi mọi biểu mẫu trả về một biểu thức, một số cấu trúc cú pháp thông thường như từ khóa THEN hoặc ELSE có thể biến mất. Lưu ý rằng ở đây ifbản thân nó cũng đánh giá một biểu thức.

Bây giờ về đánh giá: Trong Clojure (và các Lisps khác), hầu hết các biểu mẫu bạn gặp là các lệnh gọi hàm của biểu mẫu (f a1 a2 ...), nơi tất cả các đối số fđược đánh giá trước khi gọi hàm thực sự; nhưng các biểu mẫu cũng có thể là macro hoặc các biểu mẫu đặc biệt không đánh giá một số (hoặc tất cả) các đối số của nó. Nếu nghi ngờ, hãy tham khảo tài liệu (doc f)hoặc chỉ cần kiểm tra REPL:

user=> apply
#<core$apply__3243 clojure.core$apply__3243@19bb5c09>
một chức năng một macro.
user=> doseq
java.lang.Exception: Can't take value of a macro: #'clojure.core/doseq

Hai quy tắc này:

  • chúng ta có các biểu thức, không phải các câu lệnh
  • việc đánh giá một biểu mẫu con có thể xảy ra hoặc không, tùy thuộc vào cách thức hoạt động của biểu mẫu bên ngoài

sẽ giúp bạn dễ dàng dò tìm các chương trình Lisp, đặc biệt. nếu chúng có thụt lề đẹp như ví dụ bạn đưa ra.

Hi vọng điêu nay co ich.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.