Saya telah tertarik pada beberapa konsep pemrograman fungsional belakangan ini. Saya telah menggunakan OOP untuk beberapa waktu sekarang. Saya dapat melihat bagaimana saya akan membangun aplikasi yang cukup kompleks di OOP. Setiap objek akan tahu bagaimana melakukan hal-hal yang dilakukan objek. Atau apa pun yang dilakukan kelas orang tua juga. Jadi saya hanya bisa mengatakan Person().speak()
untuk membuat orang itu berbicara.
Tetapi bagaimana saya melakukan hal serupa dalam pemrograman fungsional? Saya melihat bagaimana fungsi adalah item kelas satu. Tetapi fungsi itu hanya melakukan satu hal spesifik. Apakah saya hanya memiliki say()
metode yang mengambang dan menyebutnya dengan Person()
argumen yang setara sehingga saya tahu hal seperti apa yang mengatakan sesuatu?
Jadi saya bisa melihat hal-hal sederhana, bagaimana saya bisa melakukan perbandingan OOP dan objek dalam pemrograman fungsional, sehingga saya dapat memodulasi dan mengatur basis kode saya?
Sebagai referensi, pengalaman utama saya dengan OOP adalah Python, PHP, dan beberapa C #. Bahasa yang saya lihat yang memiliki fitur fungsional adalah Scala dan Haskell. Meskipun saya condong ke arah Scala.
Contoh Dasar (Python):
Animal(object):
def say(self, what):
print(what)
Dog(Animal):
def say(self, what):
super().say('dog barks: {0}'.format(what))
Cat(Animal):
def say(self, what):
super().say('cat meows: {0}'.format(what))
dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')
Jawaban:
Apa yang sebenarnya Anda tanyakan di sini adalah bagaimana melakukan Polimorfisme dalam bahasa fungsional, yaitu cara membuat fungsi yang berperilaku berbeda berdasarkan argumen mereka.
Perhatikan argumen pertama untuk suatu fungsi biasanya setara dengan "objek" di OOP, tetapi dalam bahasa fungsional Anda biasanya ingin memisahkan fungsi dari data, sehingga "objek" cenderung menjadi nilai data yang murni (tidak berubah).
Bahasa fungsional secara umum menyediakan berbagai opsi untuk mencapai polimorfisme:
Sebagai contoh, berikut ini adalah implementasi Clojure dari masalah Anda menggunakan metode multimetode:
Perhatikan bahwa perilaku ini tidak memerlukan kelas eksplisit untuk didefinisikan: peta reguler berfungsi dengan baik. Fungsi pengiriman (: ketikkan dalam hal ini) dapat berupa fungsi sembarang argumen.
sumber
Ini bukan jawaban langsung, juga belum tentu 100% akurat karena saya bukan ahli bahasa fungsional. Namun dalam kedua kasus, saya akan berbagi dengan Anda pengalaman saya ...
Sekitar setahun yang lalu saya berada di kapal yang sama seperti Anda. Saya telah melakukan C ++ dan C # dan semua desain saya selalu sangat berat di OOP. Saya pernah mendengar tentang bahasa FP, membaca beberapa info online, membolak-balik buku F # tetapi masih belum bisa memahami bagaimana bahasa FP dapat menggantikan OOP atau berguna secara umum karena kebanyakan contoh yang saya lihat terlalu sederhana.
Bagi saya "terobosan" datang ketika saya memutuskan untuk belajar python. Saya mengunduh python, lalu pergi ke homepage proyek euler dan baru saja mulai melakukan satu masalah demi satu. Python tidak harus merupakan bahasa FP dan Anda tentu dapat membuat kelas di dalamnya, tetapi dibandingkan dengan C ++ / Java / C #, ia memiliki lebih banyak konstruksi FP, jadi ketika saya mulai bermain dengannya, saya membuat keputusan sadar untuk tidak mendefinisikan sebuah kelas kecuali saya benar-benar harus melakukannya.
Apa yang saya temukan menarik tentang Python adalah betapa mudah dan alami untuk mengambil fungsi dan "menjahit" mereka untuk membuat fungsi yang lebih kompleks dan pada akhirnya masalah Anda masih diselesaikan dengan memanggil satu fungsi.
Anda menunjukkan bahwa ketika coding Anda harus mengikuti prinsip tanggung jawab tunggal dan itu benar-benar benar. Tetapi hanya karena fungsi bertanggung jawab untuk satu tugas, tidak berarti itu hanya dapat melakukan minimum mutlak. Di FP, Anda masih memiliki level abstraksi. Jadi fungsi tingkat tinggi Anda masih bisa melakukan "satu" hal tetapi mereka dapat mendelegasikan ke fungsi tingkat yang lebih rendah untuk mengimplementasikan detail yang lebih baik tentang bagaimana "satu" hal itu tercapai.
Kuncinya dengan FP adalah Anda tidak memiliki efek samping. Selama Anda memperlakukan aplikasi sebagai transformasi data sederhana dengan set input dan output yang ditentukan, Anda dapat menulis kode FP yang akan memenuhi apa yang Anda butuhkan. Jelas tidak semua aplikasi cocok dengan cetakan ini, tetapi begitu Anda mulai melakukannya, Anda akan terkejut betapa banyak aplikasi yang cocok. Dan di sinilah saya pikir Python, F # atau Scala bersinar karena mereka memberi Anda konstruksi FP tetapi ketika Anda perlu mengingat keadaan Anda dan "memperkenalkan efek samping" Anda selalu dapat mengandalkan teknik OOP yang benar dan mencoba.
Sejak itu, saya telah menulis sejumlah besar kode python sebagai utilitas dan skrip pembantu lainnya untuk pekerjaan internal dan beberapa di antaranya ditingkatkan cukup jauh tetapi dengan mengingat prinsip-prinsip PADAT dasar, sebagian besar kode itu masih keluar sangat dapat dipertahankan dan fleksibel. Sama seperti di OOP antarmuka Anda adalah kelas dan Anda memindahkan kelas saat Anda refactor dan / atau menambahkan fungsionalitas, di FP Anda melakukan hal yang persis sama dengan fungsi.
Minggu lalu saya mulai coding di Java dan sejak itu, hampir setiap hari saya diingatkan bahwa ketika di OOP, saya harus mengimplementasikan antarmuka dengan mendeklarasikan kelas dengan metode yang mengesampingkan fungsi, dalam beberapa kasus saya bisa mencapai hal yang sama di Python menggunakan ekspresi lambda sederhana, misalnya, 20-30 baris kode yang saya tulis untuk memindai direktori, sudah 1-2 baris dalam Python dan tidak ada kelas.
FP sendiri adalah bahasa tingkat yang lebih tinggi. Dalam Python (maaf, satu-satunya pengalaman FP saya) saya bisa mengumpulkan daftar pemahaman dalam pemahaman daftar lain dengan lambdas dan hal-hal lain yang dilemparkan ke dalam dan semuanya hanya akan 3-4 baris kode. Dalam C ++, saya benar-benar dapat mencapai hal yang sama, tetapi karena C ++ adalah level yang lebih rendah, saya harus menulis lebih banyak kode daripada 3-4 baris dan ketika jumlah baris meningkat, pelatihan SRP saya akan dimulai dan saya akan mulai berpikir tentang cara membagi kode menjadi bagian-bagian yang lebih kecil (yaitu lebih banyak fungsi). Tetapi untuk kepentingan kelestarian dan menyembunyikan detail implementasi, saya akan menempatkan semua fungsi itu ke dalam kelas yang sama dan menjadikannya pribadi. Dan begitulah ... Saya baru saja membuat kelas sedangkan di python saya akan menulis "kembali (.... lambda x: ... ....)"
sumber
Di Haskell, yang terdekat dengan Anda adalah "kelas". Kelas ini meskipun tidak sama dengan kelas di Java dan C ++ , akan bekerja untuk apa yang Anda inginkan dalam kasus ini.
Dalam kasus Anda ini adalah bagaimana kode Anda akan terlihat.
Kemudian Anda dapat memiliki tipe data individual mengadaptasi metode ini.
Sunting: - Sebelum Anda dapat mengkhususkan mengatakan untuk Anjing, Anda perlu memberi tahu sistem bahwa Anjing adalah binatang.
EDIT: - Untuk Wilq.
Sekarang jika Anda ingin menggunakan katakan dalam fungsi say foo, Anda harus memberi tahu haskell bahwa foo hanya dapat bekerja dengan Animal.
sekarang jika Anda memanggil anjing dengan anjing itu akan menggonggong, jika Anda menelepon dengan kucing itu akan mengeong.
Anda sekarang tidak dapat memiliki definisi fungsi lainnya. Jika say disebut dengan sesuatu yang bukan hewan, itu akan menyebabkan kesalahan kompilasi.
sumber
Bahasa fungsional menggunakan 2 konstruksi untuk mencapai polimorfisme:
Membuat kode polimorfik dengan yang sama sekali berbeda dari bagaimana OOP menggunakan metode pewarisan dan virtual. Meskipun keduanya mungkin tersedia dalam bahasa OOP favorit Anda (seperti C #), sebagian besar bahasa fungsional (seperti Haskell) menendang hingga sebelas. Jarang berfungsi sebagai non-generik dan sebagian besar fungsi memiliki fungsi sebagai parameter.
Sulit untuk menjelaskan seperti ini dan akan membutuhkan banyak waktu Anda untuk mempelajari cara baru ini. Tetapi untuk melakukan ini, Anda harus benar-benar melupakan OOP, karena itu bukan cara kerjanya di dunia fungsional.
sumber
itu benar-benar tergantung pada apa yang ingin Anda capai.
jika Anda hanya perlu cara untuk mengatur perilaku berdasarkan kriteria selektif, Anda dapat menggunakan misalnya kamus (tabel hash) dengan objek-fungsi. dalam python bisa berupa sesuatu di sepanjang baris:
Namun perhatikan, bahwa (a) tidak ada 'kejadian' anjing atau kucing dan (b) Anda harus melacak 'jenis' objek Anda sendiri.
seperti misalnya:
pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]
. maka Anda bisa melakukan pemahaman daftar seperti[animals[pet[1]]['say'](pet[2]) for pet in pets]
sumber
Bahasa OO dapat digunakan sebagai pengganti bahasa tingkat rendah kadang-kadang untuk berinteraksi langsung dengan mesin. C ++ Tentu saja, tetapi bahkan untuk C # ada adapter dan semacamnya. Meskipun menulis kode untuk mengontrol bagian-bagian mekanik dan memiliki kontrol menit terhadap memori yang terbaik disimpan sedekat mungkin dengan level rendah. Tetapi jika pertanyaan ini terkait dengan perangkat lunak Berorientasi Objek saat ini seperti Line Of Business, aplikasi web, IOT, Layanan Web, dan sebagian besar aplikasi yang digunakan secara massal, maka ...
Jawab, jika berlaku
Pembaca mungkin mencoba bekerja dengan Arsitektur Berorientasi Layanan (SOA). Yaitu, DDD, N-Layered, N-Tiered, Hexagonal, apa pun. Saya belum melihat aplikasi bisnis besar yang secara efisien menggunakan OO "Tradisional" (Rekaman Aktif atau Model Kaya) seperti yang dijelaskan pada tahun 70-an dan 80-an dalam dekade terakhir +. (Lihat Catatan 1)
Kesalahannya bukan pada OP, tetapi ada beberapa masalah dengan pertanyaan itu.
Contoh yang Anda berikan hanya untuk menunjukkan polimorfisme, itu bukan kode produksi. Terkadang contoh persis seperti itu diambil secara harfiah.
Dalam FP dan SOA, Data dipisahkan dari Business Logic. Yaitu, Data dan Logika tidak berjalan bersama. Logika masuk ke Layanan, dan Data (Model Domain) tidak memiliki perilaku Polimorfik (Lihat Catatan 2).
Layanan dan Fungsi dapat bersifat Polimorfik. Di FP, Anda sering melewatkan fungsi sebagai parameter ke fungsi lain alih-alih nilai. Anda dapat melakukan hal yang sama dalam Bahasa OO dengan jenis-jenis seperti Callable atau Func, tetapi tidak merajalela (Lihat Catatan 3). Dalam FP dan SOA, Model Anda bukan Polimorfik, hanya Layanan / Fungsi Anda. (Lihat Catatan 4)
Ada contoh hardcoding yang buruk dalam contoh itu. Saya tidak hanya berbicara tentang benang merah "gonggongan anjing". Saya juga berbicara tentang CatModel dan DogModel sendiri. Apa yang terjadi ketika Anda ingin menambahkan Domba? Anda harus masuk ke kode Anda dan membuat kode baru? Mengapa? Dalam kode produksi, saya lebih suka melihat AnimalModel dengan propertinya. Paling buruk, AmphibianModel dan FowlModel jika properti dan penanganannya sangat berbeda.
Inilah yang saya harapkan untuk melihat dalam bahasa "OO" saat ini:
Bagaimana Anda berpindah dari Kelas di OO ke Pemrograman Fungsional? Seperti yang orang lain katakan; Anda bisa, tetapi sebenarnya tidak. Poin di atas adalah untuk menunjukkan bahwa Anda seharusnya tidak menggunakan Kelas (dalam pengertian tradisional dunia) saat melakukan Java dan C #. Setelah Anda bisa menulis kode dalam Arsitektur Berorientasi Layanan (DDD, Layered, Tiered, Hexagonal, apa pun), Anda akan selangkah lebih dekat ke Fungsional karena Anda memisahkan Data Anda (Model Domain) Dari Fungsi Logis Anda (Layanan).
Bahasa OO selangkah lebih dekat ke FP
Anda bahkan mungkin membawanya sedikit lebih jauh dan membagi Layanan SOA Anda menjadi dua jenis.
Opsional Kelas Tipe 1 : Layanan Implementasi Antarmuka Umum untuk Titik Masuk. Ini akan menjadi Entry Point "tidak murni" yang dapat memanggil fungsionalitas lain "Murni" atau "Tidak Murni". Ini mungkin Poin Masuk Anda dari API ISTIRAHAT.
Opsional Kelas Tipe 2 : Layanan Logika Bisnis Murni. Ini adalah Kelas Statis yang memiliki fungsi "Murni". Dalam FP, "Murni" berarti tidak ada efek samping. Itu tidak secara eksplisit mengatur Negara atau Kegigihan di mana saja. (Lihat Catatan 5)
Jadi, ketika Anda memikirkan Kelas dalam Bahasa Berorientasi Objek, yang digunakan dalam Arsitektur Berorientasi Layanan, itu tidak hanya menguntungkan Kode OO Anda, itu mulai membuat Pemrograman Fungsional tampak sangat mudah dimengerti.
Catatan
Catatan 1 : Desain Berorientasi Objek "Kaya" atau "Rekaman Aktif" Asli masih ada. Ada BANYAK kode warisan seperti itu ketika orang "melakukannya dengan benar" satu dekade lalu atau lebih. Terakhir kali saya melihat kode semacam itu (dilakukan dengan benar) berasal dari video game Codebase di C ++ Di mana mereka mengendalikan memori dengan tepat dan memiliki ruang yang sangat terbatas. Bukan untuk mengatakan FP dan Arsitektur Berorientasi Layanan adalah binatang dan tidak boleh mempertimbangkan perangkat keras. Tetapi mereka menempatkan kemampuan untuk terus berubah, dipelihara, memiliki ukuran data variabel, dan aspek lainnya sebagai prioritas. Dalam video game dan AI mesin, Anda mengontrol sinyal dan data dengan sangat tepat.
Catatan 2 : Model Domain tidak memiliki Perilaku Polimorfik, juga tidak memiliki Ketergantungan Eksternal. Mereka "Terisolasi". Itu tidak berarti mereka harus 100% Anemik. Mereka dapat memiliki banyak logika yang terkait dengan konstruksi mereka dan perubahan properti yang bisa berubah, jika itu berlaku. Lihat DDD "Nilai Objek" dan Entitas oleh Eric Evans dan Mark Seemann.
Catatan 3 : Linq dan Lambda sangat umum. Tetapi ketika pengguna membuat fungsi baru, mereka jarang menggunakan Func atau Callable sebagai parameter, sedangkan di FP akan aneh melihat aplikasi tanpa fungsi mengikuti pola itu.
Catatan 4 : Tidak membingungkan Polimorfisme dengan Warisan. CatModel mungkin mewarisi AnimalBase untuk menentukan Properties mana yang biasanya dimiliki Animal. Tapi seperti yang saya tunjukkan, Model seperti ini adalah Code Smell . Jika Anda melihat pola ini, Anda dapat mempertimbangkan memecahnya dan mengubahnya menjadi data.
Catatan 5 : Fungsi murni dapat (dan melakukan) menerima fungsi sebagai parameter. Fungsi yang masuk mungkin tidak murni, tetapi mungkin murni. Untuk tujuan pengujian, itu akan selalu murni. Tetapi dalam produksi, meskipun diperlakukan sebagai murni, mungkin mengandung efek samping. Itu tidak mengubah fakta bahwa fungsi murni itu murni. Padahal fungsi parameternya mungkin tidak murni. Tidak membingungkan! : D
sumber
Anda bisa melakukan sesuatu seperti ini .. php
sumber
$whostosay
menjadi tipe objek yang menentukan apa yang akan dieksekusi. Di atas dapat dimodifikasi untuk menerima parameter lain$whattosay
sehingga tipe yang mendukungnya (mis.'human'
) Dapat menggunakannya.