Mengapa program fungsional memiliki korelasi antara keberhasilan kompilasi dan kebenaran?

12

Saya sudah trending ke arah pemrograman fungsional selama 4 tahun sekarang, sejak saya mulai bekerja dengan LINQ. Baru-baru ini, saya menulis beberapa kode fungsional C # murni dan saya perhatikan, secara langsung, apa yang telah saya baca tentang program fungsional - yang setelah mereka kompilasi, mereka cenderung benar.

Saya mencoba menekankan mengapa ini yang terjadi tetapi saya belum berhasil.

Satu tebakan adalah bahwa dalam menerapkan prinsip-prinsip OO, Anda memiliki "lapisan abstraksi" yang tidak ada dalam program-program fungsional dan lapisan abstraksi ini memungkinkan kontrak antara objek-objek menjadi benar sementara implementasi salah.

Adakah yang memikirkan hal ini dan mengemukakan alasan abstrak yang mendasari korelasi antara keberhasilan kompilasi dan kebenaran program dalam pemrograman fungsional?

Aaron Anodide
sumber
9
Lisp adalah bahasa fungsional, namun tidak memiliki waktu kompilasi untuk berbicara. Sama untuk beberapa bahasa fungsional lainnya. Karakterisasi yang lebih akurat dari bahasa yang Anda bicarakan adalah: Bahasa dengan sistem tipe formal (setidaknya Hindley-Milner) yang kuat.
1
@delnan Saya tidak akan mengatakan bahwa Lisp adalah bahasa pemrograman fungsional, meskipun dapat digunakan untuk menulis kode pemrograman fungsional. Clojure yang merupakan dialek Lisp adalah bahasa pemrograman fungsional
sakisk
2
Saya setuju dengan @delnan. Pernyataan ini lebih terkait dengan bahasa pemrograman fungsional yang diketik secara statis, terutama Haskell yang menggunakan sistem Hindley-Milner. Saya pikir ide utamanya adalah jika Anda mendapatkan tipe yang benar, kepercayaan diri bahwa program Anda benar meningkat.
sakisk
1
Kode fungsional dapat memiliki sebanyak mungkin abstraksi dan tipuan seperti kode OOP umum Anda (jika tidak lebih). Iblis ada dalam perincian - lebih sedikit efek samping dan tidak ada nol berarti lebih sedikit keadaan tak terlihat untuk dilacak dan lebih sedikit peluang untuk gagal. Perhatikan bahwa Anda dapat menerapkan prinsip-prinsip yang sama dalam bahasa utama arus utama, itu hanya lebih banyak pekerjaan dan sering lebih bertele-tele (misalnya harus menampar finalsegalanya).
Doval
1
Bukan jawaban lengkap tetapi pengetikan program adalah bentuk verifikasi program formal. Secara umum program Berorientasi Objek memiliki sistem tipe yang rumit atau sangat sederhana karena substitusi perlu diperhitungkan - dalam kebanyakan kasus mereka dibuat tidak sehat demi kenyamanan. OTOH sistem seperti ML dapat menggunakan CH sepenuhnya sehingga Anda dapat menyandikan bukti hanya dalam jenis dan menggunakan kompiler sebagai pemeriksa bukti.
Maciej Piechotka

Jawaban:

12

Saya dapat menulis jawaban ini sebagai seseorang yang banyak membuktikan hal, jadi bagi saya kebenaran bukan hanya yang berhasil, tetapi yang berhasil dan mudah untuk dibuktikan.

Dalam banyak hal, pemrograman fungsional lebih membatasi daripada pemrograman imperatif. Lagi pula, tidak ada yang menghentikan Anda untuk tidak pernah mengubah variabel dalam C! Memang sebagian besar fitur dalam bahasa FP langsung untuk dibicarakan dalam hal hanya beberapa fitur inti. Itu semua cukup banyak bermuara pada lambdas, aplikasi fungsi, dan pencocokan pola!

Namun, karena kami telah membayar piper di muka, kami memiliki jauh lebih sedikit untuk ditangani dan kami memiliki jauh lebih sedikit opsi untuk bagaimana kesalahan bisa terjadi. Jika Anda penggemar tahun 1984, kebebasan memang perbudakan! Dengan menggunakan 101 trik yang berbeda untuk suatu program, kita harus mempertimbangkan berbagai hal seolah-olah 101 hal ini dapat terjadi! Itu benar-benar sulit dilakukan ternyata :)

