Groovy có gọi ứng dụng một phần là 'currying' không?


15

Groovy có một khái niệm mà nó gọi là 'currying'. Đây là một ví dụ từ wiki của họ:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

Sự hiểu biết của tôi về những gì đang diễn ra ở đây là đối số bên phải divideđang bị ràng buộc với giá trị 2. Đây có vẻ như là một hình thức của một phần của ứng dụng.

Thuật ngữ currying thường được sử dụng để có nghĩa là chuyển đổi một hàm lấy một loạt các đối số thành một hàm chỉ lấy một đối số và trả về một hàm khác. Ví dụ ở đây là loại curryhàm trong Haskell:

curry :: ((a, b) -> c) -> (a -> (b -> c))

Đối với những người chưa từng sử dụng Haskell a, bclà tất cả các thông số chung chung. currynhận một hàm có hai đối số và trả về một hàm nhận avà trả về một hàm từ bđến c. Tôi đã thêm một cặp dấu ngoặc vào loại để làm cho điều này rõ ràng hơn.

Tôi đã hiểu nhầm những gì đang diễn ra trong ví dụ hấp dẫn hay chỉ đơn thuần là ứng dụng sai một phần? Hoặc thực tế nó làm cả hai: nghĩa là chuyển đổi dividethành hàm bị cong và sau đó áp dụng một phần 2cho hàm mới này.


1
đối với những người không nói haskell msmvps.com/bloss/jon_skeet/archive/2012/01/30/ mẹo
jk.

Câu trả lời:


14

Groovy thực hiện currykhông thực sự cà ri tại bất kỳ điểm nào, ngay cả đằng sau hậu trường. Nó về cơ bản là giống hệt với ứng dụng một phần.

Các curry, rcurryncurryphương pháp trả về một CurriedClosuređối tượng chứa các đối số ràng buộc. Nó cũng có một phương thức getUncurriedArguments(được đặt tên sai là các hàm cà ri của bạn chứ không phải các đối số) trả về thành phần của các đối số được truyền cho nó với các đối số bị ràng buộc.

Khi đóng cửa được gọi, nó cuối cùng gọi các invokeMethodphương phápMetaClassImpl , mà rõ ràng kiểm tra xem nếu đối tượng gọi là một thể hiện của CurriedClosure. Nếu vậy, nó sử dụng đã nói ở trên getUncurriedArgumentsđể soạn toàn bộ các đối số để áp dụng:

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

Dựa trên danh pháp khó hiểu và có phần không nhất quán ở trên, tôi nghi ngờ rằng bất cứ ai viết bài này đều có một sự hiểu biết khái niệm tốt, nhưng có lẽ hơi vội vàng và giống như nhiều người thông minh, đã kết hợp cà ri với ứng dụng một phần. Điều này có thể hiểu được (xem câu trả lời của Paul King), nếu không may; sẽ rất khó để sửa lỗi này mà không phá vỡ tính tương thích ngược.

Một giải pháp mà tôi đã đề xuất là quá tải curryphương thức sao cho khi không có đối số nào được thông qua, nó sẽ thực hiện quá trình curry thực sự và không dùng phương thức đó với các đối số có lợi cho partialhàm mới . Điều này có vẻ hơi lạ , nhưng nó sẽ tối đa hóa khả năng tương thích ngược vì vì không có lý do gì để sử dụng ứng dụng một phần với các đối số bằng 0 trong khi tránh tình huống xấu hơn (IMHO) khi có một chức năng mới, được đặt tên khác để thực hiện chức năng cà ri trong khi thực sự chức năng được đặt tên currylàm một cái gì đó khác nhau và tương tự khó hiểu.

Không cần phải nói rằng kết quả của cuộc gọi currylà hoàn toàn khác với cà ri thực tế. Nếu nó thực sự làm hỏng chức năng, bạn sẽ có thể viết:

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

Càng và nó sẽ hoạt động, vì addCurriednên làm việc như thế nào { x -> { y -> x + y } }. Thay vào đó, nó ném một ngoại lệ thời gian chạy và bạn chết một chút bên trong.


1
Tôi nghĩ rằng RCurry và ncurry trên các hàm có đối số> 2 chứng minh rằng đây thực sự chỉ là một phần ứng dụng không phải là currying
jk.

@jk Trên thực tế, có thể chứng minh được các hàm có đối số == 2, như tôi lưu ý ở cuối. :)
Jordan Gray

3
@matcauthon Nói đúng ra, "mục đích" của currying là biến đổi một hàm có nhiều đối số thành một chuỗi các hàm lồng nhau với mỗi đối số. Tôi nghĩ những gì bạn đang yêu cầu là một lý do thực tế mà bạn muốn sử dụng cà ri, một chút khó khăn hơn để biện minh trong Groovy so với LISP hoặc Haskell. Vấn đề là, những gì bạn có thể muốn sử dụng hầu hết thời gian là ứng dụng một phần, không phải cà ri.
Jordan Gray

4
+1 choand you die a little inside
Thomas Eding

1
Ồ, tôi hiểu cà ri tốt hơn nhiều sau khi đọc câu trả lời của bạn: +1 và cảm ơn! Cụ thể cho câu hỏi, tôi thích rằng bạn nói, "cà ri kết hợp với ứng dụng một phần."
GlenPeterson

