2013-03-22 13 views
9

Trong Haskell, nếu tôi có một lambda trông giống như sauphần Haskell kiểu trong Common Lisp

(\x -> doStuff x y) 

nơi y là từ phạm vi xung quanh, tôi có thể phần nó và biến nó thành

(`doStuff` y) 

ngắn hơn và ngắn gọn hơn (và một trong những điều tôi yêu thích nhất về Haskell).

Bây giờ, trong Common Lisp tôi sẽ viết mã tương đương như

(lambda (x) (do-stuff x y)) 

Và đây thực sự là một điều rất phổ biến đối với tôi để được viết, nhưng tôi cảm thấy thậm chí đôi chút soạn sẵn làm tôi bực mình đôi chút, vì vậy tôi tự hỏi nếu có một cách để có được một cái gì đó giống như các phần theo phong cách Haskell trong Common Lisp?

+0

Vâng, tôi có nghĩa là ngắn gọn ... Tôi vẫn còn khá mới với CL mặc dù và sẽ không có ý tưởng bắt đầu từ đâu, tôi chỉ chạm vào những điều cơ bản về macro. @downvoter - quan tâm để nhận xét? – jaymmer

+0

@wvxvw Tôi không hiểu điểm đầu tiên của nhận xét của bạn. Liệu nó có thể không được giả định rằng nếu OP sử dụng một bộ mô tả chủ quan như 'súc tích' rằng người đó đang nói một ý kiến? –

+2

Về những thứ mà "... có thể được hiển thị thường xuyên ...", tôi sẽ quan tâm đến một trích dẫn nếu bạn có. Hơn nữa nó xảy ra với tôi mà một trong những ví dụ của bạn là ngắn gọn hơn khả năng phụ thuộc vào nền tảng của khán giả. Nếu một người không có nền tảng toán học nào, tôi nghi ngờ ví dụ đầu tiên được hiểu nhanh hơn và dễ dàng hơn. Ngoài tất cả những điều đó, _it là ý kiến ​​của tôi, rằng mức độ này là sự cân bằng, gây hại đến chức năng và mục đích của diễn đàn này. –

Trả lời

11

Trừ khi bạn có nhiều kinh nghiệm hơn, tôi đề nghị bạn nên viết Lisp trong Lisp, chứ không phải cách viết Haskell trong Lisp. Sau này không phải là một ý tưởng tốt. Haskell hoạt động rất khác nhau.

Lisp không thực hiện bất kỳ 'currying' (hoặc schönfinkeling ;-)) nào.

Bạn có thể viết nó như:

CL-USER 5 > (defun curry (fn arg) (lambda (&rest args) (apply fn arg args))) 
CURRY 

