Pada halaman 839 edisi kedua, Steve McConnell sedang mendiskusikan semua cara yang bisa dilakukan "programmer menaklukkan kompleksitas" dalam program besar. Kiatnya berujung pada pernyataan ini:
"Pemrograman berorientasi objek menyediakan tingkat abstraksi yang berlaku untuk algoritma dan data pada saat yang sama , semacam abstraksi yang tidak disediakan oleh dekomposisi fungsional."
Ditambah dengan kesimpulannya bahwa "mengurangi kompleksitas bisa dibilang kunci terpenting untuk menjadi programmer yang efektif" (halaman yang sama), ini tampaknya cukup banyak tantangan untuk pemrograman fungsional.
Perdebatan antara FP dan OO sering dibingkai oleh para pendukung FP tentang masalah kompleksitas yang berasal secara khusus dari tantangan konkurensi atau paralelisasi. Tetapi concurrency tentu saja bukan satu-satunya jenis kerumitan yang perlu ditaklukkan oleh pemrogram perangkat lunak. Mungkin berfokus pada pengurangan satu jenis kompleksitas akan sangat meningkatkannya di dimensi lain, sehingga bagi banyak kasus, keuntungannya tidak sebanding dengan biayanya.
Jika kita menggeser ketentuan perbandingan antara FP dan OO dari isu-isu tertentu seperti konkurensi atau penggunaan kembali ke pengelolaan kompleksitas global, bagaimana perdebatan itu terlihat?
EDIT
Kontras yang ingin saya soroti adalah bahwa OO tampaknya merangkum dan abstrak jauh dari kompleksitas data dan algoritma, sedangkan pemrograman fungsional tampaknya mendorong meninggalkan detail implementasi struktur data yang lebih "terbuka" di seluruh program.
Lihat, misalnya, Stuart Halloway (pendukung Clojure FP) di sini mengatakan bahwa "spesifikasi berlebih dari tipe data" adalah "konsekuensi negatif dari gaya OO idiomatik" dan lebih memilih untuk mengonseptualisasikan Buku Alamat sebagai vektor atau peta sederhana alih-alih objek OO yang lebih kaya. dengan properti dan metode tambahan (non-vektor & non-maplike). (Juga, para pendukung OO dan Desain Berbasis Domain dapat mengatakan bahwa mengekspos Buku Alamat sebagai vektor atau peta mengekspos data enkapsulasi dengan metode yang tidak relevan atau bahkan berbahaya dari sudut pandang domain).
Jawaban:
Ingatlah bahwa buku itu ditulis lebih dari 20 tahun. Bagi programmer profesional saat itu, FP tidak ada - itu sepenuhnya berada di ranah akademisi & peneliti.
Kita perlu membingkai "dekomposisi fungsional" dalam konteks pekerjaan yang tepat. Penulis tidak mengacu pada pemrograman fungsional. Kita perlu mengikat ini kembali ke "pemrograman terstruktur" dan
GOTO
kekacauan penuh yang datang sebelumnya. Jika titik referensi Anda adalah FORTRAN / COBOL / BASIC lama yang tidak memiliki fungsi (mungkin, jika Anda beruntung Anda akan mendapatkan satu tingkat GOSUB) dan semua variabel Anda bersifat global, dapat memecah program Anda ke dalam lapisan fungsi adalah anugerah utama.OOP adalah penyempurnaan lebih lanjut pada 'dekomposisi fungsional' semacam ini. Anda tidak hanya dapat menggabungkan instruksi bersama dalam fungsi-fungsi tetapi Anda juga dapat mengelompokkan fungsi - fungsi terkait dengan data yang sedang mereka kerjakan. Hasilnya adalah potongan kode yang jelas yang dapat Anda lihat dan pahami (idealnya) tanpa harus mengejar seluruh basis kode Anda untuk menemukan apa lagi yang mungkin beroperasi pada data Anda.
sumber
Saya membayangkan pendukung pemrograman fungsional akan berpendapat bahwa sebagian besar bahasa FP menyediakan lebih banyak sarana abstraksi daripada "dekomposisi fungsional saja" dan memang memungkinkan sarana abstraksi yang sebanding dalam kekuasaan dengan yang dari Bahasa Berorientasi Objek. Sebagai contoh, seseorang dapat mengutip kelas tipe Haskell atau modul tingkat tinggi ML sebagai sarana abstraksi. Jadi pernyataan (yang saya cukup yakin adalah tentang orientasi objek vs pemrograman prosedural, bukan pemrograman fungsional) tidak berlaku untuk mereka.
Juga harus ditunjukkan bahwa FP dan OOP adalah konsep ortogonal dan tidak saling eksklusif. Jadi tidak masuk akal untuk membandingkan mereka satu sama lain. Anda bisa membandingkan "imperative OOP" (mis. Java) vs. "fungsional OOP" (mis. Scala), tetapi pernyataan yang Anda kutip tidak berlaku untuk perbandingan itu.
sumber
Saya menemukan pemrograman fungsional sangat membantu dalam mengelola kompleksitas. Anda cenderung berpikir tentang kompleksitas dengan cara yang berbeda, mendefinisikannya sebagai fungsi yang bekerja pada data yang tidak dapat diubah pada tingkat yang berbeda daripada enkapsulasi dalam arti OOP.
Sebagai contoh, saya baru-baru ini menulis sebuah game di Clojure, dan seluruh kondisi permainan didefinisikan dalam struktur data tunggal yang tidak dapat diubah:
Dan loop game utama dapat didefinisikan sebagai menerapkan beberapa fungsi murni ke status game dalam satu loop:
Fungsi utama yang dipanggil adalah
update-game
, yang menjalankan langkah simulasi mengingat kondisi permainan sebelumnya dan beberapa input pengguna, dan mengembalikan status permainan baru.Jadi di mana kompleksitasnya? Dalam pandangan saya itu telah dikelola dengan cukup baik:
OOP juga dapat mengelola kompleksitas melalui enkapsulasi, tetapi jika Anda membandingkannya dengan OOP, fungsional memiliki beberapa keuntungan yang sangat besar:
Akhirnya, bagi orang-orang yang tertarik pada wawasan lebih lanjut tentang cara mengelola kompleksitas dalam bahasa fungsional vs. bahasa OOP, saya sangat merekomendasikan video pidato utama Rich Hickey, Simple Made Easy (difilmkan pada konferensi teknologi Strange Loop )
sumber
Dekomposisi fungsional saja tidak cukup untuk membuat algoritma atau program apa pun: Anda perlu merepresentasikan data juga. Saya pikir pernyataan di atas secara implisit mengasumsikan (atau setidaknya dapat dipahami seperti) bahwa "data" dalam kasus fungsional adalah jenis yang paling dasar: hanya daftar simbol dan tidak ada yang lain. Memprogram dalam bahasa seperti itu jelas sangat tidak nyaman. Namun, banyak, terutama bahasa baru dan modern, fungsional (atau multi-paradigma), seperti Clojure, menawarkan struktur data yang kaya: tidak hanya daftar, tetapi juga string, vektor, peta dan set, catatan, struct - dan objek! - dengan metadata dan polimorfisme.
Keberhasilan besar praktis dari abstraksi OO hampir tidak dapat diperdebatkan. Tapi apakah itu kata terakhir? Seperti yang Anda tulis, masalah konkurensi sudah menjadi masalah besar, dan OO klasik tidak mengandung gagasan konkurensi sama sekali. Akibatnya, solusi OO de facto untuk menangani konkurensi hanyalah lakban yang dilapiskan: berfungsi, tetapi mudah untuk dikacaukan, mengambil banyak sumber daya otak dari tugas penting yang ada, dan tidak dapat diukur dengan baik. Mungkin mungkin untuk mengambil yang terbaik dari banyak dunia. Itulah yang dikejar oleh bahasa multi-paradigma modern.
sumber
Keadaan berubah-ubah adalah akar dari sebagian besar kompleksitas dan masalah yang terkait dengan pemrograman dan perancangan perangkat lunak / sistem.
OO mencakup keadaan yang bisa berubah. FP membenci keadaan bisa berubah.
Baik OO dan FP memiliki kegunaan & sweet spot mereka. Pilihlah dengan bijak. Dan ingat pepatah: "Penutupan adalah benda orang miskin. Objek adalah penutupan orang miskin."
sumber
Pemrograman Fungsional dapat memiliki objek, tetapi objek tersebut cenderung tidak berubah. Fungsi murni (fungsi tanpa efek samping) kemudian beroperasi pada struktur data tersebut. Dimungkinkan untuk membuat objek yang tidak berubah dalam bahasa pemrograman berorientasi objek, tetapi mereka tidak dirancang untuk melakukannya dan itu bukan cara mereka cenderung digunakan. Ini membuatnya sulit untuk beralasan tentang program berorientasi objek.
Mari kita ambil contoh yang sangat sederhana. Katakanlah Oracle memutuskan bahwa Java Strings harus memiliki metode terbalik dan Anda menulis kode berikut.
apa yang baris terakhir evaluasi? Anda memerlukan pengetahuan khusus tentang kelas String untuk mengetahui bahwa ini akan bernilai false.
Bagaimana jika saya membuat WuHoString kelas saya sendiri
Tidak mungkin untuk mengetahui apa yang dinilai oleh baris terakhir.
Dalam gaya Pemrograman Fungsional akan ditulis lebih sebagai berikut:
dan itu harus benar.
Jika 1 fungsi di salah satu kelas paling dasar sangat sulit untuk dipikirkan, maka orang bertanya-tanya apakah memperkenalkan ide objek yang bisa berubah ini telah menambah atau mengurangi kompleksitas.
Jelas ada segala macam definisi tentang apa yang merupakan berorientasi objek dan apa artinya menjadi fungsional dan apa artinya memiliki keduanya. Bagi saya Anda dapat memiliki "gaya pemrograman fungsional" dalam bahasa yang tidak memiliki hal-hal seperti fungsi kelas satu tetapi bahasa lain dibuat untuk itu.
sumber
Saya pikir dalam banyak kasus abstraksi OOP klasik tidak mencakup kompleksitas konkurensi. Oleh karena itu OOP (dengan makna aslinya) tidak mengecualikan FP, dan itulah mengapa kita melihat hal-hal seperti scala.
sumber
Jawabannya tergantung pada bahasanya. Lisps, misalnya, memiliki benar-benar rapi benar bahwa kode adalah data - algoritma Anda menulis sebenarnya hanya Lisp daftar! Anda menyimpan data dengan cara yang sama seperti Anda menulis program. Abstraksi ini secara simultan lebih sederhana dan lebih teliti daripada OOP dan memungkinkan Anda melakukan beberapa hal yang sangat rapi (lihat makro).
Haskell (dan bahasa yang serupa, saya bayangkan) memiliki jawaban yang sama sekali berbeda: tipe data aljabar. Tipe data aljabar seperti
C
struct, tetapi dengan lebih banyak opsi. Tipe data ini menyediakan abstraksi yang dibutuhkan untuk memodelkan data; fungsi menyediakan abstraksi yang diperlukan untuk memodelkan algoritma. Jenis kelas dan fitur canggih lainnya memberikan bahkan lebih tinggi tingkat abstraksi lebih baik.Sebagai contoh, saya sedang mengerjakan bahasa pemrograman yang disebut TPL untuk bersenang-senang. Tipe data aljabar membuatnya sangat mudah untuk mewakili nilai:
Apa yang dikatakan ini - dengan cara yang sangat visual - adalah bahwa TPLValue (nilai apa pun dalam bahasa saya) dapat berupa a
Null
atau aNumber
denganInteger
nilai atau bahkanFunction
dengan daftar nilai (parameter) dan nilai akhir (badan ).Selanjutnya saya bisa menggunakan kelas tipe untuk menyandikan beberapa perilaku umum. Sebagai contoh, saya bisa membuat
TPLValue
dan contohShow
yang artinya dapat dikonversi ke string.Selain itu, saya bisa menggunakan kelas tipe saya sendiri ketika saya perlu menentukan perilaku tipe tertentu (termasuk yang saya tidak menerapkan sendiri). Sebagai contoh, saya memiliki
Extractable
kelas tipe yang memungkinkan saya menulis fungsi yang mengambilTPLValue
dan mengembalikan nilai normal yang sesuai. Dengan demikianextract
dapat mengkonversi aNumber
keInteger
atauString
keString
selamaInteger
danString
merupakan contohExtractable
.Akhirnya, logika utama program saya adalah dalam beberapa fungsi seperti
eval
danapply
. Ini benar-benar inti - mereka mengambilTPLValue
dan mengubahnya menjadi lebih banyakTPLValue
, serta menangani keadaan dan kesalahan.Secara keseluruhan, abstraksi yang saya gunakan dalam kode Haskell saya sebenarnya lebih kuat daripada apa yang akan saya gunakan dalam bahasa OOP.
sumber
eval
. "Hei, lihat aku! Aku tidak perlu menulis lubang keamananku sendiri; Aku punya kerentanan eksekusi kode arbitrer yang dibangun langsung ke dalam bahasa pemrograman!" Penggabungan data dengan kode adalah akar penyebab salah satu dari dua kelas kerentanan keamanan paling populer sepanjang masa. Setiap kali Anda melihat seseorang diretas karena serangan injeksi SQL (di antara banyak hal lain) itu karena beberapa programmer di luar sana tidak tahu cara memisahkan data dari kode dengan benar.eval
tidak terlalu bergantung pada struktur Lisp - Anda dapat memilikieval
dalam bahasa seperti JavaScript dan Python. Kekuatan sebenarnya datang dalam penulisan makro, yang pada dasarnya adalah program yang bekerja pada program seperti data dan output program lain. Ini membuat bahasa ini sangat fleksibel dan membuat abstraksi yang kuat menjadi mudah.and
. hubungan arus pendekor
.let
.let-rec
.cond
.defn
. Tak satu pun dari ini dapat diimplementasikan dengan fungsi dalam bahasa urutan aplikatif.for
(daftar pemahaman).dotimes
.doto
.and
!" Saya mendengar, "hei lihat saya, bahasa saya sangat lumpuh sehingga tidak datang dengan korsletingand
dan saya harus menemukan kembali roda untuk semuanya !"Kalimat yang dikutip tidak memiliki validitas lagi, sejauh yang saya bisa lihat.
Bahasa OO kontemporer tidak dapat abstrak di atas jenis yang jenisnya tidak *, yaitu jenis dengan jenis yang lebih tinggi tidak diketahui. Sistem tipe mereka tidak memungkinkan untuk mengekspresikan ide "beberapa wadah dengan elemen Int, yang memungkinkan untuk memetakan fungsi atas elemen".
Karenanya, fungsi dasar ini seperti Haskells
tidak dapat ditulis dengan mudah di Java *), misalnya, setidaknya tidak dengan cara yang aman. Oleh karena itu, untuk mendapatkan fungsionalitas dasar, Anda harus menulis banyak boilerplate, karena Anda memerlukannya
Namun, kelima metode tersebut pada dasarnya adalah kode yang sama, memberi atau mengambil beberapa. Sebaliknya, di Haskell, saya perlu:
Perhatikan bahwa ini tidak akan berubah dengan Java 8 (hanya saja orang dapat menerapkan fungsi lebih mudah, tetapi kemudian, justru, masalah di atas akan terwujud. Selama Anda bahkan tidak memiliki fungsi urutan yang lebih tinggi, kemungkinan besar Anda bahkan tidak dapat memahami jenis jenis apa yang lebih baik.)
Bahkan bahasa OO baru seperti Ceylon tidak memiliki jenis yang lebih tinggi. (Saya telah bertanya kepada Gavin King belakangan ini, dan dia mengatakan kepada saya, itu tidak penting saat ini.) Namun, tidak tahu tentang Kotlin.
*) Agar adil, Anda dapat memiliki Functor antarmuka yang memiliki metode fmap. Yang buruk adalah, Anda tidak bisa mengatakan: Hei, saya tahu bagaimana menerapkan fmap untuk kelas perpustakaan SuperConcurrentBlockedDoublyLinkedDequeHasMap, kompiler yang terhormat, terima bahwa mulai sekarang, semua SuperConcurrentBlockedDoublyLinkedDequeHasMaps adalah Functors.
sumber
Siapa pun yang pernah diprogram dalam dBase akan tahu betapa bermanfaatnya makro baris tunggal untuk membuat kode yang dapat digunakan kembali. Meskipun saya belum memprogram dalam Lisp, saya telah membaca dari banyak orang lain yang bersumpah dengan menyusun waktu makro. Gagasan menyuntikkan kode ke dalam kode Anda pada waktu kompilasi digunakan dalam bentuk sederhana di setiap program C dengan arahan "include". Karena Lisp dapat melakukan ini dengan program Lisp dan karena Lisp sangat reflektif, Anda mendapatkan lebih banyak termasuk fleksibel.
Setiap programmer yang hanya akan mengambil string teks sewenang-wenang dari web dan meneruskannya ke database mereka bukan seorang programmer. Demikian juga, siapa pun yang memungkinkan data "pengguna" secara otomatis menjadi kode yang dapat dieksekusi jelas bodoh. Itu tidak berarti bahwa mengizinkan program untuk memanipulasi data pada waktu eksekusi dan kemudian menjalankannya sebagai kode adalah ide yang buruk. Saya percaya bahwa teknik ini akan sangat diperlukan di masa depan yang akan memiliki kode "pintar" yang sebenarnya menulis sebagian besar program. Seluruh "masalah data / kode" atau tidak adalah masalah keamanan dalam bahasa.
Salah satu masalah dengan sebagian besar bahasa adalah bahwa mereka dibuat untuk satu orang offline untuk menjalankan beberapa fungsi untuk diri mereka sendiri. Program dunia nyata mengharuskan banyak orang memiliki akses setiap saat dan pada saat yang sama dari banyak Core dan beberapa cluster komputer. Keamanan harus menjadi bagian dari bahasa daripada sistem operasi dan dalam waktu yang tidak terlalu lama itu akan menjadi.
sumber