Tại sao cộng đồng Lisp thích tích lũy tất cả các dấu ngoặc đơn ở cuối hàm?


26

Tại sao cộng đồng Lisp thích tích lũy tất cả các dấu ngoặc đơn ở cuối hàm:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)))

Tại sao không sử dụng một quy ước như C hoặc Java?
Chà, Lisp già hơn nhiều so với những ngôn ngữ đó, nhưng tôi đang nói về Lispers đương đại.

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

Lưu ý: Đoạn mã được lấy từ cuốn sách "Niềm vui của Clojure".


13
Tôi đã nói rằng vào thời của thẻ đục lỗ, thông thường kết thúc chương trình Lisp với một thẻ phụ có 80 dấu ngoặc đơn bên phải, chỉ để đảm bảo có đủ để khớp với tất cả các dấu ngoặc đơn mở trong chương trình .
Alger

2
Ồ, tôi thích nó. Tôi luôn ghét dấu ngoặc đơn, nhưng ví dụ thứ hai của bạn có vẻ dễ chịu với tôi. Không đẹp như Python, nhưng cải tiến.
Eric Wilson

5
bởi vì họ có thể?
Muad'Dib

Sẽ thật tuyệt nếu một phương ngữ của Clojure thúc đẩy thụt vào thành cấu trúc mã. Điều đó sẽ loại bỏ hoàn toàn dấu ngoặc đơn (như F #, v.v.). Tất nhiên, dấu ngoặc đơn vẫn sẽ là hợp pháp, nếu muốn.
intrepidis

Câu trả lời:


28

Một lý do khiến các ngôn ngữ dựa trên Algol khuyến khích niềng răng trên dòng riêng của họ là để khuyến khích thêm nhiều dòng ở giữa các dấu ngoặc phân định mà không phải di chuyển niềng răng. Đó là, nếu một người bắt đầu với

if (pred)
{
  printf("yes");
}

thật dễ dàng để đi cùng và thêm một tuyên bố khác trong niềng răng:

if (pred)
{
  printf("yes");
  ++yes_votes;
}

Có dạng ban đầu là

if (pred)
{ printf("yes"); }

sau đó chúng ta phải "di chuyển" hai niềng răng, nhưng ví dụ của tôi quan tâm nhiều hơn đến cái niềng răng sau. Ở đây, các niềng răng đang phân định những gì dự định là một chuỗi các tuyên bố , chủ yếu được viện dẫn cho tác dụng phụ.

Ngược lại, Lisp thiếu các tuyên bố; mọi hình thức đều là biểu thức , mang lại một số giá trị, ngay cả trong một số trường hợp hiếm hoi (nghĩ về Common Lisp), giá trị đó được cố tình chọn là "không có giá trị" thông qua một (values)hình thức trống . Nó ít phổ biến hơn để tìm các chuỗi biểu thức , trái ngược với các biểu thức lồng nhau . Mong muốn "mở một chuỗi các bước cho đến khi dấu phân cách đóng" không xuất hiện thường xuyên, bởi vì các câu lệnh biến mất và trả về các giá trị trở thành tiền tệ phổ biến hơn, hiếm khi bỏ qua giá trị trả về của biểu thức và do đó nhiều hơn hiếm khi đánh giá một chuỗi các biểu thức cho tác dụng phụ một mình.

Trong Common Lisp, prognbiểu mẫu là một ngoại lệ (như anh chị em của nó):

(progn
  (exp-ignored-return-1)
  (exp-ignored-return-2)
  (exp-taken-return))

Ở đây, prognđánh giá ba biểu thức theo thứ tự, nhưng loại bỏ các giá trị trả về của hai biểu thức đầu tiên. Bạn có thể tưởng tượng việc viết dấu ngoặc đơn cuối cùng trên dòng riêng của mình, nhưng lưu ý một lần nữa rằng vì hình thức cuối cùng là đặc biệt ở đây (không phải theo nghĩa chung Lisp là đặc biệt ), với cách xử lý riêng biệt, nhiều khả năng người ta sẽ thêm mới các biểu thức ở giữa chuỗi, thay vì chỉ "thêm một cái khác vào cuối", vì người gọi sau đó sẽ bị ảnh hưởng không chỉ bởi bất kỳ tác dụng phụ mới nào mà thay vào đó có thể là thay đổi giá trị trả về.

Làm cho đơn giản hóa tổng thể, các dấu ngoặc đơn trong hầu hết các phần của chương trình Lisp đang phân định các đối số được truyền cho các hàm giống như trong các ngôn ngữ giống như C, và không phân định các khối lệnh. Vì những lý do tương tự, chúng ta có xu hướng giữ các dấu ngoặc đơn ràng buộc một lệnh gọi hàm trong C gần với các đối số, do đó, chúng ta cũng làm như vậy trong Lisp, với ít động lực hơn để đi chệch khỏi nhóm gần đó.

Việc đóng dấu ngoặc đơn là nhập khẩu ít hơn nhiều so với việc thụt lề của biểu mẫu nơi chúng mở. Theo thời gian, một người học cách bỏ qua các dấu ngoặc đơn và viết và đọc theo hình dạng giống như các lập trình viên Python làm. Tuy nhiên, đừng để sự tương tự đó khiến bạn nghĩ rằng việc loại bỏ hoàn toàn dấu ngoặc đơn sẽ có giá trị. Không, đó là một cuộc tranh luận tốt nhất được lưu cho comp.lang.lisp.


2
tôi nghĩ rằng việc thêm vào cuối không phải là hiếm, ví dụ (let ((var1 expr1) more-bindings-to-be-added) ...), hoặc(list element1 more-elements-to-be-added)
Alexey

14

Bởi vì nó không giúp được gì. Chúng tôi sử dụng thụt lề để hiển thị cấu trúc mã. Nếu chúng ta muốn tách các khối mã, chúng ta sử dụng các dòng thực sự trống.

Vì cú pháp Lisp rất phù hợp, các dấu ngoặc đơn là hướng dẫn chính xác để thụt lề cho cả lập trình viên và biên tập viên.

(Đối với tôi, câu hỏi đặt ra là tại sao các lập trình viên C và Java thích quăng quăng niềng răng của họ.)

Chỉ để chứng minh, giả sử rằng các toán tử này có sẵn bằng ngôn ngữ giống như C:

Foo defer_expensive (Thunk cheap, Thunk expensive) {
    if (Foo good_enough = force (cheap)) {
        return good_enough; }
    else {
        return force (expensive); }}

Hai niềng răng đóng hai cấp độ lồng. Cú pháp rõ ràng là chính xác. Tương tự cú pháp Python, các dấu ngoặc chỉ là các mã thông báo INDENT và DEDENT rõ ràng.

Tất nhiên, sức mạnh này không phải là "một phong cách cú đúp true" TM , nhưng tôi tin rằng đó chỉ là tai nạn lịch sử và thói quen.


2
Ngoài ra, hầu hết các biên tập viên lisp đánh dấu ngoặc đơn phù hợp khi đóng cửa (một số cũng đúng )đến ]hoặc ngược lại), để bạn biết bạn đóng đúng số lượng thậm chí nếu bạn không kiểm tra mức độ thụt đầu dòng.
cấu hình

