Saya telah membaca artikel tentang pemrograman Fungsional setiap hari dan berusaha menerapkan beberapa praktik sebanyak mungkin. Tapi saya tidak mengerti apa yang unik dalam aplikasi kari atau aplikasi parsial.
Ambil kode Groovy ini sebagai contoh:
def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }
Saya tidak mengerti apa perbedaan antara tripler1
dan tripler2
. Bukankah keduanya sama? 'Currying' didukung dalam bahasa fungsional murni atau parsial seperti Groovy, Scala, Haskell dll. Tapi saya dapat melakukan hal yang sama (kari-kiri, kari-kanan, kari-n, atau aplikasi sebagian) dengan hanya membuat nama lain atau anonim fungsi atau penutupan yang akan meneruskan parameter ke fungsi asli (seperti tripler2
) di sebagian besar bahasa (bahkan C.)
Apakah saya melewatkan sesuatu di sini? Ada tempat-tempat di mana saya dapat menggunakan aplikasi currying dan parsial dalam aplikasi Grails saya tetapi saya ragu untuk melakukannya karena saya bertanya pada diri sendiri "Apa bedanya?"
Tolong beri tahu saya.
EDIT: Apakah kalian mengatakan bahwa aplikasi parsial / currying hanya lebih efisien daripada membuat / memanggil fungsi lain yang meneruskan parameter default ke fungsi asli?
sumber
f x y = x + y
berarti ituf
adalah fungsi yang mengambil satu parameter int. Hasil darif x
(f
diterapkan kex
) adalah fungsi yang mengambil satu parameter int. Hasilnyaf x y
(atau(f x) y
, yaituf x
diterapkan key
) adalah ekspresi yang tidak mengambil parameter input dan dievaluasi dengan mengurangix + y
.Jawaban:
Currying adalah tentang memutar / mewakili suatu fungsi yang mengambil n input menjadi n fungsi yang masing-masing mengambil 1 input. Aplikasi parsial adalah tentang memperbaiki beberapa input ke suatu fungsi.
Motivasi untuk aplikasi parsial terutama yang membuatnya lebih mudah untuk menulis perpustakaan fungsi tingkat tinggi. Sebagai contoh, algoritma dalam C ++ STL sebagian besar mengambil predikat atau fungsi unary, bind1st memungkinkan pengguna perpustakaan untuk mengaitkan fungsi non unary dengan batas nilai. Penulis perpustakaan karenanya tidak perlu menyediakan fungsi berlebih untuk semua algoritma yang mengambil fungsi unary untuk menyediakan versi biner
Currying itu sendiri berguna karena memberi Anda sebagian aplikasi di mana saja Anda inginkan secara gratis yaitu Anda tidak lagi memerlukan fungsi seperti
bind1st
untuk menerapkan sebagian.sumber
currying
sesuatu yang spesifik untuk asyik atau berlaku di berbagai bahasa?Dan pengoptimal akan melihat itu dan segera pergi ke sesuatu yang dapat dimengerti. Currying adalah trik kecil yang bagus untuk pengguna akhir, tetapi memiliki manfaat yang jauh lebih baik dari sudut pandang desain bahasa. Ini benar-benar bagus untuk menangani semua metode yang unary
A -> B
manaB
mungkin metode lain.Ini menyederhanakan metode apa yang harus Anda tulis untuk menangani fungsi tingkat tinggi. Analisis statis dan pengoptimalan dalam bahasa hanya memiliki satu jalur untuk bekerja dengan yang berperilaku dengan cara yang diketahui. Penjilidan parameter hanya jatuh dari desain daripada membutuhkan simpai untuk melakukan perilaku umum ini.
sumber
Sebagai @jk. disinggung, kari dapat membantu membuat kode lebih umum.
Sebagai contoh, misalkan Anda memiliki tiga fungsi ini (dalam Haskell):
Fungsi di
f
sini mengambil dua fungsi sebagai argumen, beralih1
ke fungsi pertama dan meneruskan hasil panggilan pertama ke fungsi kedua.Jika kami akan menelepon
f
menggunakanq
danr
sebagai argumen, itu akan efektif dilakukan:di mana
q
akan diterapkan ke1
dan mengembalikan fungsi lain (sepertiq
yang kari); fungsi yang dikembalikan ini kemudian akan diteruskan ker
sebagai argumen untuk diberikan argumen3
. Hasil ini akan menjadi nilai9
.Sekarang, katakanlah kita memiliki dua fungsi lain:
kami dapat meneruskannya
f
juga dan mendapatkan nilai7
atau15
, tergantung pada apakah argumen kamis t
ataut s
. Karena fungsi-fungsi ini keduanya mengembalikan nilai daripada fungsi, tidak ada aplikasi parsial yang akan terjadi dif s t
atauf t s
.Jika kami telah menulis
f
denganq
danr
mengingat kami mungkin telah menggunakan lambda (fungsi anonim) daripada aplikasi parsial, misalnya:tetapi ini akan membatasi generalisasi
f'
.f
dapat dipanggil dengan argumenq
danr
ataus
dant
, tetapif'
hanya bisa dipanggil denganq
danr
-f' s t
danf' t s
keduanya menghasilkan kesalahan.LEBIH
Jika
f'
dipanggil dengan aq'
/r'
pair di manaq'
mengambil lebih dari dua argumen,q'
masih akan sebagian diterapkan dif'
.Atau, Anda bisa membungkus di
q
luar,f
bukan di dalam, tapi itu akan meninggalkan Anda dengan lambda bersarang jahat:yang pada dasarnya adalah kari
q
itu sejak awal!sumber
def f = { a, b -> b a.curry(1) }
agarf q, r
bekerja dandef f = { a, b -> b a(1) }
ataudef f = { a, b -> b a.curry(1)() }
untukf s, t
bekerja. Anda harus melewati semua parameter atau secara eksplisit mengatakan Anda sedang mencari. :(f x y
berarti apa yang akan ditulis oleh banyak bahasaf(x)(y)
, bukanf(x, y)
. Mungkin kode Anda akan berfungsi di Groovy jika Anda menulisq
sehingga diharapkan seperti ituq(1)(2)
?(partial f a b ...)
- terbiasa dengan Haskell, saya kehilangan banyak currying yang tepat ketika pemrograman dalam bahasa lain (meskipun saya baru-baru ini bekerja di F # yang, untungnya, mendukungnya).Ada dua poin utama tentang aplikasi parsial. Yang pertama adalah sintaksis / kenyamanan - beberapa definisi menjadi lebih mudah dan lebih pendek untuk dibaca dan ditulis, seperti yang disebutkan oleh @jk. (Lihat pemrograman Pointfree untuk informasi lebih lanjut tentang betapa hebatnya ini!)
Yang kedua, seperti yang disebutkan @telastyn, adalah tentang model fungsi dan tidak hanya nyaman. Dalam versi Haskell, dari mana saya akan mendapatkan contoh saya karena saya tidak terbiasa dengan bahasa lain dengan aplikasi parsial, semua fungsi mengambil satu argumen. Ya, bahkan fungsinya seperti:
ambil satu argumen; karena asosiatif dari konstruktor tipe fungsi
->
, hal di atas setara dengan:yang merupakan fungsi yang mengambil
a
dan mengembalikan fungsi[a] -> [a]
.Ini memungkinkan kita untuk menulis fungsi seperti:
yang dapat menerapkan fungsi apa pun pada argumen dengan tipe yang sesuai. Bahkan yang gila seperti:
Oke, jadi itu contoh yang dibuat-buat. Tetapi yang lebih bermanfaat melibatkan kelas tipe Applicative , yang mencakup metode ini:
Seperti yang Anda lihat, tipe ini identik dengan
$
jika Anda mengambilApplicative f
bitnya, dan pada kenyataannya, kelas ini menjelaskan aplikasi fungsi dalam suatu konteks. Jadi alih-alih aplikasi fungsi normal:Kami dapat menerapkan fungsi dalam konteks Applicative; misalnya, dalam konteks Maybe di mana sesuatu dapat hadir atau hilang:
Sekarang bagian yang sangat keren adalah bahwa kelas tipe Applicative tidak menyebutkan apa pun tentang fungsi lebih dari satu argumen - namun, ia dapat menangani mereka, bahkan fungsi 6 argumen seperti
f
:Sejauh yang saya tahu, kelas tipe Applicative dalam bentuk umumnya tidak akan mungkin terjadi tanpa konsepsi aplikasi parsial. (Untuk para ahli pemrograman di luar sana - tolong perbaiki saya jika saya salah!) Tentu saja, jika bahasa Anda tidak memiliki aplikasi parsial, Anda dapat membangunnya dalam beberapa bentuk, tapi ... itu tidak sama, bukan? ? :)
sumber
Applicative
tanpa currying atau aplikasi parsial akan digunakanfzip :: (f a, f b) -> f (a, b)
. Dalam bahasa dengan fungsi tingkat tinggi, ini memungkinkan Anda mengangkat aplikasi sebagian dan currying ke dalam konteks functor dan setara dengan(<*>)
. Tanpa fungsi tingkat tinggi Anda tidak akan memilikifmap
sehingga semuanya akan sia-sia.f <$> x <*> y
gaya idiomatik yang menawan bekerja dengan mudah karena aplikasi aplikasi kari dan parsial bekerja dengan mudah. Dengan kata lain, apa yang menyenangkan lebih penting daripada apa yang mungkin di sini.