Jika Anda memulai dengan gunting keselamatan alih-alih pedang, berlari tidak terlalu berbahaya.

Sekarang kami melihat pertanyaan Anda: bagaimana semua ini cocok dengan "itu mengkompilasi dan bekerja!" fenomena. Saya pikir sebagian besar dari ini adalah alasan yang sama seperti mengapa mudah untuk membuktikan kode! Lagi pula, ketika Anda menulis perangkat lunak, Anda membuat bukti informal bahwa itu benar. Karena hal ini, apa yang dicakup oleh bukti-bukti handwavy alami Anda dan para penyusun memiliki gagasan tentang kebenaran (pengetikan) cukup banyak.

Saat Anda menambahkan fitur dan interaksi yang rumit di antara mereka, apa yang tidak diperiksa oleh sistem tipe meningkat. Namun, kemampuan Anda untuk membangun bukti informal tampaknya tidak membaik! Ini berarti bahwa masih ada lagi yang bisa lolos dari pemeriksaan awal Anda dan harus ditangkap kemudian.

Daniel Gratzer
sumber
1
Saya suka jawaban Anda tetapi saya tidak melihat bagaimana ia menjawab pertanyaan OP
sakisk
1
@faif Memperluas jawaban saya. TLDR: semua orang adalah ahli matematika.
Daniel Gratzer
"Dengan menggunakan 101 trik rapi yang berbeda untuk suatu program, kita harus beralasan tentang hal-hal seolah-olah salah satu dari 101 hal ini dapat terjadi!": Saya membaca di suatu tempat bahwa Anda perlu menjadi jenius untuk memprogram dengan mutasi, karena Anda harus tetap begitu banyak informasi di kepala Anda.
Giorgio
12

alasan abstrak yang mendasari korelasi antara keberhasilan kompilasi dan kebenaran program dalam pemrograman fungsional?

Negara yang bisa berubah.

Compiler memeriksa hal-hal secara statis. Mereka memastikan program Anda terbentuk dengan baik, dan sistem tipe menyediakan mekanisme untuk mencoba memastikan bahwa jenis nilai yang tepat diperbolehkan di tempat yang tepat. Sistem tipe juga mencoba memastikan bahwa semantik yang tepat diizinkan di tempat yang tepat.

Segera setelah program Anda memperkenalkan keadaan, kendala yang terakhir menjadi kurang berguna. Anda tidak hanya perlu khawatir tentang nilai-nilai yang tepat di tempat yang tepat, tetapi Anda juga perlu memperhitungkan nilai yang berubah pada titik-titik acak program Anda. Anda harus memperhitungkan semantik kode Anda yang berubah di samping kondisi itu.

Jika Anda melakukan pemrograman fungsional dengan baik, tidak ada (atau sangat sedikit) keadaan bisa berubah.

Ada beberapa perdebatan tentang penyebabnya di sini - jika program tanpa kerja negara setelah kompilasi lebih sering karena kompiler dapat menangkap lebih banyak bug atau jika program tanpa kerja negara setelah kompilasi lebih sering karena gaya pemrograman menghasilkan lebih sedikit bug.

Ini mungkin campuran dari keduanya dalam pengalaman saya.

Telastyn
sumber
2
"Itu mungkin campuran dari keduanya dalam pengalaman saya.": Saya memiliki pengalaman yang sama. Pengetikan statis menangkap kesalahan pada waktu kompilasi juga ketika menggunakan bahasa imperatif (misalnya Pascal). Dalam FP, penghindaran mutabilitas dan, saya akan menambahkan, penggunaan gaya pemrograman yang lebih deklaratif membuatnya lebih mudah untuk alasan tentang kode. Jika suatu bahasa menawarkan keduanya, Anda mendapatkan keduanya.
Giorgio
7

Sederhananya, pembatasan berarti ada lebih sedikit cara yang benar untuk menyatukan segala sesuatunya, dan fungsi kelas satu memudahkan untuk memfaktorkan hal-hal seperti struktur loop. Ambil loop dari jawaban ini , misalnya:

for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        iterator.remove();
    }
}

Ini merupakan cara imperatif yang aman di Jawa untuk menghapus elemen dari koleksi saat Anda mengulanginya. Ada banyak cara yang terlihat sangat dekat, tetapi salah. Orang-orang yang tidak mengetahui metode ini kadang-kadang pergi melalui cara berbelit-belit untuk menghindari masalah, seperti iterasi melalui salinan.

