2012-06-27 40 views
15

Tức là, khi bạn gọi hàm có> 1 arity chỉ với một đối số, nó sẽ thay vì hiển thị lỗi, quấy rối đối số đó và trả về hàm kết quả có giảm số nguyên. Điều này có thể thực hiện bằng cách sử dụng các macro của Lisp không?Có thể thực hiện tự động currying cho các ngôn ngữ gia đình Lisp?

+0

Bạn sẽ phải triển khai một ngôn ngữ được gõ trên đầu trang của Lisp (rất dễ dàng với các macro), nhưng sẽ không dễ dàng kết hợp ngôn ngữ này với phần còn lại của Lisp. –

Trả lời

9

Trong Scheme nó có thể để cà ri có một chức năng sử dụng các thủ tục curry:

(define (add x y) 
    (+ x y)) 

(add 1 2)   ; non-curried procedure call 
(curry add)   ; curried procedure, expects two arguments 
((curry add) 1)  ; curried procedure, expects one argument 
(((curry add) 1) 2) ; curried procedure call 

Từ vợt của documentation:

[ri] trả về một thủ tục mà là một phiên bản cà ri của proc. Khi thủ tục kết quả được áp dụng lần đầu tiên, trừ khi nó được cho số lượng đối số tối đa mà nó có thể chấp nhận, kết quả là một thủ tục để chấp nhận các đối số bổ sung.

Bạn có thể dễ dàng thực hiện một macro tự động sử dụng curry khi xác định các thủ tục mới, một cái gì đó như thế này:

(define-syntax define-curried 
    (syntax-rules() 
     ((_ (f . a) body ...) 
     (define f (curry (lambda a (begin body ...))))))) 

Bây giờ định nghĩa sau đây của add sẽ được cà ri:

(define-curried (add a b) 
    (+ a b)) 

add 
> #<procedure:curried> 

(add 1) 
> #<procedure:curried> 

((add 1) 2) 
> 3 

(add 1 2) 
> 3 
+2

Câu trả lời tuyệt vời tổng thể nhưng bạn đã cung cấp đoạn mã nhỏ được chứng minh là rất hữu ích vì vậy tôi không thể chấp nhận câu trả lời khác nhưng điều này. – MaiaVictor

1

Lisp đã có chức năng Currying:

* (defun adder (n) 
    (lambda (x) (+ x n))) 
ADDER 

http://cl-cookbook.sourceforge.net/functions.html

Dưới đây là những gì tôi đã đọc về macro Lisp: https://web.archive.org/web/20060109115926/http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html

Có thể thực hiện điều này trong Lisp tinh khiết. Nó có thể thực hiện nó bằng cách sử dụng macro là tốt, tuy nhiên nó có vẻ như là mặc dù macro sẽ làm cho nó khó hiểu hơn cho những thứ rất cơ bản.

+1

"arity" là một thuật ngữ phổ biến trong Prolog. Có các hàm (* predicates *) có cùng tên và các vị trí khác nhau được xem là khác nhau. –

2

Câu trả lời ngắn gọn là có, mặc dù không dễ dàng.

bạn có thể ngụ ý điều này dưới dạng macro bao quanh mọi cuộc gọi trong partial, mặc dù chỉ trong ngữ cảnh hạn chế. Clojure có một số tính năng mà có thể làm cho điều này khá khó khăn như các chức năng biến đổi và các cuộc gọi. Clojure thiếu một hệ thống kiểu chính thức để cụ thể quyết định khi nào cuộc gọi có thể không còn đối số và thực sự nên được gọi.

13

Có thể, nhưng không dễ dàng nếu bạn muốn có kết quả hữu ích.

  • Nếu bạn muốn một ngôn ngữ luôn làm việc đơn giản thì việc triển khai thật dễ dàng. Bạn chỉ cần chuyển đổi mọi ứng dụng của nhiều hơn một đầu vào thành một ứng dụng lồng nhau, và tương tự cho các hàm của nhiều đối số. Với Racket's cơ sở ngôn ngữ, đây là một bài tập rất đơn giản. (Trong các lisps khác, bạn có thể nhận được hiệu ứng tương tự bằng một số macro xung quanh mã mà bạn muốn sử dụng nó.)

    (Ngẫu nhiên, tôi có một ngôn ngữ trên đầu vợt chỉ thực hiện điều này. ngôn ngữ tự động, nhưng nó không có ý định thực tế.)

    Tuy nhiên, nó không quá hữu ích vì nó chỉ hoạt động cho các chức năng của một đối số.Bạn có thể làm cho nó hữu ích với một số hack, ví dụ, điều trị phần còn lại của hệ thống lisp xung quanh ngôn ngữ của bạn như là một ngôn ngữ nước ngoài và cung cấp các hình thức để sử dụng nó. Một lựa chọn khác là cung cấp ngôn ngữ của bạn với thông tin tinh thần về các chức năng của lisp xung quanh. Một trong số này đòi hỏi nhiều công việc hơn.

  • Một tùy chọn khác là chỉ cần kiểm tra mọi ứng dụng. Nói cách khác, bạn bật mỗi

    (f x y z) 
    

    vào mã để kiểm tra các arity của f và sẽ tạo ra một đóng cửa nếu không có đủ lý lẽ. Đây không phải là quá khó khăn trong chính nó, nhưng nó sẽ dẫn đến một mức giá đáng kể trên không. Bạn có thể cố gắng sử dụng một mẹo tương tự về một số thông tin về các chức năng mà bạn muốn sử dụng ở cấp độ vĩ mô để biết các bao đóng như thế sẽ được tạo ra ở đâu - nhưng điều đó cũng khó về cơ bản theo cùng một cách.

