Saya seorang pemula ke Haskell dan saya telah mengikuti e-book Pemrograman dengan Haskell

Saya belajar tentang penutupan dengan fungsi lambda tetapi saya gagal melihat perbedaan dalam kode berikut:

genIfEven :: Integral p => p -> (p -> p) -> p
genIfEven x = (\f -> isEven f x)

genIfEven2 :: Integral p => (p -> p) -> p -> p
genIfEven2 f x = isEven f x

Akan sangat bagus jika ada yang bisa menjelaskan apa perbedaan yang tepat di sini

0
Hamothy 5 April 2021, 06:08

2 jawaban

Jawaban Terbaik

1

Fungsi adalah nilai kelas satu di Haskell. Yang berarti Anda dapat meneruskannya ke fungsi lain, mengembalikannya sebagai hasil, menyimpan dan mengambilnya dalam struktur data, dll, dll sama seperti Anda bisa dengan angka, atau string, atau nilai lainnya.

Sama seperti dengan angka, string, dll, sangat membantu untuk memiliki sintaks untuk menunjukkan nilai fungsi, karena Anda mungkin ingin membuat yang sederhana tepat di tengah kode lain. Akan sangat mengerikan jika Anda, katakan, perlu lulus x + 1 ke beberapa fungsi dan Anda tidak bisa hanya menulis literal 1 untuk nomor satu, Anda harus pergi ke tempat lain dalam file dan Tambahkan one = 1 mengikat sehingga Anda dapat kembali dan menulis x + one. Dengan cara yang persis sama, Anda mungkin perlu meneruskan fungsi lain untuk menambahkan 1; Akan menjengkelkan untuk pergi ke tempat lain dalam file dan menambahkan definisi terpisah plusOne x = x + 1, jadi sintaks lambda memberi kita cara menulis "fungsi literal": \x -> x + 1. 2

Mempertimbangkan sintaks definisi fungsi "normal", seperti ini:

incrementAllBy _ [] = []
incrementAllBy n (x:xs) = (x + n) : xs 

Di sini kami tidak memiliki sedikit kode sumber yang hanya mewakili nilai fungsi yang incrementAllBy adalah nama untuk. Fungsi ini tersirat dalam sintaks ini, menyebar lebih banyak "aturan" yang mengatakan apa nilai fungsi kami mengembalikan mengingat bahwa itu diterapkan pada argumen dari bentuk tertentu. Sintaks ini juga mendasar memaksa kita untuk mengikat fungsi ke nama. Semua itu berbeda dengan sintaks lambda yang secara langsung mewakili fungsi itu sendiri, tanpa analisis atau nama kasus yang dibundel.

Namun mereka berdua hanya berbagai cara menulis fungsi. Setelah didefinisikan, tidak ada perbedaan antara fungsi, yang sintaks mana yang Anda gunakan untuk mengekspresikannya.


Anda menyebutkan bahwa Anda belajar tentang penutupan. Tidak terlalu jelas bagaimana itu berkaitan dengan pertanyaan, tetapi saya dapat menebak itu agak membingungkan.

3.

Penutupan adalah apa yang terlibat dalam membuat sesuatu seperti incrementAllBy n xs = map (\x -> x + n) xs berfungsi. Fungsi yang dibuat di sini \x -> x + n tergantung pada n, yang merupakan parameter sehingga bisa berbeda setiap kali incrementAllBy disebut, dan dapat ada beberapa panggilan seperti itu berjalan pada saat yang sama. Jadi fungsi \x -> x + n ini tidak dapat berakhir hanya sebagai potongan kode yang dikompilasi pada alamat tertentu dalam biner program, cara fungsi tingkat atas. Struktur dalam memori yang diteruskan ke map harus menyimpan salinan n atau menyimpan referensi untuk itu. Struktur seperti itu disebut "penutupan", dan dikatakan telah "menutup" n, atau "menangkap" itu.

Di Haskell, Anda tidak perlu tahu semua itu. Saya melihat ekspresi \n -> x + n hanya dengan membuat nilai fungsi baru, tergantung pada nilai n (dan juga nilai +, yang merupakan nilai kelas satu juga!) Itu terjadi pada dalam ruang lingkup. Saya tidak berpikir Anda perlu memikirkan hal ini berbeda dari yang Anda pikirkan tentang ekspresi x + n membuat nilai numerik baru tergantung pada {{x4} setempat. Penutupan di Haskell hanya penting ketika Anda mencoba memahami bagaimana bahasa tersebut diimplementasikan, bukan ketika Anda pemrograman di Haskell.

Penutupan do materi dalam bahasa imperatif. Ada pertanyaan apakah (setara) \x -> x + n menyimpan referensi ke n atau salinan n (dan ketika salinan diambil, dan seberapa dalam) sangat penting untuk memahami caranya Kode menggunakan fungsi ini berfungsi, karena n bukan hanya cara merujuk pada nilai, itu adalah variabel yang memiliki (berpotensi) nilai yang berbeda dari waktu ke waktu.

Tetapi di Haskell saya tidak benar-benar berpikir kita harus mengajar pemula tentang istilah atau konsep penutupan. Ini sangat rumit "Anda dapat membuat fungsi baru dari nilai yang ada, bahkan yang hanya dalam ruang lingkup".

Jadi, jika Anda telah diberi dua fungsi ini sebagai contoh untuk mencoba dan menggambarkan konsep penutupan, dan itu tidak masuk akal bagi Anda apa bedanya "penutupan" ini, Anda mungkin dapat mengabaikan seluruh masalah dan beralih ke sesuatu yang lebih penting.


1 Kadang-kadang pilihan sintaks "setara" yang Anda gunakan untuk menulis kode apakah mempengaruhi perilaku operasional, seperti kinerja. Biasanya (tetapi tidak selalu) efeknya dapat diabaikan. Sebagai pemula, saya sangat merekomendasikan mengabaikan masalah seperti itu untuk saat ini, jadi saya belum menyebutkannya. Jauh lebih mudah untuk mempelajari prinsip-prinsip yang terlibat dalam alasan tentang bagaimana kode Anda dapat dieksekusi setelah Anda memiliki pemahaman menyeluruh tentang apa artinya semua elemen bahasa.

Kadang-kadang juga dapat memengaruhi cara GHC menyiapkan jenis (kebanyakan sebenarnya bukan fakta bahwa mereka lambdas, tetapi jika Anda mengikat nama fungsi tanpa parameter sintaksis seperti pada plusOne = \x -> x + 1 Anda dapat menyampaikan batasan monomorfisme, tapi itu topik lain Tercakup dalam banyak pertanyaan overflow, jadi saya tidak akan mengatasinya di sini).

2 Dalam hal ini Anda juga dapat menggunakan bagian operator untuk menulis fungsi yang lebih sederhana literal seperti (+1).

3.

1
Ben 5 April 2021, 06:26

Tidak ada perbedaan apa pun, kecuali satu: ekspresi lambda tidak perlu nama. Untuk alasan itu, mereka kadang-kadang disebut "fungsi anonim" dalam bahasa lain.

Jika Anda berencana untuk sering menggunakan fungsi, Anda akan ingin memberinya nama, jika Anda hanya membutuhkannya sekali, lambda biasanya akan melakukannya, karena Anda dapat mendefinisikannya di situs tempat Anda menggunakannya.

Anda dapat, tentu saja, nama fungsi anonim setelah itu lahir:

genIfEven2 = \f x -> isEven f x

Itu akan setara dengan definisi Anda.

2
Hans Lub 5 April 2021, 06:47