Apakah groovy menyebut aplikasi parsial 'currying'?

15

Groovy memiliki konsep yang disebut 'currying'. Berikut ini contoh dari wiki mereka:

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

def halver = divide.rcurry(2)

assert halver(8) == 4

Pemahaman saya tentang apa yang terjadi di sini adalah bahwa argumen tangan kanan dividesedang terikat dengan nilai 2. Ini seperti bentuk aplikasi parsial.

Istilah currying biasanya digunakan untuk mengubah fungsi yang mengambil serangkaian argumen menjadi fungsi yang hanya membutuhkan satu argumen dan mengembalikan fungsi lainnya. Sebagai contoh di sini adalah jenis curryfungsi di Haskell:

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

Untuk orang yang belum pernah menggunakan Haskell a, bdan csemuanya adalah parameter umum. currymengambil fungsi dengan dua argumen, dan mengembalikan fungsi yang mengambil adan mengembalikan fungsi dari bke c. Saya telah menambahkan sepasang kurung tambahan ke jenisnya untuk membuatnya lebih jelas.

Pernahkah saya salah paham tentang apa yang terjadi dalam contoh asyik atau hanya sebagian aplikasi yang salah? Atau apakah itu memang melakukan keduanya: yaitu mengkonversi dividemenjadi fungsi kari dan kemudian sebagian berlaku 2untuk fungsi baru ini.

Richard Warburton
sumber
1
bagi mereka yang tidak berbicara haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk.

Jawaban:

14

Implementasi Groovy currytidak benar-benar menjilat pada titik mana pun, bahkan di belakang layar. Ini pada dasarnya identik dengan aplikasi parsial.

The curry, rcurrydan ncurrymetode mengembalikan CurriedClosureobjek yang memegang argumen terikat. Ini juga memiliki metode getUncurriedArguments(misnamed — you curry functions, not arguments) yang mengembalikan komposisi argumen yang diteruskan kepadanya dengan argumen terikat.

Ketika penutupan dipanggil, akhirnya panggilan itu invokeMethodmetodeMetaClassImpl , yang secara eksplisit memeriksa untuk melihat apakah objek memanggil adalah turunan dari CurriedClosure. Jika demikian, ia menggunakan yang disebutkan di atas getUncurriedArgumentsuntuk menyusun berbagai argumen untuk diterapkan:

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

Berdasarkan nomenklatur yang membingungkan dan agak tidak konsisten di atas, saya curiga bahwa siapa pun yang menulis ini memiliki pemahaman konseptual yang baik, tetapi mungkin sedikit terburu-buru dan — seperti banyak orang pintar — menyulap kari dengan aplikasi sebagian. Ini bisa dimengerti (lihat jawaban Paul King), jika sedikit disayangkan; akan sulit untuk memperbaikinya tanpa merusak kompatibilitas.

Salah satu solusi yang saya sarankan adalah membebani currymetode sedemikian rupa sehingga ketika tidak ada argumen yang dilewatinya itu benar- benar currying, dan mencela memanggil metode dengan argumen yang mendukung partialfungsi baru . Ini mungkin tampak sedikit aneh , tetapi ini akan memaksimalkan kompatibilitas ke belakang — karena tidak ada alasan untuk menggunakan aplikasi parsial dengan argumen nol — sambil menghindari situasi (IMHO) yang lebih buruk dari memiliki fungsi baru yang diberi nama berbeda untuk penjelajahan yang tepat sementara fungsi sebenarnya bernama currymelakukan sesuatu yang berbeda dan membingungkan mirip.

Tak perlu dikatakan bahwa hasil panggilan currysama sekali berbeda dari currying yang sebenarnya. Jika itu benar-benar mengacaukan fungsinya, Anda dapat menulis:

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 

... Dan itu akan berhasil, karena addCurriedharus bekerja seperti { x -> { y -> x + y } }. Sebaliknya itu melempar pengecualian runtime dan Anda mati sedikit di dalam.

Jordan Gray
sumber
1
Saya pikir rcurry dan ncurry pada fungsi dengan argumen> 2 menunjukkan bahwa ini benar-benar hanya sebagian aplikasi yang tidak currying
jk.
@ jk Bahkan, itu dapat dibuktikan pada fungsi dengan argumen == 2, seperti yang saya perhatikan di akhir. :)
Jordan Gray
3
@matcauthon Secara tegas, "tujuan" dari currying adalah untuk mengubah fungsi dengan banyak argumen menjadi rantai fungsi yang bersarang dengan masing-masing satu argumen. Saya pikir apa yang Anda minta adalah alasan praktis Anda ingin menggunakan kari, yang sedikit lebih sulit untuk dibenarkan di Groovy daripada di misalnya LISP atau Haskell. Intinya, apa yang mungkin ingin Anda gunakan sebagian besar waktu adalah aplikasi parsial, bukan kari.
Jordan Grey
4
+1 untukand you die a little inside
Thomas Eding
1
Wow, saya mengerti kari jauh lebih baik setelah membaca jawaban Anda: +1 dan terima kasih! Khusus untuk pertanyaan, saya suka Anda mengatakan, "menyisir kari dengan aplikasi parsial."
GlenPeterson
3

