Saya berasal dari latar belakang berorientasi objek di mana saya telah belajar bahwa kelas atau setidaknya dapat digunakan untuk membuat lapisan abstraksi yang memungkinkan daur ulang kode yang mudah yang kemudian dapat digunakan untuk membuat objek atau digunakan dalam warisan.
Seperti misalnya saya dapat memiliki kelas hewan dan kemudian dari itu mewarisi kucing dan anjing dan sedemikian rupa sehingga semuanya mewarisi banyak sifat yang sama, dan dari sub-kelas tersebut saya kemudian dapat membuat objek yang dapat menentukan jenis hewan atau bahkan nama. itu.
Atau saya bisa menggunakan kelas untuk menentukan beberapa instance dari kode yang sama yang menangani atau berisi hal-hal yang sedikit berbeda; seperti node dalam pohon pencarian atau beberapa koneksi database yang berbeda dan apa yang tidak.
Saya baru saja pindah ke pemrograman fungsional, jadi saya mulai bertanya-tanya:
Bagaimana bahasa fungsional murni menangani hal-hal seperti itu? Yaitu, bahasa tanpa konsep kelas dan objek.
sumber
Jawaban:
Banyak bahasa fungsional memiliki sistem modul. (Banyak bahasa berorientasi objek juga, by the way.) Tetapi bahkan tanpa adanya satu, Anda dapat menggunakan fungsi sebagai modul.
JavaScript adalah contoh yang bagus. Dalam JavaScript, fungsi digunakan baik untuk mengimplementasikan modul dan bahkan enkapsulasi berorientasi objek. Dalam Skema, yang merupakan inspirasi utama untuk JavaScript, hanya ada fungsi. Fungsi digunakan untuk mengimplementasikan hampir semua hal: objek, modul (disebut unit dalam Racket), bahkan struktur data.
OTOH, Haskell dan keluarga ML memiliki sistem modul eksplisit.
Orientasi Objek adalah tentang abstraksi data. Itu dia. Modularitas, Warisan, Polimorfisme, bahkan keadaan yang bisa berubah adalah masalah ortogonal.
sumber
module
. Saya pikir sangat disayangkan bahwa Racket memiliki konsep yang disebutmodule
yang bukan modul, dan konsep yang merupakan modul tetapi tidak disebutmodule
. Ngomong-ngomong, Anda menulis: "unit semacam setengah antara ruang nama dan antarmuka OO". Nah, bukankah itu semacam definisi modul itu?map
dan unit yang tergantung pada penjilidanmap
berbeda karena modul harus merujuk padamap
penjilidan tertentu , seperti dari penjilidanracket/base
, sementara pengguna unit yang berbeda dapat memberikan definisi berbedamap
untuk unit tersebut.Sepertinya Anda mengajukan dua pertanyaan: "Bagaimana Anda bisa mencapai modularitas dalam bahasa fungsional?" yang telah dibahas dalam jawaban lain dan "bagaimana Anda bisa membuat abstraksi dalam bahasa fungsional?" yang akan saya jawab.
Dalam bahasa OO, Anda cenderung berkonsentrasi pada kata benda, "seekor binatang", "server surat", "garpu kebunnya", dll. Bahasa fungsional, sebaliknya, menekankan kata kerja, "berjalan", "untuk mengambil surat" , "untuk mendorong", dll.
Maka, tidak mengherankan bahwa abstraksi dalam bahasa fungsional cenderung lebih dari kata kerja atau operasi daripada hal-hal. Salah satu contoh yang selalu saya raih ketika saya mencoba menjelaskan ini adalah parsing. Dalam bahasa fungsional, cara yang baik untuk menulis parser adalah dengan menentukan tata bahasa dan kemudian menafsirkannya. Penerjemah menciptakan abstraksi atas proses penguraian.
Contoh konkret lain dari ini adalah proyek yang saya kerjakan belum lama ini. Saya sedang menulis database di Haskell. Saya punya satu 'bahasa tertanam' untuk menentukan operasi di level terendah; misalnya, itu memungkinkan saya untuk menulis dan membaca sesuatu dari media penyimpanan. Saya memiliki 'bahasa tertanam' lain yang terpisah untuk menentukan operasi di level tertinggi. Lalu saya punya, apa yang pada dasarnya adalah juru bahasa, untuk mengubah operasi dari level yang lebih tinggi ke level yang lebih rendah.
Ini adalah bentuk abstraksi yang sangat umum, tetapi ini bukan satu-satunya yang tersedia dalam bahasa fungsional.
sumber
Meskipun "pemrograman fungsional" tidak membawa implikasi yang luas untuk masalah modularitas, bahasa-bahasa tertentu membahas pemrograman dalam cara yang berbeda. Penggunaan ulang dan abstraksi kode berinteraksi dalam semakin sedikit Anda mengekspos semakin sulit untuk menggunakan kembali kode. Mengesampingkan abstraksi, saya akan membahas dua masalah penggunaan kembali.
Bahasa OOP yang diketik secara statis biasanya menggunakan subtyping nominal, yang berarti bahwa kode yang dirancang untuk kelas / modul / antarmuka A hanya dapat menangani kelas / modul / antarmuka B ketika B secara eksplisit menyebutkan A. Bahasa dalam keluarga pemrograman fungsional terutama menggunakan subtyping struktural, yang berarti kode yang dirancang untuk A dapat menangani B setiap kali B memiliki semua metode dan / atau bidang A. B dapat dibuat oleh tim yang berbeda sebelum ada kebutuhan untuk kelas / antarmuka yang lebih umum A. Misalnya dalam OCaml, subtyping struktural berlaku untuk sistem modul, sistem objek mirip OOP, dan jenis varian polimorfiknya yang cukup unik.
Perbedaan paling menonjol antara OOP dan FP wrt. modularitas adalah bahwa "unit" default dalam bundel OOP bersama sebagai objek berbagai operasi pada nilai kasus yang sama, sedangkan "unit" default dalam bundel FP bersama sebagai fungsi operasi yang sama untuk berbagai kasus nilai. Dalam FP masih sangat mudah untuk menggabungkan operasi bersama, misalnya sebagai modul. (BTW, baik Haskell maupun F # tidak memiliki sistem modul keluarga lengkap.) Masalah Ekspresiadalah tugas menambahkan secara bertahap kedua operasi baru yang bekerja pada semua nilai (mis. melampirkan metode baru ke objek yang ada), dan kasus nilai baru yang harus didukung oleh semua operasi (misalnya menambahkan kelas baru dengan antarmuka yang sama). Seperti dibahas dalam ceramah Ralf Laemmel pertama di bawah ini (yang memiliki contoh luas dalam C #), menambahkan operasi baru bermasalah dalam bahasa OOP.
Kombinasi OOP dan FP dalam Scala mungkin menjadikannya salah satu bahasa yang paling kuat wrt. modularitas. Tetapi OCaml masih merupakan bahasa favorit saya dan menurut pendapat pribadi saya, subyektif itu tidak kalah dengan Scala. Dua kuliah Ralf Laemmel di bawah ini membahas solusi untuk masalah ekspresi di Haskell. Saya pikir solusi ini, meskipun berfungsi dengan baik, membuatnya sulit untuk menggunakan data yang dihasilkan dengan polimorfisme parametrik. Memecahkan masalah ekspresi dengan varian polimorfik di OCaml, dijelaskan dalam artikel Jaques Garrigue yang ditautkan di bawah, tidak memiliki kekurangan ini. Saya juga menautkan ke bab buku teks yang membandingkan penggunaan modularitas non-OOP dan OOP di OCaml.
Di bawah ini adalah tautan spesifik Haskell dan OCaml yang berkembang pada Masalah Ekspresi :
sumber
Sebenarnya, kode OO jauh lebih tidak dapat digunakan kembali, dan itu adalah desain. Gagasan di balik OOP adalah untuk membatasi operasi pada potongan data tertentu ke kode istimewa tertentu yang ada di kelas atau di tempat yang sesuai dalam hierarki warisan. Ini membatasi efek buruk dari mutabilitas. Jika struktur data berubah, hanya ada begitu banyak tempat dalam kode yang dapat bertanggung jawab.
Dengan kekekalan, Anda tidak peduli siapa yang dapat beroperasi pada struktur data apa pun, karena tidak ada yang dapat mengubah salinan data Anda. Ini membuat membuat fungsi-fungsi baru untuk bekerja pada struktur data yang ada jauh lebih mudah. Anda cukup membuat fungsi dan mengelompokkannya ke dalam modul yang tampaknya sesuai dari sudut pandang domain. Anda tidak perlu khawatir tentang di mana menempatkan mereka ke dalam hierarki warisan.
Jenis lain penggunaan kembali kode adalah menciptakan struktur data baru untuk bekerja pada fungsi yang ada. Ini ditangani dalam bahasa fungsional menggunakan fitur seperti generik dan kelas tipe. Sebagai contoh, kelas jenis Ord Haskell memungkinkan Anda untuk menggunakan
sort
fungsi pada jenis apa pun dengan sebuahOrd
instance. Contoh mudah dibuat jika belum ada.Ambil
Animal
contoh Anda , dan pertimbangkan menerapkan fitur pemberian makan. Implementasi OOP langsung adalah untuk mempertahankan koleksiAnimal
objek, dan loop melalui semuanya, memanggilfeed
metode pada masing-masing.Namun, banyak hal menjadi rumit ketika Anda sampai ke detail. Suatu
Animal
objek secara alami tahu jenis makanan apa yang dimakannya, dan berapa banyak yang dibutuhkan agar merasa kenyang. Itu tidak secara alami tahu di mana makanan disimpan dan berapa banyak tersedia, sehingga suatuFoodStore
objek baru saja menjadi ketergantungan setiapAnimal
, baik sebagai bidangAnimal
objek, atau diteruskan sebagai parameterfeed
metode. Secara bergantian, untuk menjaga agarAnimal
kelas lebih kohesif, Anda mungkin pindahfeed(animal)
keFoodStore
objek, atau Anda dapat membuat kekejian dari kelas yang disebutAnimalFeeder
atau semacamnya.Dalam FP, tidak ada kecenderungan untuk bidang
Animal
untuk selalu tetap dikelompokkan bersama, yang memiliki beberapa implikasi menarik untuk dapat digunakan kembali. Katakanlah Anda memiliki daftarAnimal
catatan, dengan bidang sepertiname
,species
,location
,food type
,food amount
, dll Anda juga memiliki daftarFoodStore
catatan dengan bidang-bidang sepertilocation
,food type
, danfood amount
.Langkah pertama dalam pemberian makanan mungkin adalah memetakan masing-masing daftar catatan tersebut ke daftar
(food amount, food type)
pasangan, dengan angka negatif untuk jumlah hewan. Anda kemudian dapat membuat fungsi untuk melakukan segala macam hal dengan pasangan ini, seperti jumlah jumlah masing-masing jenis makanan. Fungsi-fungsi ini bukan milik modulAnimal
atauFoodStore
modul, tetapi sangat dapat digunakan kembali oleh keduanya.Anda berakhir dengan banyak fungsi yang melakukan hal-hal berguna dengan
[(Num A, Eq B)]
yang dapat digunakan kembali dan modular, tetapi Anda memiliki kesulitan mencari tahu di mana harus meletakkannya atau apa yang harus mereka sebut sebagai grup. Efeknya adalah bahwa modul FP lebih sulit untuk diklasifikasi, tetapi klasifikasinya jauh kurang penting.sumber
Salah satu solusi populer, adalah memecah kode menjadi modul, berikut ini cara melakukannya dalam JavaScript:
The artikel lengkap menjelaskan pola ini dalam JavaScript , selain itu ada beberapa cara lain untuk menentukan sebuah modul, seperti RequireJS , CommonJS , Google Penutupan. Contoh lain adalah Erlang, di mana Anda memiliki modul dan perilaku yang menegakkan API dan pola, memainkan peran yang sama seperti Antarmuka di OOP.
sumber