2011-02-03 39 views
28

Có rất nhiều hướng dẫn về cách thực hiện các hàm và nhiều câu hỏi ở đây tại stackoverflow. Tuy nhiên, sau khi đọc The Little Schemer, một số sách, hướng dẫn, bài đăng trên blog và chủ đề stackoverflow tôi vẫn không biết câu trả lời cho câu hỏi đơn giản: "Điểm cà ri là gì?" Tôi hiểu làm thế nào để curry một chức năng, chỉ cần không phải là "tại sao?" đằng sau nó.Sử dụng thực tế các chức năng đã kết hôn?

thể ai đó hãy giải thích cho tôi những ứng dụng thực tế của chức năng cà ri (bên ngoài của ngôn ngữ mà chỉ cho phép một đối số cho mỗi chức năng, nơi mà sự cần thiết của việc sử dụng currying là tất nhiên khá hiển nhiên.)

chỉnh sửa: Taking vào tài khoản một số ví dụ từ TLS, lợi ích của

(define (action kind) 
    (lambda (a b) 
     (kind a b))) 

là những gì như trái ngược với

(define (action kind a b) 
    (kind a b)) 

Tôi chỉ có thể xem thêm mã và không có thêm tính linh hoạt ...

+0

Sử dụng 4 thụt lề, khi định dạng mã của bạn. Xem [Trợ giúp chỉnh sửa] (http://stackoverflow.com/editing-help). –

+2

Hai định nghĩa, bạn cung cấp, không sử dụng currying, chúng ta đang nói về, đây chỉ là ví dụ về các cách khác nhau để định nghĩa một hàm. Xem liên kết đến câu hỏi khác trong nhận xét cho câu trả lời của tôi. –

+0

Thú vị, nguyên nhân 'cà ri' chính xác là cách TLS gọi nó ... Trích dẫn: "' (lambda (a) (lambda (x) (eq? X a))) '... Có phải đó là 'Curry-ing?' ... [Có.] " –

Trả lời

23

Một cách sử dụng hiệu quả các hàm được thu thập là giảm số lượng mã.

Hãy xem xét ba chức năng, hai trong số đó là gần như giống hệt nhau:

(define (add a b) 
    (action + a b)) 

(define (mul a b) 
    (action * a b)) 

(define (action kind a b) 
    (kind a b)) 

Nếu mã của bạn gọi add, nó lần lượt gọi action với loại +. Tương tự với mul.

Bạn đã xác định các chức năng như bạn sẽ làm trong nhiều ngôn ngữ phổ biến bắt buộc (một số đã bao gồm lambdas, currying và các tính năng khác thường được tìm thấy trong thế giới chức năng, vì tất cả đều cực kỳ tiện dụng).

Tất cả addsum làm, đang kết thúc cuộc gọi đến action với kind thích hợp. Bây giờ, hãy xem xét các định nghĩa đã được xử lý của các chức năng sau:

(define add-curried 
    ((curry action) +)) 

(define mul-curried 
    ((curry action) *)) 

Chúng trở nên ngắn hơn đáng kể. Chúng ta chỉ cần thực hiện hàm action bằng cách chỉ chuyển một đối số, kind và nhận hàm curried chấp nhận hai đối số còn lại.

Cách tiếp cận này cho phép bạn viết ít mã hơn, với mức độ bảo trì cao.

Chỉ cần hình dung rằng hàm action sẽ ngay lập tức được viết lại để chấp nhận thêm 3 đối số. Nếu không tách lạng bộ bạn sẽ phải viết lại triển khai lại addmul:

(define (action kind a b c d e) 
    (kind a b c d e)) 

(define (add a b c d e) 
    (action + a b c d e)) 

(define (mul a b c d e) 
    (action * a b c d e)) 

Nhưng tách lạng bộ lưu bạn từ đó công việc khó chịu và dễ bị lỗi; bạn không phải viết lại ngay cả một biểu tượng trong các hàm add-curriedmul-curried ở tất cả, bởi vì hàm gọi sẽ cung cấp số lượng đối số cần thiết được chuyển đến action.

+0

Xem xét một số ví dụ từ TLS và ví dụ của bạn ở trên, lợi ích của '(define (action type) (lambda (ab) (loại ab)))' trái với '(define (action type ab) (type) ab)) '? Tôi chỉ có thể xem thêm mã và không có tính linh hoạt bổ sung ... –

+0

@Philip: Không có lợi ích, ngoại trừ khẩu vị và số lần bấm phím :-) Xem "[Tại sao tất cả lambdas trong Little Schemer?] (Http: // stackoverflow.com/q/4777865/298282) ". –

+0

Tôi không nói về lambdas ... Nếu tôi hiểu nó đúng, chức năng đầu tiên là curried, và thứ hai là không. Một lần nữa, nếu tôi không nhầm lẫn, cả hai đều làm chính xác điều tương tự, nhưng phiên bản đã kết thúc dài hơn cả trong định nghĩa của nó và khi nó được gọi là '((action *) 5 5)' trái ngược với '(action * 5 5) ' –

0

Vì vậy, bạn không phải tăng boilerplate với một ít lambda.

0