Ini tidak terlalu sulit untuk membuat generik ini, jadi ini akan berfungsi pada lebih dari sekedar koleksi Strings, tetapi tanpa fungsi kelas satu, Anda tidak dapat mengganti predikat (kondisi di dalam if), sehingga kode ini cenderung disalin dan ditempelkan dan sedikit dimodifikasi.

Gabungkan fungsi-fungsi kelas satu yang memberi Anda kemampuan untuk lulus predikat sebagai parameter, dengan pembatasan kekekalan yang membuatnya sangat menjengkelkan jika Anda tidak melakukannya, dan Anda menghasilkan blok bangunan sederhana seperti filter, seperti dalam kode Scala ini itu melakukan hal yang sama:

list filter (!_.isEmpty)

Sekarang pikirkan tentang apa tipe sistem memeriksa untuk Anda, pada waktu kompilasi dalam kasus Scala, tetapi pemeriksaan ini juga dilakukan oleh sistem tipe dinamis saat pertama kali Anda menjalankannya:

  • listharus semacam jenis yang mendukung filtermetode, yaitu koleksi.
  • Elemen-elemen listharus memiliki isEmptymetode yang mengembalikan boolean.
  • Output akan menjadi koleksi (berpotensi) lebih kecil dengan jenis elemen yang sama.

Setelah hal-hal itu diperiksa, cara-cara lain apa yang tersisa bagi programmer untuk gagal? Saya tidak sengaja lupa !, yang menyebabkan kegagalan test case yang sangat jelas. Itu satu-satunya kesalahan yang tersedia untuk dibuat, dan saya hanya membuatnya karena saya langsung menerjemahkan dari kode yang diuji untuk kondisi terbalik.

Pola ini terus berulang. Fungsi kelas satu memungkinkan Anda merombak berbagai hal menjadi utilitas kecil yang dapat digunakan kembali dengan semantik yang tepat, pembatasan seperti immutability memberi Anda dorongan untuk melakukannya, dan mengetik memeriksa parameter dari utilitas tersebut menyisakan sedikit ruang untuk mengacaukannya.

Tentu saja, ini semua tergantung pada programmer yang mengetahui bahwa fungsi penyederhanaan seperti filtersudah ada, dan dapat menemukannya, atau mengenali manfaat dari membuat sendiri. Cobalah untuk menerapkan ini sendiri di mana-mana hanya menggunakan rekursi ekor, dan Anda segera kembali ke kapal kompleksitas yang sama dengan versi imperatif, hanya lebih buruk. Hanya karena Anda dapat menulisnya dengan sangat sederhana, bukan berarti versi sederhananya jelas.

Karl Bielefeldt
sumber
"Setelah hal-hal itu diperiksa, cara lain apa yang tersisa bagi programmer untuk mengacaukannya?": Ini entah bagaimana menegaskan pengalaman saya bahwa (1) pengetikan statis + (2) gaya fungsional menyisakan lebih sedikit cara untuk mengacaukan sesuatu. Akibatnya saya cenderung mendapatkan program yang benar lebih cepat dan perlu menulis lebih sedikit unit test saat menggunakan FP.
Giorgio
2

Saya tidak berpikir ada korelasi yang signifikan antara kompilasi pemrograman fungsional dan kebenaran runtime. Mungkin ada beberapa korelasi antara kompilasi yang diketik secara statis dan kebenaran runtime, karena setidaknya Anda mungkin memiliki tipe yang tepat, jika Anda tidak melakukan casting.

Aspek bahasa pemrograman yang entah bagaimana dapat mengkorelasikan kompilasi yang berhasil dengan kebenaran jenis runtime, seperti yang Anda jelaskan, adalah pengetikan statis, dan bahkan kemudian, hanya jika Anda tidak melemahkan pemeriksa tipe dengan gips yang hanya dapat ditegaskan saat runtime (dalam lingkungan dengan nilai atau tempat yang diketik dengan kuat, mis. Java atau .Net) atau tidak sama sekali (di lingkungan di mana informasi jenis hilang atau dengan pengetikan yang lemah, mis. C dan C ++).

Namun, pemrograman fungsional sendiri dapat membantu dengan cara lain, seperti menghindari data bersama dan keadaan yang bisa berubah.

