Apakah penutupan dianggap tidak murni dalam pemrograman fungsional?
Tampaknya seseorang umumnya dapat menghindari penutupan dengan memberikan nilai langsung ke suatu fungsi. Karena itu, apakah penutupan harus dihindari jika memungkinkan?
Jika mereka tidak murni, dan saya benar dalam menyatakan bahwa mereka dapat dihindari, mengapa begitu banyak bahasa pemrograman fungsional mendukung penutupan?
Salah satu kriteria untuk fungsi murni adalah bahwa "Fungsi selalu mengevaluasi nilai hasil yang sama dengan nilai argumen yang sama ."
Seharusnya
f: x -> x + y
f(3)
tidak akan selalu memberikan hasil yang sama. f(3)
tergantung pada nilai y
yang bukan merupakan argumen f
. Jadi f
bukan fungsi murni.
Karena semua penutupan bergantung pada nilai-nilai yang bukan argumen, bagaimana mungkin penutupan apa pun menjadi murni? Ya, secara teori nilai tertutup bisa konstan tetapi tidak ada cara untuk mengetahui bahwa hanya dengan melihat kode sumber fungsi itu sendiri.
Hal ini menuntun saya ke adalah bahwa fungsi yang sama mungkin murni dalam satu situasi tetapi tidak murni dalam situasi lain. Seseorang tidak selalu dapat menentukan apakah suatu fungsi murni atau tidak dengan mempelajari kode sumbernya. Sebaliknya, seseorang mungkin harus mempertimbangkannya dalam konteks lingkungannya pada titik ketika ia dipanggil sebelum perbedaan tersebut dapat dibuat.
Apakah saya memikirkan hal ini dengan benar?
sumber
y
tidak bisa berubah, sehingga output darif(3)
akan selalu sama.y
adalah bagian dari definisif
walaupun itu tidak secara eksplisit ditandai sebagai inputf
- itu masih merupakan kasus yangf
didefinisikan dalam haly
(kita dapat menunjukkan fungsi f_y, untuk membuat ketergantungan paday
eksplisit), dan oleh karena itu perubahany
memberikan fungsi yang berbeda . Fungsi khusus yangf_y
didefinisikan untuk suatu fungsiy
sangat murni. (Misalnya, kedua fungsif: x -> x + 3
dan fungsif: x -> x + 5
yang berbeda , dan keduanya murni, meskipun kami kebetulan menggunakan huruf yang sama untuk menyatakannya.)Jawaban:
Kemurnian dapat diukur dengan dua hal:
Jika jawaban 1 adalah ya dan jawaban 2 tidak, maka fungsinya murni. Penutupan hanya membuat fungsi tidak murni jika Anda memodifikasi variabel tertutup.
sumber
y
. Jadi misalnya kita mendefinisikan fungsig
sedemikian rupa sehingga fungsi itug(y)
sendirix -> x + y
. Kemudiang
adalah fungsi integer yang mengembalikan fungsi,g(3)
adalah fungsi integer yang mengembalikan integer, dang(2)
merupakan fungsi berbeda dari integer yang mengembalikan integer. Ketiga fungsi itu murni.Penutupan muncul Lambda Calculus, yang merupakan bentuk paling murni dari pemrograman fungsional yang mungkin, jadi saya tidak akan menyebut mereka "tidak murni" ...
Penutupan tidak "tidak murni" karena fungsi dalam bahasa fungsional adalah warga negara kelas satu - yang berarti mereka dapat diperlakukan sebagai nilai.
Bayangkan ini (kodesemu):
y
adalah sebuah nilai. Nilainya tergantungx
, tetapix
tidak berubah sehinggay
nilainya juga tidak berubah. Kita dapat memanggilfoo
berkali-kali dengan argumen berbeda yang akan menghasilkany
s yang berbeda , tetapiy
itu semua hidup dalam cakupan yang berbeda dan bergantung padax
s yang berbeda sehingga kemurnian tetap utuh.Sekarang mari kita ubah:
Di sini kita menggunakan closure (kita menutup x), tapi itu sama seperti in
foo
- callsbar
berbeda dengan argumen yang berbeda menciptakan nilai yang berbeda dariy
(ingat - fungsi adalah nilai) yang semuanya tidak dapat diubah sehingga kemurnian tetap utuh.Perlu diketahui juga bahwa penutupan memiliki efek yang sangat mirip dengan kari:
baz
tidak benar-benar berbeda daribar
- di keduanya kita membuat nilai fungsi bernamay
yang mengembalikan argumen plus itux
. Sebenarnya, dalam Kalkulus Lambda Anda menggunakan penutupan untuk membuat fungsi dengan beberapa argumen - dan itu masih tidak murni.sumber
Orang lain telah meliput pertanyaan umum dengan baik dalam jawaban mereka, jadi saya hanya akan melihat membersihkan kebingungan yang Anda berikan pada hasil edit Anda.
Penutupan tidak menjadi input dari fungsi, melainkan 'masuk' ke fungsi tubuh. Agar lebih konkret, fungsi mengacu pada nilai dalam lingkup luar di tubuhnya.
Anda mendapat kesan bahwa itu membuat fungsi tidak murni. Itu tidak terjadi, secara umum. Dalam pemrograman fungsional, nilai-nilai sebagian besar tidak berubah . Itu berlaku untuk nilai penutupan juga.
Katakanlah Anda memiliki kode seperti ini:
Memanggil
make 3
danmake 4
akan memberikan dua fungsi dengan penutupan lebihmake
'sy
argumen. Salah satu dari mereka akan kembalix + 3
, yang lainx + 4
. Namun mereka adalah dua fungsi yang berbeda, dan keduanya murni. Mereka dibuat menggunakanmake
fungsi yang sama , tetapi hanya itu.Catat sebagian besar waktu beberapa paragraf kembali.
Perhatikan bahwa untuk 2 dan 3, bahasa-bahasa itu tidak menawarkan jaminan apa pun tentang kemurnian. Kenajisan di sana bukan milik penutupan, tetapi dari bahasa itu sendiri. Penutupan tidak banyak mengubah gambar sendiri.
sumber
IO A
nilai yang tidak dapat diubah , dan tipe penutupan AndaIO (B -> C)
atau semacamnya. Kemurnian dipertahankanBiasanya saya akan meminta Anda untuk menjelaskan definisi "tidak murni" Anda, tetapi dalam hal ini tidak terlalu penting. Anggap Anda mengontraskannya dengan istilah yang murni fungsional , jawabannya adalah "tidak", karena tidak ada penutupan yang secara inheren bersifat destruktif. Jika bahasa Anda benar-benar berfungsi tanpa penutupan, itu masih akan berfungsi murni dengan penutupan. Jika sebaliknya yang Anda maksud "tidak berfungsi", jawabannya tetap "tidak"; Penutupan memfasilitasi pembuatan fungsi.
Ya, tetapi kemudian fungsi Anda akan memiliki satu parameter lagi, dan itu akan mengubah tipenya. Penutupan memungkinkan Anda membuat fungsi berdasarkan variabel tanpa menambahkan parameter. Ini berguna ketika Anda memiliki, katakanlah, fungsi yang membutuhkan 2 argumen, dan Anda ingin membuat versi yang hanya membutuhkan 1 argumen.
EDIT: Sehubungan dengan edit / contoh Anda sendiri ...
Tergantung adalah pilihan kata yang salah di sini. Mengutip artikel Wikipedia yang sama dengan yang Anda lakukan:
Dengan asumsi
y
tidak berubah (yang biasanya terjadi dalam bahasa fungsional), kondisi 1 puas: untuk semua nilaix
, nilaif(x)
tidak berubah. Ini harus jelas dari fakta yangy
tidak berbeda dari konstanta, danx + 3
murni. Juga jelas tidak ada mutasi atau I / O yang terjadi.sumber
Sangat cepat: substitusi adalah "transparan referensial" jika "mengganti yang suka dengan yang suka" dan fungsi yang "murni" jika semua efeknya terkandung dalam nilai kembalinya. Keduanya dapat dibuat tepat, tetapi penting untuk dicatat bahwa mereka tidak identik atau bahkan tidak satu sama lain.
Sekarang mari kita bicara tentang penutupan.
"Penutupan" yang membosankan (kebanyakan murni)
Penutupan terjadi karena ketika kita mengevaluasi istilah lambda kita menafsirkan variabel (terikat) sebagai pencarian lingkungan. Jadi, ketika kita mengembalikan istilah lambda sebagai hasil dari evaluasi, variabel-variabel di dalamnya akan "menutup" nilai-nilai yang mereka ambil ketika didefinisikan.
Dalam kalkulus lambda biasa, ini adalah hal yang sepele dan seluruh gagasan menghilang begitu saja. Untuk menunjukkan itu, berikut adalah juru bahasa kalkulus lambda yang relatif ringan:
Bagian penting yang perlu diperhatikan adalah
addEnv
ketika kita menambah lingkungan dengan nama baru. Fungsi ini dipanggil hanya "di dalam" dariAbs
istilah traksi yang ditafsirkan (istilah lambda). Lingkungan akan "mendongak" setiap kali kita mengevaluasi suatuVar
istilah dan oleh karenaVar
itu tekad untuk apa pun yangName
dimaksud dalamEnv
yang ditangkap olehAbs
traksi yang mengandungVar
.Sekarang, sekali lagi, dalam istilah LC biasa ini membosankan. Ini berarti bahwa variabel terikat hanya konstanta sejauh ada yang peduli. Mereka dievaluasi secara langsung dan segera sebagai nilai-nilai yang mereka tunjukkan dalam lingkungan sebagai leksikal mencakup hingga titik itu.
Ini juga (hampir) murni. Satu-satunya makna istilah apa pun dalam kalkulus lambda kami ditentukan oleh nilai pengembaliannya. Satu-satunya pengecualian adalah efek samping dari non-terminasi yang diwujudkan oleh istilah Omega:
Penutupan (tidak murni) yang menarik
Sekarang untuk latar belakang tertentu penutupan yang dijelaskan dalam LC sederhana di atas membosankan karena tidak ada gagasan untuk dapat berinteraksi dengan variabel yang telah kami tutup. Secara khusus, kata "closure" cenderung memohon kode seperti Javascript berikut
Ini menunjukkan bahwa kita telah menutup
n
variabel dalam fungsi dalamincr
dan memanggilincr
interaksi yang bermakna dengan variabel itu.mk_counter
murni, tetapiincr
jelas tidak murni (dan tidak transparan secara referensial).Apa yang membedakan kedua contoh ini?
Pengertian "variabel"
Jika kita melihat apa arti substitusi dan abstraksi dalam arti LC sederhana kita perhatikan bahwa mereka jelas-jelas polos. Variabel secara harfiah tidak lebih dari pencarian lingkungan langsung. Abstraksi Lambda secara harfiah tidak lebih dari menciptakan lingkungan yang diperluas untuk mengevaluasi ekspresi batin. Tidak ada ruang dalam model ini untuk jenis perilaku yang kita lihat
mk_counter
/incr
karena tidak ada variasi yang diizinkan.Bagi banyak orang inilah inti makna "variabel" — variasi. Namun, semanticists suka membedakan antara jenis variabel yang digunakan dalam LC dan jenis "variabel" yang digunakan dalam Javascript. Untuk melakukannya, mereka cenderung menyebut yang terakhir sebagai "sel yang bisa berubah-ubah" atau "slot".
Nomenklatur ini mengikuti penggunaan historis yang panjang dari "variabel" dalam matematika di mana itu berarti sesuatu yang lebih seperti "tidak diketahui": ekspresi (matematika) "x + x" tidak memungkinkan untuk
x
bervariasi dari waktu ke waktu, itu malah dimaksudkan untuk memiliki makna terlepas dari dari nilai (tunggal, konstan) yangx
diambil.Jadi, kami mengatakan "slot" untuk menekankan kemampuan untuk menempatkan nilai ke dalam slot dan membawanya keluar.
Untuk menambah kebingungan lebih lanjut, dalam Javascript "slot" ini terlihat sama dengan variabel: kita menulis
untuk membuat satu dan kemudian ketika kita menulis
itu menunjukkan kita mencari nilai yang saat ini disimpan di slot itu. Untuk membuat ini lebih jelas, bahasa murni cenderung menganggap slot sebagai mengambil nama sebagai (matematika, lambda kalkulus) nama. Dalam hal ini kita harus secara eksplisit memberi label ketika kita mendapatkan atau memasukkannya dari slot. Notasi seperti itu cenderung terlihat seperti
Keuntungan dari notasi ini adalah bahwa kita sekarang memiliki perbedaan tegas antara variabel matematika dan slot yang bisa berubah. Variabel dapat mengambil slot sebagai nilainya, tetapi slot tertentu yang dinamai oleh variabel konstan sepanjang cakupannya.
Dengan menggunakan notasi ini, kita dapat menulis ulang
mk_counter
contoh (kali ini dalam sintaksis mirip Haskell, meskipun semantik tidak seperti Haskell):Dalam hal ini kami menggunakan prosedur yang memanipulasi slot yang dapat berubah ini. Untuk mengimplementasikannya, kita harus menutup tidak hanya lingkungan nama yang konstan seperti
x
tetapi juga lingkungan yang bisa berubah yang berisi semua slot yang diperlukan. Ini lebih dekat dengan gagasan umum tentang "penutupan" yang sangat dicintai orang.Sekali lagi,
mkCounter
sangat tidak murni. Itu juga sangat referensial buram. Tetapi perhatikan bahwa efek samping tidak muncul dari penangkapan atau penutupan nama melainkan penangkapan dari sel yang bisa berubah dan operasi efek samping yang disukaiget
danput
.Pada akhirnya, saya pikir ini adalah jawaban terakhir untuk pertanyaan Anda: kemurnian tidak dipengaruhi oleh penangkapan variabel (matematis) melainkan oleh operasi efek samping yang dilakukan pada slot yang dapat diubah yang dinamai oleh variabel yang ditangkap.
Hanya saja dalam bahasa-bahasa yang tidak berusaha dekat dengan LC atau tidak berusaha mempertahankan kemurnian, kedua konsep ini begitu sering digabungkan sehingga menimbulkan kebingungan.
sumber
Tidak, closure tidak menyebabkan fungsi menjadi tidak murni, selama nilai tertutupnya konstan (tidak diubah oleh closure atau kode lain), yang merupakan kasus umum dalam pemrograman fungsional.
Perhatikan bahwa meskipun Anda selalu dapat memberikan nilai sebagai argumen, biasanya Anda tidak dapat melakukannya tanpa kesulitan yang berarti. Misalnya (naskah kopi):
Dengan saran Anda, Anda bisa mengembalikan:
Fungsi ini tidak dipanggil pada titik ini, hanya ditentukan , jadi Anda harus menemukan beberapa cara untuk melewatkan nilai yang Anda inginkan
closedValue
ke titik di mana fungsi sebenarnya dipanggil. Paling-paling ini menciptakan banyak kopling. Paling buruk, Anda tidak mengontrol kode pada titik panggilan, sehingga secara efektif tidak mungkin.Pustaka acara dalam bahasa yang tidak mendukung penutupan biasanya menyediakan cara lain untuk meneruskan data sewenang-wenang kembali ke panggilan balik, tetapi tidak cantik, dan menciptakan banyak kerumitan baik untuk pengelola perpustakaan dan pengguna perpustakaan.
sumber