Rất dễ dàng để tạo bao đóng. Thỉnh thoảng tôi sử dụng SRFI-26. Nó thực sự dễ thương.

4

Bạn có thể xem currying như một chuyên môn. Chọn một số giá trị mặc định và để người dùng (có thể là chính bạn) với chức năng chuyên biệt, hơn biểu cảm.

11

Chúng có thể làm cho mã dễ đọc hơn. Hãy xem xét hai đoạn mã Haskell sau đây:

lengths :: [[a]] -> [Int] 
lengths xs = map length xs 

lengths' :: [[a]] -> [Int] 
lengths' = map length 

Tại sao đặt tên cho biến bạn sẽ không sử dụng?

chức năng cà ri cũng giúp trong những tình huống như thế này:

doubleAndSum ys = map (\xs -> sum (map (*2) xs) ys 

doubleAndSum' = map (sum . map (*2)) 

Loại bỏ các biến thêm làm cho mã dễ đọc và không cần để bạn có thể tinh thần giữ rõ ràng những gì xs và những gì ys là.

HTH.

+1

được gọi là kiểu lập trình * pointfree *, chỉ có thể có được với mã được curried – comonad

+4

Ứng dụng một phần của Haskell lạnh hơn rất nhiều so với việc làm curry rõ ràng. –

+2

Lập trình theo kiểu pointfree hoặc tacit không yêu cầu chức năng được thu thập. Nó có thể được thực hiện bằng cách sử dụng combinators, ví dụ. Phong cách nói là nổi bật với ngôn ngữ lập trình J. Trong cà ri J không xảy ra theo mặc định, và dạng tacit không bao giờ phụ thuộc vào nó. – kaleidic

3

Tôi nghĩ rằng currying là một cách truyền thống để xử lý các chức năng chung n-ary với điều kiện là những cái duy nhất bạn có thể định nghĩa là đơn nhất.

Ví dụ, trong phép tính lambda (từ đó ngôn ngữ lập trình chức năng xuất phát), chỉ có trừu tượng một biến (chuyển thành hàm đơn nhất trong FPL). Về tính toán lambda, tôi nghĩ dễ dàng hơn để chứng minh mọi thứ về hình thức như vậy vì bạn không thực sự cần xử lý trường hợp hàm n-ary (vì bạn có thể biểu diễn bất kỳ hàm n-ary nào với số lượng unary thông qua currying) .

(Những người khác đã đề cập một số các ứng dụng thực tế của quyết định này vì vậy tôi sẽ dừng lại ở đây.)

+1

Trích dẫn từ câu hỏi ban đầu của tôi: '(bên ngoài các ngôn ngữ chỉ cho phép một đối số cho mỗi chức năng, trong đó sự cần thiết của việc sử dụng cà ri là điều hiển nhiên.)' :) –

0

Trong và của chính nó tách lạng bộ là đường cú pháp. Đường cú pháp là tất cả về những gì bạn muốn làm cho dễ dàng. Ví dụ C muốn đưa ra các hướng dẫn "rẻ" trong ngôn ngữ lắp ráp như tăng dần, dễ dàng và do đó chúng có cú pháp đường cho tăng dần, ký hiệu ++.

t = x + y 
x = x + 1 

được thay thế bằng t = x ++ + y

Ngôn ngữ chức năng có thể chỉ là một cách dễ dàng có những thứ như thế.

f(x,y,z) = abc 
g(r,s)(z) = f(r,s,z). 
h(r)(s)(z) = f(r,s,z) 

nhưng thay vì tất cả tự động. Và điều đó cho phép một g bị ràng buộc bởi một r0, s0 cụ thể (nghĩa là các giá trị cụ thể) được chuyển thành một hàm biến.

Lấy chức năng sắp xếp của perl lấy danh sách phụ sắp xếp trong đó phụ là hàm của hai biến đánh giá danh sách boolean và là danh sách tùy ý.

Bạn tự nhiên sẽ muốn sử dụng toán tử so sánh (< =>) trong Perl và có sortordinal = sort (< =>) nơi công trình sortordinal trên danh sách. Để làm điều này, bạn sẽ sắp xếp là một chức năng được thu thập.
Và trên thực tế, sắp xếp danh sách được xác định theo cách chính xác theo cách này trong Perl.

Tóm lại: currying là đường để làm cho lớp học đầu tiên hoạt động tự nhiên hơn.

2

Sử dụng all :: (a -> Bool) -> [a] -> Bool với vị từ được kết thúc.

all (`elem` [1,2,3]) [0,3,4,5] 

Haskell nhà khai thác có thể được ghi vào cà ri ở hai bên, vì vậy bạn có thể dễ dàng cà ri kim hoặc phía container của elem chức năng (là phần tử-of).

+0

Predicates là một trong những thứ hữu ích nhất để trải nghiệm trong kinh nghiệm của tôi . –

0

Tôi muốn thêm ví dụ vào câu trả lời @Francesco.

enter image description here

2

Chúng ta có thể không trực tiếp soạn chức năng mà phải mất nhiều tham số. Kể từ khi thành phần chức năng là một trong những khái niệm quan trọng trong lập trình chức năng. Bằng cách sử dụng kỹ thuật Currying chúng ta có thể soạn các hàm nhận nhiều tham số.

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