Pada banyak artikel, menggambarkan manfaat pemrograman fungsional, saya telah melihat bahasa pemrograman fungsional, seperti Haskell, ML, Scala atau Clojure, disebut sebagai "bahasa deklaratif" yang berbeda dari bahasa imperatif seperti C / C ++ / C # / Java. Pertanyaan saya adalah apa yang membuat bahasa pemrograman fungsional deklaratif dan bukan imperatif.
Penjelasan yang sering dijumpai menggambarkan perbedaan antara pemrograman Deklaratif dan Imperatif adalah bahwa dalam pemrograman Imperatif Anda memberi tahu komputer "Cara melakukan sesuatu" yang bertentangan dengan "Apa yang harus dilakukan" dalam bahasa deklaratif. Masalah yang saya miliki dengan penjelasan ini adalah bahwa Anda terus melakukan keduanya dalam semua bahasa pemrograman. Bahkan jika Anda turun ke unit level terendah Anda masih memberi tahu komputer "Apa yang harus dilakukan", Anda memberi tahu CPU untuk menambahkan dua angka, Anda tidak menginstruksikannya tentang bagaimana melakukan penambahan. Jika kita pergi ke ujung lain spektrum, bahasa fungsional murni tingkat tinggi seperti Haskell, Anda sebenarnya memberi tahu komputer cara mencapai tugas tertentu, itulah yang program Anda adalah urutan instruksi untuk mencapai tugas tertentu yang komputer tidak tahu bagaimana mencapainya sendirian. Saya mengerti bahwa bahasa-bahasa seperti Haskell, Clojure, dll. Jelas tingkat lebih tinggi dari C / C ++ / C # / Java dan menawarkan fitur-fitur seperti evaluasi malas, struktur data yang tidak dapat diubah, fungsi anonim, penjelajahan, struktur data persisten, dll. Semuanya membuat pemrograman fungsional mungkin dan efisien, tetapi saya tidak akan mengklasifikasikannya sebagai bahasa deklaratif.
Bahasa deklaratif murni bagi saya akan menjadi bahasa yang hanya terdiri dari deklarasi saja, contoh bahasa seperti itu adalah CSS (Ya saya tahu CSS secara teknis tidak akan menjadi bahasa pemrograman). CSS hanya berisi deklarasi gaya yang digunakan oleh HTML dan Javascript halaman. CSS tidak dapat melakukan hal lain selain membuat deklarasi, ia tidak dapat membuat fungsi kelas, yaitu fungsi yang menentukan gaya untuk ditampilkan berdasarkan beberapa parameter, Anda tidak dapat menjalankan skrip CSS, dll. Bagi saya itu menggambarkan bahasa deklaratif (perhatikan saya tidak mengatakan deklaratif bahasa pemrograman ).
Memperbarui:
Saya telah bermain-main dengan Prolog baru-baru ini dan bagi saya, Prolog adalah bahasa pemrograman terdekat dengan bahasa deklaratif penuh (Setidaknya menurut saya), jika itu bukan satu-satunya bahasa pemrograman deklaratif sepenuhnya. Untuk menguraikan pemrograman dalam Prolog dilakukan dengan membuat deklarasi yang menyatakan fakta (fungsi predikat yang mengembalikan true untuk input tertentu) atau aturan (fungsi predikat yang mengembalikan true untuk kondisi / pola tertentu berdasarkan input), aturan didefinisikan menggunakan teknik pencocokan pola. Untuk melakukan apa pun di prolog, Anda meminta basis pengetahuan dengan mengganti satu atau lebih input predikat dengan variabel dan prolog mencoba menemukan nilai untuk variabel yang berhasil dilakukan predikat.
Maksud saya adalah di prolog tidak ada instruksi penting, Anda pada dasarnya mengatakan (menyatakan) komputer apa yang ia ketahui dan kemudian bertanya (bertanya) tentang pengetahuan. Dalam bahasa pemrograman fungsional, Anda masih memberikan instruksi, misalnya mengambil nilai, memanggil fungsi X dan menambahkan 1 ke dalamnya, dll, bahkan jika Anda tidak secara langsung memanipulasi lokasi memori atau menulis langkah demi langkah perhitungan. Saya tidak akan mengatakan bahwa pemrograman dalam Haskell, ML, Scala atau Clojure adalah deklaratif dalam pengertian ini, meskipun saya mungkin salah. Adalah tepat, benar, deklaratif pemrograman fungsional murni dalam arti yang saya jelaskan di atas.
(let [x 1] (let [x (+ x 2)] (let [x (* x x)] x)))
(Semoga Anda mengerti itu, Clojure). Pertanyaan orisinal saya adalah apa yang membuat ini berbeda dari int inix = 1; x += 2; x *= x; return x;
Menurut saya sebagian besar sama.Jawaban:
Anda tampaknya menarik garis antara menyatakan sesuatu dan menginstruksikan mesin. Tidak ada pemisahan yang keras dan cepat. Karena mesin yang diinstruksikan dalam pemrograman imperatif tidak harus berupa perangkat keras fisik, ada banyak kebebasan untuk interpretasi. Hampir semuanya dapat dilihat sebagai program eksplisit untuk mesin abstrak yang tepat. Misalnya, Anda bisa melihat CSS sebagai bahasa tingkat tinggi untuk memprogram mesin yang terutama menyelesaikan penyeleksi dan menetapkan atribut objek DOM yang dipilih.
Pertanyaannya adalah apakah perspektif seperti itu masuk akal, dan sebaliknya, seberapa dekat urutan instruksi menyerupai deklarasi hasil yang dihitung. Untuk CSS, perspektif deklaratif jelas lebih bermanfaat. Bagi C, perspektif imperatif jelas dominan. Adapun bahasa seperti Haskell, well ...
Bahasa telah ditentukan, semantik konkret. Artinya, tentu saja orang dapat mengartikan program sebagai rantai operasi. Bahkan tidak perlu terlalu banyak usaha untuk memilih operasi primitif sehingga mereka memetakan dengan baik ke perangkat keras komoditas (inilah yang dilakukan mesin STG dan model lainnya).
Namun, cara program Haskell ditulis, mereka sering dapat secara masuk akal dibaca sebagai deskripsi dari hasil yang akan dihitung. Ambil, misalnya, program untuk menghitung jumlah faktorial N pertama:
Anda dapat menghapus ini dan membacanya sebagai urutan operasi STG, tetapi jauh lebih alami untuk membacanya sebagai deskripsi hasil (yang, saya pikir, definisi yang lebih berguna dari pemrograman deklaratif daripada "apa yang harus dihitung"): Hasilnya adalah jumlah produk
[1..i]
untuk semuai
= 0, ..., n. Dan itu jauh lebih deklaratif daripada hampir semua program atau fungsi C.sumber
map (sum_of_fac . read) (lines fileContent)
,. Tentu saja pada titik tertentu I / O ikut bermain, tetapi seperti yang saya katakan, ini sebuah rangkaian. Bagian dari program Haskell lebih penting, tentu saja, sama seperti C memiliki sintaks ekspresi agak deklaratif (x + y
, tidakload x; load y; add;
)Unit dasar dari program imperatif adalah pernyataan . Pernyataan dieksekusi karena efek sampingnya. Mereka mengubah keadaan yang mereka terima. Urutan pernyataan adalah urutan perintah, yang menunjukkan lakukan ini lalu lakukan itu. Pemrogram menentukan urutan yang tepat untuk melakukan perhitungan. Inilah yang dimaksud orang dengan memberi tahu komputer bagaimana cara melakukannya.
Unit dasar dari program deklaratif adalah ekspresi . Ekspresi tidak memiliki efek samping. Mereka menentukan hubungan antara input dan output, menciptakan output baru dan terpisah dari input mereka, daripada mengubah keadaan input mereka. Urutan ekspresi tidak ada artinya tanpa beberapa ekspresi mengandung menentukan hubungan di antara mereka. Pemrogram menentukan hubungan antara data, dan program menyimpulkan perintah untuk melakukan perhitungan dari hubungan tersebut. Inilah yang dimaksud orang dengan memberi tahu komputer apa yang harus dilakukan.
Bahasa imperatif memiliki ekspresi, tetapi cara utama mereka untuk menyelesaikan sesuatu adalah pernyataan. Demikian juga, bahasa deklaratif memiliki beberapa ekspresi dengan semantik yang mirip dengan pernyataan, seperti urutan monad di dalam
do
notasi Haskell , tetapi pada intinya, mereka adalah satu ekspresi besar. Hal ini memungkinkan pemula menulis kode yang tampak sangat imperatif, tetapi kekuatan sebenarnya dari bahasa tersebut datang ketika Anda lolos dari paradigma itu.sumber
foreach
.Karakteristik mendefinisikan nyata yang memisahkan deklaratif dari pemrograman imperatif adalah dalam gaya deklaratif Anda tidak memberikan instruksi berurutan; di level yang lebih rendah ya CPU beroperasi dengan cara ini, tetapi itu menjadi perhatian kompiler.
Anda menyarankan CSS adalah "bahasa deklaratif", saya tidak akan menyebutnya bahasa sama sekali. Ini adalah format struktur data, seperti JSON, XML, CSV, atau INI, hanya format untuk mendefinisikan data yang diketahui oleh seorang penafsir.
Beberapa efek samping yang menarik terjadi ketika Anda mengambil operator penugasan dari bahasa, dan ini adalah penyebab sebenarnya untuk kehilangan semua instruksi imperatif step1-step2-step3 dalam bahasa deklaratif.
Apa yang Anda lakukan dengan operator penugasan dalam suatu fungsi? Anda membuat langkah menengah. Itulah intinya, operator penugasan digunakan untuk mengubah data secara bertahap. Segera setelah Anda tidak lagi dapat mengubah data, Anda kehilangan semua langkah itu dan berakhir dengan:
Setiap fungsi hanya memiliki satu pernyataan, pernyataan ini menjadi deklarasi tunggal
Sekarang ada banyak cara untuk membuat pernyataan tunggal terlihat seperti banyak pernyataan, tapi itu hanya tipuan, misalnya:
1 + 3 + (2*4) + 8 - (7 / (8*3))
ini jelas pernyataan tunggal, tetapi jika Anda menuliskannya sebagai ...Ini dapat mengambil tampilan urutan operasi yang dapat lebih mudah bagi otak untuk mengenali dekomposisi yang diinginkan penulis bermaksud. Saya sering melakukan ini dalam C # dengan kode seperti ..
Ini adalah pernyataan tunggal, tetapi menunjukkan penampilan yang terurai menjadi banyak - perhatikan tidak ada tugas.
Perhatikan juga, dalam kedua metode di atas - cara kode sebenarnya dieksekusi tidak dalam urutan Anda segera membacanya; karena kode ini mengatakan apa yang harus dilakukan, tetapi tidak menentukan caranya . Perhatikan aritmatika sederhana di atas jelas tidak akan dieksekusi dalam urutan yang dituliskan dari kiri ke kanan, dan dalam contoh C # di atas ketiga metode tersebut sebenarnya semua masuk kembali dan tidak dieksekusi sampai selesai secara berurutan, kompilator sebenarnya menghasilkan kode yang akan melakukan apa pernyataan itu ingin , tetapi belum tentu bagaimana Anda menganggap.
Saya percaya membiasakan diri dengan pendekatan deklaratif-tanpa-langkah-langkah ini adalah bagian yang paling sulit dari semuanya; dan mengapa Haskell sangat rumit karena hanya sedikit bahasa yang benar-benar melarangnya seperti yang dilakukan Haskell. Anda harus mulai melakukan beberapa senam yang menarik untuk menyelesaikan beberapa hal yang biasanya Anda lakukan dengan bantuan variabel perantara, seperti penjumlahan.
Dalam bahasa deklaratif, jika Anda ingin variabel perantara melakukan sesuatu dengan - itu berarti meneruskannya sebagai parameter ke fungsi yang melakukan hal itu. Inilah sebabnya mengapa rekursi menjadi sangat penting.
sum xs = (head xs) + sum (tail xs)
Anda tidak dapat membuat
resultSum
variabel dan menambahkannya ketika Anda mengulanginyaxs
, Anda harus mengambil nilai pertama, dan menambahkannya ke jumlah penjumlahan dari segala sesuatu yang lain - dan untuk mengakses semua yang lain Anda harus meneruskanxs
ke suatu fungsitail
karena Anda tidak bisa hanya membuat variabel untuk xs dan pop off yang akan menjadi pendekatan yang sangat penting. (Ya saya tahu Anda bisa menggunakan destrukturisasi tetapi contoh ini dimaksudkan sebagai ilustrasi)sumber
x = 1 + 2
kemudianx = 3
danx = 4 - 1
. Pernyataan berurutan menentukan instruksi sehingga ketikax = (y = 1; z = 2 + y; return z;)
Anda tidak lagi memiliki sesuatu yang dapat ditempa, sesuatu yang dapat dikompilasi oleh kompiler dengan berbagai cara - lebih penting kompiler melakukan apa yang Anda perintahkan di sana, karena kompiler tidak dapat mengetahui semua efek samping dari instruksi Anda sehingga bisa ' t mengubahnya.Aku tahu aku terlambat ke pesta, tapi aku punya pencerahan hari lain jadi begini ...
Saya pikir komentar tentang fungsional, tidak berubah dan tidak ada efek samping meleset ketika menjelaskan perbedaan antara deklaratif vs imperatif atau menjelaskan apa arti pemrograman deklaratif. Juga, seperti yang Anda sebutkan dalam pertanyaan Anda, keseluruhan "apa yang harus dilakukan" vs "bagaimana melakukannya" terlalu kabur dan benar-benar tidak menjelaskan keduanya.
Mari kita ambil kode sederhana
a = b + c
sebagai dasar dan lihat pernyataan dalam beberapa bahasa berbeda untuk mendapatkan ide:Ketika kita menulis
a = b + c
dalam bahasa imperatif, seperti C, kita menempatkan saat nilaib + c
ke variabela
dan tidak lebih. Kami tidak membuat pernyataan mendasar tentang apaa
itu. Sebaliknya, kami hanya menjalankan langkah dalam suatu proses.Ketika kita menulis
a = b + c
dalam bahasa deklaratif, seperti Microsoft Excel, (ya, Excel adalah bahasa pemrograman dan mungkin yang paling deklaratif dari semuanya,) kami menyatakan hubungan antaraa
,b
danc
sedemikian rupa sehingga selalua
merupakan jumlah yang merupakan jumlah dari dua yang lainnya. Itu bukan langkah dalam suatu proses, itu adalah invarian, jaminan, pernyataan kebenaran.Bahasa fungsional juga deklaratif, tetapi hampir tidak sengaja. Dalam Haskel misalnya
a = b + c
juga menegaskan hubungan yang invarian, tetapi hanya karenab
danc
tidak dapat diubah.Jadi ya, ketika objek tidak berubah dan fungsi bebas efek samping, kode menjadi deklaratif (meskipun terlihat identik dengan kode imperatif), tetapi bukan itu intinya. Juga tidak menghindari tugas. Inti dari kode deklaratif adalah membuat pernyataan mendasar tentang hubungan.
sumber
Anda benar bahwa tidak ada perbedaan yang jelas antara memberi tahu komputer apa yang harus dilakukan dan bagaimana melakukannya.
Namun, di satu sisi spektrum Anda hampir secara eksklusif berpikir tentang cara memanipulasi memori. Yaitu, untuk menyelesaikan masalah, Anda mempresentasikannya ke komputer dalam bentuk seperti "atur lokasi memori ini ke x, kemudian atur lokasi memori itu ke y, lompat ke lokasi memori z ..." dan kemudian entah bagaimana akhirnya Anda memiliki hasil di beberapa lokasi memori lain.
Dalam bahasa yang dikelola seperti Java, C # dan sebagainya, Anda tidak memiliki akses langsung ke memori perangkat keras lagi. Programmer imperatif sekarang memusatkan perhatian pada variasi statis, referensi atau bidang instance kelas, yang semuanya dalam beberapa derajat absraksi untuk lokasi memori.
Dalam bahasa languga seperti Haskell, OTOH, memori benar-benar hilang. Ini tidak terjadi di
harus ada dua sel memori yang memegang argumen a dan b dan satu lagi yang memegang hasil antara y. Yang pasti, backend kompiler dapat memancarkan kode final yang bekerja dengan cara ini (dan dalam beberapa hal ia harus melakukannya selama arsitektur target adalah mesin v. Neumann).
Tetapi intinya adalah kita tidak perlu menginternalisasi arsitektur v. Neumann untuk memahami fungsi di atas. Kita juga tidak memerlukan komputer kontemporer untuk menjalankannya. Misalnya, akan mudah untuk menerjemahkan program dalam bahasa FP murni ke mesin hipotetis yang berfungsi di dasar kalkulus SKI. Sekarang coba hal yang sama dengan program C!
Ini tidak cukup kuat, IMHO. Bahkan program C hanyalah urutan deklarasi. Saya merasa kita harus memenuhi syarat deklarasi lebih lanjut. Sebagai contoh, apakah mereka memberi tahu kita apa sesuatu itu (deklaratif) atau apa yang dilakukannya (imperatif).
sumber