CL-USER 6 > (mapcar (curry #'expt 2) '(2 3 4 5 6)) 
(4 8 16 32 64) 

Nó chi phí một hiệu quả chút như vậy, mặc dù.

CL-USER 7 > (mapcar (lambda (base) (expt base 2)) '(2 3 4 5 6)) 
(4 8 16 32 64) 

Cá nhân tôi thích cái sau hơn, bởi vì tôi có tên thật có thể đọc được cho biến. Điều này giúp trong một trình gỡ lỗi, nơi tôi nhìn thấy sau đó một backtrace. Các công cụ như thế này có lẽ quan trọng hơn trong Lisp, hơn là trong Haskell.

CL-USER 12 > (mapcar (lambda (base) (expt base 2)) '(2 3 "four" 5 6)) 

lỗi. Hãy nhìn vào backtrace:

CL-USER 12 : 1 > :bb 
... 

Condition: In EXPT of ("four" 2) arguments should be of type NUMBER. 

Call to SYSTEM::ARGS-TO-BINARY-ARITHMETIC-FN-NOT-OF-TYPE {offset 189} 
    SYSTEM::FN-NAME : EXPT 
    SYSTEM::ARG1 : "four" 
    SYSTEM::ARG2 : 2 
    TYPE {Closing} : NUMBER 

Interpreted call to (SUBFUNCTION :ANONYMOUS SYSTEM::ANONYMOUS-LAMBDA): 
    BASE : "four" 

Bây giờ tôi có thể thấy rằng điều đó có tên. Tôi đã chuyển chuỗi "four" đến hàm có biến có tên là base.

Phát triển tương tác với REPL và công cụ gỡ lỗi là phổ biến. Tốt nhất chuẩn bị mã để có ích cho phong cách phát triển này. Lisp thường không được tối ưu hóa để cung cấp các trình biên dịch chương trình đầy đủ với việc kiểm tra kiểu mở rộng - như trong Haskell.

Một trong những vấn đề chính của Lisp là rất khó để tìm hiểu xem một đoạn mã thực sự là gì. Mặc định (các chương trình chức năng nghiêm ngặt với cú pháp tiền tố) tương đối dễ hiểu. Nhưng có nhiều khả năng để thay đổi ý nghĩa của mã trong Lisp (macro, đọc macro, macro biểu tượng, giao thức Meta Object, tư vấn, ...).

Quy tắc đầu tiên: nếu bạn đang viết mã Lisp cơ bản, hãy tuân thủ các khả năng cú pháp và ngữ nghĩa cơ bản. Viết phòng thủ. Mong rằng ai đó cần hiểu mã. Cho rằng mã nên dễ đọc, dễ hiểu, sử dụng các thành ngữ phổ biến và nó nên được debuggable.

Trong Haskell nhiều người có nền toán học muốn viết mã theo một cách rất nhỏ gọn với mức trừu tượng cao. Bạn cũng có thể làm điều đó trong Lisp. Nhưng đối với mã thông thường tôi sẽ không đi con đường đó và cho các đoạn mã lớn hơn, Lisp thường sử dụng các cơ chế khác (chuyển đổi mã thông qua các macro, ...).

+0

Cảm ơn - mặc dù tôi chắc chắn không cố gắng học cách viết Haskell trong Lisp, tôi chỉ không muốn là một trong những người từ bỏ ngôn ngữ hoặc kết thúc viết mã xấu/quy ước bất chấp suy nghĩ rằng " nó thậm chí không làm x "trong khi nó thực sự không làm x và tôi chỉ không nhận thức được nó. – jaymmer

+3

Ba thứ (hai trong số đó là nitpicks). Đầu tiên, 'curry' của bạn không giải quyết được vấn đề; những gì được mô tả không chỉ là currying, mà còn là một tính năng cú pháp (áp dụng một hàm cho đối số thứ hai). Thứ hai, hầu hết sự phát triển Haskell của tôi diễn ra tại một REPL, và tôi nghĩ đó là điển hình; mặt khác, trong khi GHCi (REPL của trình biên dịch chuẩn) có trình gỡ lỗi tích hợp, tôi hầu như không sử dụng nó, và tôi nghĩ nó cũng điển hình (nhưng ít hơn). Thứ ba, tôi không nghĩ rằng "nền toán học" có liên quan đến việc bạn tìm thấy '\ x -> doStuff x y',' flip doStuff y' hay '(\' doStuff \ 'y)' có thể đọc được hay không. –

5

Tôi không nghĩ rằng bạn có thể làm điều đó trực tiếp, nhưng ...

Nếu bạn biết rằng bạn luôn muốn làm một cái gì đó tương đương với (lambda (x) (fun x lexical)) và chỉ đơn giản là muốn có một cách ngắn thể hiện điều đó, bạn có thể theo lý thuyết, sử dụng macro.

Cá nhân tôi, khuyên bạn không nên làm như vậy, (lambda (x) (fun x lex)) không cần nhập nhiều và xóa một lớp tối tăm khỏi mã của bạn. Nhưng nếu nó là một mô hình đó là đủ phổ biến mà nó bảo đảm xử lý đặc biệt, giống như sau có thể làm:

(defmacro section (function lexical) 
    (let ((sym (gensym)) 
    `(lambda (,sym) (,function ,sym ,lexical)))) 

Điều đó làm cho phần Haskell:

(`doStuff` y) 

trở thành phần Common Lisp:

(section dostuff y) 

Tôi không làm như vậy, dễ đọc hơn, ít nhất là trong ngắn hạn, nhưng nếu đó là điều tôi đã xem lại, tôi thực sự xem xét (và đã làm, nhiều hơn cho mục đích thử nghiệm hơn bất kỳ thứ gì khác) một macro để làm cho nó nhanh hơn (tôi có macro nửa nướng, ở đâu đó, cho phép bạn làm những việc như (_ func _2 lexical _1) ->*(lambda (a b) (func b lexical a)) và đôi khi tiện dụng, nhưng không thực sự cải thiện khả năng đọc).

9

Bạn có thể phát triển cú pháp đặc biệt tùy ý cho các biểu mẫu như vậy. Có nhiều biến thể. Tôi, ví dụ, sử dụng một cảm hứng Clojure sharp-backquote syntax. Sử dụng nó, biểu mẫu của bạn sẽ trông giống như sau:

#`(do-stuff % y) 
3

Có một backquote sắc nét đọc vĩ mô trong Let Over Lambda mà có thể làm việc cho trường hợp này:

CL-USER> 
(print 
    '#`,(+ a1 y)) 

(LAMBDA (A1) (+ A1 Y)) 
(LAMBDA (A1) (+ A1 Y)) 

CL-USER> 
(let ((y 2)) 
    (mapcar #`,(+ a1 y) 
      (list 1 2 3 4))) 
(3 4 5 6) 
CL-USER> 

Cách tiếp cận này rất giống với kỹ thuật được đề cập bởi @Vsevolod Dyomkin. Phiên bản Hoyte có một vài tính năng bổ sung, như xây dựng một lambda với bất kỳ số lượng các đối số. Mặt khác, khó phân tích cú pháp hơn, bởi vì nó được biểu diễn ở một mức ký hiệu cao hơn, ở đó để đánh giá một biểu mẫu, bạn phải bỏ dấu backquote (sử dụng ',' trong ví dụ này).

3

Sơ đồ có macro cut (trong SRFI-26), cho phép bạn chỉ định lỗ trong cuộc gọi thủ tục với <>. Ví dụ:

(cut doStuff <> y) ;; same as (lambda (x) (doStuff x y)) 
(cut - 5 <> 6 <> 7) ;; same as (lambda (x y) (- 5 x 6 y 7)) 

Có thể bạn có thể xác định điều gì đó tương tự trong CL.

1

Gói alexandria xuất các ký hiệu curryrcurry. Vì vậy, trong trường hợp của bạn, bạn chỉ cần thực hiện (alexandria:rcurry function arg) ví dụ: (rcurry #'do-staff y).

Rcurry và hàm trả về cà ri để bạn cần phải funcall kết quả như bình thường.

2

Tôi cũng bỏ lỡ sự dễ dàng của chức năng currying và thành phần theo phong cách Haskell khi ở dạng lisp chung.Kết quả là tôi đã viết các gói sau đây định nghĩa các macro đọc cho cà ri ngắn gọn và thành phần trong lisp (nó sử dụng các hàm alexandria).

http://eschulte.github.io/curry-compose-reader-macros/

Với gói này (mapcar (compose (curry #'* 2) (curry #'+ 1)) (list 1 2 3 4)) trở thành (mapcar [{* 2} {+ 1}] (list 1 2 3 4)). Tôi bây giờ sử dụng điều này trong gần như tất cả các dự án CL của tôi và thấy nó làm giảm đáng kể kích thước mã và tăng khả năng đọc.