6
WRT "câu hỏi", bởi vì "ném xung quanh" các mã thông báo đóng theo kiểu của ví dụ thứ hai cho phép bạn dễ dàng sắp xếp chúng bằng mắt và xem cái gì đóng lại, ngay cả khi bạn chỉ trong trình soạn thảo văn bản không có kết hợp tự động / tính năng nổi bật.
Mason Wheeler

1
@Mason Wheeler Vâng, Chính xác.
Chiron

3
TBH, điều này nói với tôi là các parens trong LISP hầu hết đều dư thừa, với khả năng (như với C ++) rằng vết lõm có thể đánh lừa - nếu thụt lề cho bạn biết những gì bạn cần biết thì cũng nên nói với trình biên dịch điều tương tự, như trong Haskell và Python. BTW - có niềng răng ở cùng mức thụt lề như if / switch / while / anything trong C ++ giúp ngăn ngừa các trường hợp vết lõm bị sai lệch - quét hình ảnh xuống LHS cho bạn biết rằng mọi niềng răng mở đều khớp với niềng răng sát và vết lõm đó phù hợp với những dấu ngoặc nhọn đó.
Steve314

1
@ Steve314: Không, vết lõm là dư thừa. Việc thụt lề cũng có thể đánh lừa trong Python và Haskell (hãy nghĩ về việc thụt lề một lần hoặc các bảng thông báo), chỉ có điều là trình phân tích cú pháp cũng bị đánh lừa. Tôi nghĩ rằng dấu ngoặc đơn là một công cụ cho người viết và trình phân tích cú pháp, trong khi thụt lề là một công cụ cho người viết và người đọc (con người). Nói cách khác, thụt lề là một nhận xét, dấu ngoặc đơn là cú pháp.
Svante