Kedua aspek bersama-sama mungkin memiliki korelasi yang signifikan dalam kebenaran, tetapi Anda harus menyadari bahwa tidak memiliki kesalahan kompilasi dan runtime mengatakan apa-apa, secara tegas, tentang kebenaran dalam arti yang lebih luas, seperti dalam program melakukan apa yang seharusnya dilakukan dan gagal dengan cepat lebih dari input tidak valid atau kegagalan runtime yang tidak terkendali. Untuk itu, Anda memerlukan aturan bisnis, persyaratan, kasus penggunaan, pernyataan, tes unit, tes integrasi, dll. Pada akhirnya, setidaknya menurut saya, mereka memberikan kepercayaan diri yang jauh lebih baik daripada pemrograman fungsional, pengetikan statis atau keduanya.

acelent
sumber
Ini. Kebenaran suatu program tidak dapat dinilai dari kompilasi yang berhasil. Jika seorang kompiler dapat memahami persyaratan yang seringkali saling bertentangan dan tidak akurat dari setiap orang yang berkontribusi terhadap spesifikasi program, maka mungkin kompilasi yang berhasil dapat dianggap benar. Tetapi kompiler mitos itu tidak membutuhkan seorang programmer! Walaupun mungkin ada korelasi keseluruhan yang sedikit lebih tinggi antara kompilasi dan kebenaran untuk program fungsional versus program imperatif, itu adalah bagian kecil dari penilaian kebenaran total yang saya pikir pada dasarnya tidak relevan
Jordan Rieger
2

Penjelasan untuk manajer:

Program fungsional seperti sebuah mesin besar di mana semuanya terhubung, tabung, kabel. [Mobil]

Program prosedural seperti bangunan dengan ruangan yang berisi mesin kecil, menyimpan produk parsial dalam nampan, mendapatkan produk parsial dari tempat lain. [Sebuah pabrik]

Jadi ketika mesin fungsional sudah cocok: ia terikat untuk menghasilkan sesuatu. Jika kompleks prosedural berjalan, Anda mungkin mengawasi efek tertentu, menimbulkan kekacauan, tidak menjamin fungsinya. Bahkan jika Anda memiliki daftar periksa dari segala sesuatu yang terintegrasi dengan benar, ada begitu banyak keadaan, situasi yang mungkin terjadi (produk parsial tergeletak, ember meluap, hilang), sehingga jaminan sulit untuk diberikan.


Tetapi serius, kode prosedural tidak menentukan semantik dari hasil yang diinginkan sebanyak kode fungsional. Pemrogram prosedural mungkin lebih mudah lolos dengan kode dan data tidak langsung, dan memperkenalkan beberapa cara untuk melakukan satu hal (beberapa di antaranya tidak sempurna). Biasanya data asing dibuat. Pemrogram fungsional mungkin membutuhkan waktu lebih lama ketika masalahnya menjadi lebih kompleks?

Bahasa fungsional yang diketik kuat masih dapat melakukan data dan analisis aliran yang lebih baik. Dengan bahasa prosedural, tujuan suatu program sering harus didefinisikan di luar program, sebagai analisis kebenaran formal.

Joop Eggen
sumber
1
Anologi yang lebih baik: Pemrograman fungsional seperti meja bantuan tanpa pelanggan - semuanya indah (selama Anda tidak mempertanyakan tujuan atau efisiensi).
Brendan
@Brendan mobil & pabrik tidak membentuk analogi yang buruk. Ia mencoba menjelaskan mengapa program (skala kecil) dalam bahasa fungsional lebih mungkin untuk bekerja dan lebih sedikit kesalahan daripada sebuah "pabrik." Tetapi untuk penyelamatan katakanlah OOP datang bahwa pabrik dapat menghasilkan beberapa hal dan lebih besar. Perbandingan Anda tepat; seberapa sering seseorang mendengar FP dapat memparalelkan dan mengoptimalkan, tetapi efeknya (tidak ada kata-kata) memberikan hasil yang lambat. Saya masih memegang FP.
Joop Eggen
Pemrograman fungsional pada skala berfungsi dengan baik untuk en.wikipedia.org/wiki/Spherical_cow. Tetap lokal.
Den
@Den Saya sendiri akan takut tidak ada masalah kelayakan bekerja pada proyek FP skala besar. Bahkan menyukainya. Generalisasi memiliki keterbatasan. Tetapi karena pernyataan terakhir itu adalah generalisasi juga ... (terima kasih untuk sapi berbentuk bola)
Joop Eggen