Nhưng có vấn đề nghiêm trọng hơn nhiều, ở mức cao của những gì bạn muốn làm. Vấn đề là các hàm biến thiên không chỉ hoạt động tốt với việc tự động làm curry. Ví dụ, có một biểu hiện như:

(+ 1 2 3)

Làm thế nào bạn sẽ quyết định nếu điều này nên được gọi như là, hoặc cho dù đó nên được dịch sang ((+ 1 2) 3)? Nó có vẻ như có một câu trả lời dễ dàng ở đây, nhưng những gì về điều này? (dịch sang phương ngữ lisp yêu thích của bạn)

(define foo (lambda xs (lambda ys (list xs ys)))) 

Trong trường hợp này bạn có thể chia (foo 1 2 3) bằng nhiều cách. Tuy nhiên, một vấn đề khác là bạn sẽ làm gì với cái gì đó như:

(list +) 

Ở đây bạn có + như một biểu hiện, nhưng bạn có thể quyết định rằng đây là giống như áp dụng nó trên zero đầu vào mà phù hợp + s arity, nhưng sau đó làm thế nào để bạn viết một biểu thức đánh giá chức năng bổ sung? (Sidenote: ML và Haskell "giải quyết" điều này bằng cách không có chức năng null ...)

Một số vấn đề này có thể được giải quyết bằng cách quyết định rằng mỗi ứng dụng "thực" phải có mệnh lệnh cho nó, do đó, + không bao giờ được áp dụng. Nhưng điều đó làm mất đi sự dễ thương khi có ngôn ngữ tự động, và bạn vẫn gặp khó khăn để giải quyết ...

1

Như đã lưu ý của Alex W, Sổ tay Lisp chung does give an example của hàm "curry" cho Common Lisp. Ví dụ cụ thể nằm sâu hơn trên trang đó:

(declaim (ftype (function (function &rest t) function) curry) 
     (inline curry)) ;; optional 
(defun curry (function &rest args) 
    (lambda (&rest more-args) 
    (apply function (append args more-args)))) 

Không cần thực hiện quá trình tự động làm khó, vì vậy tôi đã giải quyết vấn đề này.Lưu ý rằng đây là không thử nghiệm rộng rãi, và không kiểm tra mà không có quá nhiều args (chức năng chỉ hoàn thành khi có con số trở lên):

(defun auto-curry (function num-args) 
    (lambda (&rest args) 
    (if (>= (length args) num-args) 
     (apply function args) 
     (auto-curry (apply (curry #'curry function) args) 
        (- num-args (length args)))))) 

Dường như làm việc, mặc dù:

* (auto-curry #'+ 3) 
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F78EB9}> 

* (funcall (auto-curry #'+ 3) 1) 
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F7A689}> 

* (funcall (funcall (funcall (auto-curry #'+ 3) 1) 2) 5) 
8 

* (funcall (funcall (auto-curry #'+ 3) 3 4) 7) 
14 

Một nguyên thủy (không xử lý danh sách lambda đầy đủ đúng cách, chỉ cần danh sách tham số đơn giản) phiên bản của một số đường cú pháp vĩ mô đối với phần trên:

(defmacro defun-auto-curry (fn-name (&rest args) &body body) 
    (let ((currying-args (gensym))) 
    `(defun ,fn-name (&rest ,currying-args) 
     (apply (auto-curry (lambda (,@args) ,@body) 
          ,(length args)) 
       ,currying-args)))) 

Dường như làm việc, mặc dù thứ e cần funcall vẫn còn gây phiền nhiễu:

* (defun-auto-curry auto-curry-+ (x y z) 
    (+ x y z)) 
AUTO-CURRY-+ 

* (funcall (auto-curry-+ 1) 2 3) 
6 

* (auto-curry-+ 1) 
#<CLOSURE (LAMBDA (&REST ARGS)) {1002B0DE29}> 
1

Chắc chắn, bạn chỉ cần phải quyết định ngữ nghĩa chính xác cho ngôn ngữ của bạn, và sau đó thực hiện của riêng bạn nạp mà sẽ dịch file nguồn của bạn sang ngôn ngữ thực hiện.

Bạn có thể, ví dụ: dịch từng hàm người dùng gọi (f a b c ... z) thành (...(((f a) b) c)... z) và mỗi (define (f a b c ... z) ...) thành (define f (lambda(a) (lambda(b) (lambda(c) (... (lambda(z) ...) ...))))) ở đầu Đề án, để có Đề án tự động làm curry (tất nhiên sẽ cấm các hàm varargs).

Bạn cũng sẽ cần xác định nguyên thủy của riêng mình, chuyển các hàm varargs như ví dụ: (+) thành nhị phân và chuyển ứng dụng sang sử dụng fold ví dụ: (+ 1 2 3 4) ==>(fold (+) (list 1 2 3 4) 0) hoặc gì đó - hoặc có thể chỉ thực hiện các cuộc gọi như (+ 1 2 3 4) bất hợp pháp trong ngôn ngữ mới của bạn, với mong muốn người dùng tự viết các biểu mẫu fold.

Đó là ý của tôi bằng cách "quyết định ... ngữ nghĩa cho ngôn ngữ của bạn".

Trình tải có thể đơn giản như việc gói nội dung tệp vào cuộc gọi đến một macro - mà sau đó bạn sẽ phải triển khai, theo câu hỏi của bạn.

Các vấn đề liên quan