Saya pikir jelas bahwa asyik kari sebenarnya aplikasi parsial ketika mempertimbangkan fungsi dengan lebih dari dua argumen. mempertimbangkan

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

bentuk karinya adalah

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

Namun kari groovy akan mengembalikan sesuatu yang setara (dengan asumsi dipanggil dengan 1 argumen x)

fgroovy :: (b,c) -> d 

yang akan memanggil f dengan nilai tetap ke x

yaitu sementara kari groovy dapat mengembalikan fungsi dengan argumen N-1, fungsi kari dengan benar hanya pernah memiliki 1 argumen, oleh karena itu groovy tidak dapat kari dengan kari

jk.
sumber
2

Groovy meminjam penamaan metode kari dari banyak bahasa FP non-murni lainnya yang juga menggunakan penamaan yang serupa untuk aplikasi parsial - mungkin disayangkan untuk fungsi FP-centric tersebut. Ada beberapa implementasi kari "nyata" yang diusulkan untuk dimasukkan dalam Groovy. Sebuah utas yang baik untuk mulai membaca tentang mereka ada di sini:

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

Fungsionalitas yang ada akan tetap dalam beberapa bentuk dan kompatibilitas ke belakang akan dipertimbangkan ketika melakukan panggilan pada apa yang akan memberi nama metode baru dll. - jadi saya tidak bisa mengatakan pada tahap ini apa penamaan akhir dari metode baru / lama akan menjadi. Mungkin kompromi pada penamaan tetapi kita akan lihat.

Bagi kebanyakan programmer OO perbedaan antara dua istilah (currying dan aplikasi parsial) bisa dibilang sebagian besar bersifat akademis; Namun, begitu Anda terbiasa dengan mereka (dan siapa pun yang akan menjaga kode Anda dilatih untuk membaca gaya pengkodean ini) maka pemrograman gaya point-free atau diam-diam (yang mendukung kari "nyata" mendukung) memungkinkan jenis algoritma tertentu untuk diekspresikan lebih kompak. dan dalam beberapa kasus lebih elegan. Jelas ada beberapa "keindahan terletak di mata yang melihatnya" di sini tetapi memiliki kemampuan untuk mendukung kedua gaya sesuai dengan sifat Groovy (OO / FP, statis / dinamis, kelas / skrip dll).

Paul King
sumber
1

Mengingat definisi ini ditemukan di IBM:

Istilah kari diambil dari Haskell Curry, ahli matematika yang mengembangkan konsep fungsi parsial. Currying mengacu pada mengambil beberapa argumen ke dalam fungsi yang membutuhkan banyak argumen, menghasilkan fungsi baru yang mengambil argumen yang tersisa dan mengembalikan hasilnya.

halveradalah fungsi (atau kari) baru Anda, yang sekarang hanya membutuhkan satu parameter. Panggilan halver(10)akan menghasilkan 5.

Untuk itu mengubah fungsi dengan argumen n dalam fungsi dengan argumen n-1. Hal yang sama dikatakan oleh haskell Anda contoh apa yang kari lakukan.

matcauthon
sumber
4
Definisi IBM tidak benar. Apa yang mereka definisikan sebagai currying sebenarnya adalah aplikasi fungsi parsial, yang mengikat (memperbaiki) argumen fungsi untuk membuat fungsi dengan arity yang lebih kecil. Currying mengubah fungsi yang mengambil banyak argumen menjadi rangkaian fungsi yang masing-masing mengambil satu argumen.
Jordan Grey
1
Dalam definisi wikipedia adalah sama dengan dari IBM: Dalam matematika dan ilmu komputer, currying adalah teknik mengubah fungsi yang mengambil banyak argumen (atau n-tuple argumen) sedemikian rupa sehingga dapat disebut sebagai rantai fungsi masing-masing dengan argumen tunggal (aplikasi parsial). Groovy mengubah suatu fungsi (dengan dua argumen) dengan rcurryfungsi (yang mengambil satu argumen) menjadi fungsi (dengan sekarang hanya satu argumen). Saya merantai fungsi kari dengan argumen ke fungsi basis saya untuk mendapatkan fungsi yang dihasilkan.
matcauthon
3
tidak definisi wikipedia berbeda - aplikasi parsial adalah ketika Anda memanggil fungsi curried - bukan ketika Anda mendefinisikannya yang adalah apa yang dilakukan groovy
jk.
6
@ jk benar. Baca penjelasan Wikipedia lagi dan Anda akan melihat bahwa yang dikembalikan adalah rangkaian fungsi dengan masing-masing argumen, bukan satu fungsi dengan n - 1argumen. Lihat contoh di akhir jawaban saya; lihat juga nanti di artikel untuk lebih lanjut tentang perbedaan yang dibuat. en.wikipedia.org/wiki/…
Jordan Gray
4
Ini sangat penting, percayalah. Sekali lagi, kode di akhir jawaban saya menunjukkan bagaimana implementasi yang benar akan bekerja; tidak perlu berdebat, untuk satu hal. Implementasi saat ini harus benar-benar dinamai misalnya partial.
Jordan Grey