3

Tôi nghĩ rõ ràng rằng cà ri Groovy thực sự là một phần ứng dụng khi xem xét các hàm có nhiều hơn hai đối số. xem xét

f :: (a,b,c) -> d

hình thức của nó sẽ là

fcurried :: a -> b -> c -> d

tuy nhiên cà ri của Groovy sẽ trả về một cái gì đó tương đương với (giả sử được gọi với 1 đối số x)

fgroovy :: (b,c) -> d 

sẽ gọi f với giá trị cố định là x

tức là trong khi cà ri của Groovy có thể trả về các hàm với các đối số N-1, thì các hàm được cuộn đúng cách chỉ có 1 đối số, do đó Groovy không thể được cà ri với cà ri


2

Groovy đã mượn cách đặt tên các phương thức cà ri của mình từ nhiều ngôn ngữ FP không thuần túy khác cũng sử dụng cách đặt tên tương tự cho ứng dụng một phần - có lẽ không may cho chức năng trung tâm của FP như vậy. Có một số triển khai cà ri "thực sự" đang được đề xuất để đưa vào Groovy. Một chủ đề tốt để bắt đầu đọc về chúng là ở đây:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

Chức năng hiện có sẽ vẫn ở dạng nào đó và tính tương thích ngược sẽ được xem xét khi thực hiện cuộc gọi về cách đặt tên cho các phương thức mới, v.v. - vì vậy tôi không thể nói ở giai đoạn này việc đặt tên cuối cùng của phương thức mới / cũ sẽ là gì là. Có lẽ là một sự thỏa hiệp về việc đặt tên nhưng chúng ta sẽ thấy.

Đối với hầu hết các lập trình viên OO, sự khác biệt giữa hai thuật ngữ (ứng dụng currying và một phần) được cho là phần lớn mang tính học thuật; tuy nhiên, một khi bạn đã quen với chúng (và bất cứ ai sẽ duy trì mã của bạn được đào tạo để đọc kiểu mã hóa này) thì lập trình kiểu không điểm hoặc ngầm (hỗ trợ currying "thực") cho phép các loại thuật toán nhất định được thể hiện gọn hơn và trong một số trường hợp thanh lịch hơn. Rõ ràng có một số "vẻ đẹp nằm trong mắt của kẻ si tình" ở đây nhưng có khả năng hỗ trợ cả hai phong cách phù hợp với bản chất của Groovy (OO / FP, tĩnh / động, các lớp / tập lệnh, v.v.).


1

Đưa ra định nghĩa này được tìm thấy tại IBM:

Thuật ngữ cà ri được lấy từ Haskell Curry, nhà toán học đã phát triển khái niệm về các hàm một phần. Currying đề cập đến việc đưa nhiều đối số vào một hàm có nhiều đối số, dẫn đến một hàm mới lấy các đối số còn lại và trả về kết quả.

halverlà hàm (curried) mới của bạn (hoặc đóng), giờ chỉ mất một tham số. Gọi halver(10)sẽ có kết quả trong 5.

Do đó, nó biến đổi một hàm với n đối số trong một hàm với các đối số n-1. Điều tương tự cũng được nói bởi ví dụ haskell của bạn những gì cà ri làm.


4
Định nghĩa của IBM là không chính xác. Những gì họ định nghĩa là currying thực sự là một phần ứng dụng hàm, nó liên kết (sửa) các đối số của hàm để tạo ra một hàm có độ nhỏ hơn. Currying biến đổi một hàm lấy nhiều đối số thành một chuỗi các hàm mà mỗi đối số lấy một đối số.
Jordan Gray

1
Trong định nghĩa của wikipedia giống như từ IBM: Trong toán học và khoa học máy tính, currying là kỹ thuật biến đổi một hàm lấy nhiều đối số (hoặc một n-tuple của các đối số) theo cách mà nó có thể được gọi là chuỗi chức năng mỗi hàm với một đối số (ứng dụng một phần). Groovy không chuyển đổi một hàm (có hai đối số) với rcurryhàm (lấy một đối số) thành một hàm (hiện chỉ có một đối số). Tôi đã xâu chuỗi hàm curry với một đối số cho hàm cơ sở để lấy hàm kết quả.
matcauthon

3
Không định nghĩa wikipedia là khác nhau - ứng dụng một phần là khi bạn gọi hàm curried - không phải khi bạn định nghĩa nó là cái mà Groovy làm
jk.

6
@jk đúng rồi. Đọc lại lời giải thích của Wikipedia và bạn sẽ thấy rằng những gì được trả về là một chuỗi các hàm với mỗi đối số, không phải là một hàm với các n - 1đối số. Xem ví dụ ở cuối câu trả lời của tôi; cũng xem sau trong bài viết để biết thêm về sự khác biệt đang được thực hiện. vi.wikipedia.org/wiki/ Kẻ
Jordan Gray

4
Nó rất quan trọng, tin tôi đi. Một lần nữa, mã ở cuối câu trả lời của tôi cho thấy cách thực hiện đúng; nó sẽ không có đối số, cho một điều. Việc thực hiện hiện tại nên thực sự được đặt tên, ví dụ partial.
Jordan Gray
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.