11

Mã nhỏ gọn hơn nhiều rồi. Chuyển động trong trình chỉnh sửa là bằng biểu thức s, vì vậy bạn không cần khoảng trống đó để chỉnh sửa. Mã được đọc chủ yếu theo cấu trúc và câu - không phải bằng cách phân cách.


Thật vinh dự khi nhận được câu trả lời từ bạn :) Cảm ơn.
Chiron

Trình soạn thảo nào di chuyển bằng biểu thức s? Cụ thể hơn, làm thế nào để tôi vimlàm điều đó?
hasen

2
@HasenJ Bạn có thể cần vimacs.vim plugin . : P
Đánh dấu C

5

Lispers, bạn biết đấy, hatefully bletcherous vì nó có thể được, có một số je ne sais Quới để ví dụ thứ hai:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

Lúc đầu, tôi không thể đặt một ngón tay vào nguồn của sự quyến rũ, có thể nói, và sau đó tôi nhận ra rằng nó chỉ thiếu một kích hoạt:

(defn defer-expensive [cheap expensive]      
  (if-let [good-enough (force cheap)]
    good-enough   ;   )
    (force expensive) 
  )
)

Võngà!

Bây giờ tôi sẽ luyện tập tay cầm gangster Lisp của mình!


2
Đây là một trong những câu trả lời hài hước và bị đánh giá thấp nhất tôi từng thấy trên trang web này. Tôi đội mũ của tôi.
byxor

0

Trong trường hợp của tôi, tôi thấy các dòng dành riêng cho các dấu phân cách lãng phí không gian màn hình và khi bạn viết mã bằng C cũng có kiểu của

if (pred) {
   printf("yes");
   ++yes_votes;
}

Tại sao mọi người lại đặt dấu ngoặc mở trong cùng một dòng "nếu" để tiết kiệm không gian và bởi vì nó có vẻ dư thừa để có dòng riêng khi "nếu" đã có dòng riêng.

Bạn đạt được kết quả tương tự bằng cách đặt dấu ngoặc đơn ở cuối. Trong C sẽ trông lạ vì các câu lệnh kết thúc bằng dấu chấm phẩy và cặp dấu ngoặc không mở như thế này

{if (pred)
    printf("yes");
}

Nó giống như là cú đúp kết thúc ở cuối, trông không đúng chỗ. nó mở ra như thế này

if (pred) {
    printf("yes");
}

cung cấp cho bạn cái nhìn rõ ràng về khối và giới hạn của khối bằng '{' & '}'

Và với sự trợ giúp của trình soạn thảo khớp với dấu ngoặc đơn bằng cách làm nổi bật chúng như vim, bạn có thể đi đến cuối nơi tất cả các dấu ngoặc đơn được đóng gói và di chuyển qua chúng một cách dễ dàng và khớp với tất cả các dấu mở đầu và xem biểu mẫu lisp lồng nhau

(defn defer-expensive [cheap expensive]
    _(if-let [good-enough (force cheap)]
    good-enough
    (force expensive)_))

bạn có thể đặt con trỏ của mình vào paren đóng ở giữa và có paren mở của if-let được tô sáng.

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.