Apakah pemrograman fungsional superset berorientasi objek?

26

Semakin banyak pemrograman fungsional yang saya lakukan, semakin saya merasa seperti itu menambahkan lapisan tambahan abstraksi yang tampak seperti bagaimana lapisan bawang itu - semua mencakup lapisan sebelumnya.

Saya tidak tahu apakah ini benar, jadi pergi dari prinsip-prinsip OOP yang telah saya kerjakan selama bertahun-tahun, adakah yang bisa menjelaskan bagaimana fungsional menggambarkan secara akurat atau salah satu di antaranya: Enkapsulasi, Abstraksi, Warisan, Polimorfisme

Saya pikir kita semua bisa mengatakan, ya itu enkapsulasi melalui tuple, atau apakah tuple secara teknis dianggap sebagai "pemrograman fungsional" atau mereka hanya sebuah utilitas dari bahasa itu?

Saya tahu Haskell dapat memenuhi persyaratan "antarmuka", tetapi sekali lagi tidak yakin apakah metode itu fakta fungsional? Saya menduga bahwa kenyataan bahwa functors memiliki dasar matematika Anda bisa mengatakan mereka adalah yang pasti dibangun dengan harapan fungsional, mungkin?

Tolong, jelaskan bagaimana menurut Anda fungsional berfungsi atau tidak memenuhi 4 prinsip OOP.

Sunting: Saya mengerti perbedaan antara paradigma fungsional dan paradigma berorientasi objek baik-baik saja dan menyadari ada banyak bahasa multiparadigma hari ini yang dapat melakukan keduanya. Saya benar-benar hanya mencari definisi tentang bagaimana fp (berpikir purist, seperti haskell) dapat melakukan salah satu dari 4 hal yang tercantum, atau mengapa hal itu tidak dapat dilakukan. yaitu "Enkapsulasi dapat dilakukan dengan penutupan" (atau jika saya salah dalam keyakinan ini, sebutkan mengapa).

Jimmy Hoffa
sumber
7
Keempat prinsip itu tidak "membuat" OOP. OOP hanya "memecahkan" mereka dengan menggunakan kelas, kelas hiearchy dan instance mereka. Tetapi saya juga ingin jawaban jika ada cara untuk mencapai mereka dalam pemrograman fungsional.
Euforia
2
@ Euforia Tergantung pada definisi, itu membuat OOP.
Konrad Rudolph
2
@KonradRudolph Saya tahu banyak orang mengklaim hal-hal ini dan manfaat yang mereka bawa sebagai properti unik OOP. Dengan asumsi "polimorfisme" berarti "polimorfisme subtipe", saya dapat memilih dua yang terakhir sebagai bagian integral dari OOP. Tetapi saya belum menemukan definisi enkapsulasi dan abstraksi yang berguna yang mengecualikan pendekatan non-OOP. Anda dapat menyembunyikan detail dan membatasi akses ke mereka dengan baik bahkan di Haskell. Dan Haskell juga memiliki polimorfisme ad-hoc, tidak hanya polimorfisme subtipe - pertanyaannya adalah, apakah bit "subtipe" itu penting?
1
@KonradRudolph Itu tidak membuatnya lebih dapat diterima. Jika ada, insentif untuk meningkatkan dan memberikan alasan bagi mereka untuk mempertimbangkannya kembali.
1
Abstraksi bersifat intrinsik untuk pemrograman apa pun, setidaknya pemrograman apa pun di luar kode mesin mentah. Enkapsulasi telah ada jauh sebelum OOP, dan itu intrinsik untuk pemrograman fungsional. Bahasa fungsional tidak diperlukan untuk memasukkan sintaksis eksplisit untuk pewarisan atau polimorfisme. Saya kira itu menambahkan hingga 'tidak'.
sdenham

Jawaban:

44

Pemrograman fungsional bukan lapisan di atas OOP; ini adalah paradigma yang sama sekali berbeda. Dimungkinkan untuk melakukan OOP dalam gaya fungsional (F # ditulis tepat untuk tujuan ini), dan di ujung lain spektrum Anda memiliki hal-hal seperti Haskell, yang secara eksplisit menolak prinsip-prinsip orientasi objek.

Anda dapat melakukan enkapsulasi dan abstraksi dalam bahasa apa pun yang cukup maju untuk mendukung modul dan fungsi. OO menyediakan mekanisme khusus untuk enkapsulasi, tetapi itu bukan sesuatu yang melekat pada OO. Inti dari OO adalah pasangan kedua yang Anda sebutkan: warisan dan polimorfisme. Konsep ini secara resmi dikenal sebagai substitusi Liskov, dan Anda tidak bisa mendapatkannya tanpa dukungan tingkat bahasa untuk pemrograman berorientasi objek. (Ya, mungkin saja memalsukannya dalam beberapa kasus, tetapi Anda kehilangan banyak keuntungan yang dibawa OO ke meja.)

Pemrograman fungsional tidak fokus pada substitusi Liskov. Ini berfokus pada peningkatan tingkat abstraksi, dan pada meminimalkan penggunaan keadaan yang dapat berubah dan rutinitas dengan "efek samping", yang merupakan istilah yang sering digunakan oleh programmer fungsional untuk membuat rutinitas yang benar-benar melakukan sesuatu (bukan sekadar menghitung sesuatu) suara mengerikan. Tetapi sekali lagi, mereka sepenuhnya paradigma yang terpisah, yang dapat digunakan bersama-sama, atau tidak, tergantung pada bahasa dan keterampilan programmer.

Mason Wheeler
sumber
1
Nah, warisan (dalam kasus-kasus yang sangat jarang ketika dibutuhkan) dapat dicapai di atas komposisi, dan itu lebih bersih daripada warisan tipe-tingkat. Polimorfisme adalah alami, terutama dengan adanya tipe polimorfik. Tapi tentu saja saya setuju bahwa FP tidak ada hubungannya dengan OOP dan prinsip-prinsipnya.
SK-logic
Selalu dimungkinkan untuk memalsukannya - Anda dapat mengimplementasikan objek dalam bahasa apa pun yang Anda pilih. Saya setuju dengan segala sesuatu yang lain :)
Eliot Ball
5
Saya tidak berpikir istilah "efek samping" diciptakan (atau terutama digunakan) oleh programmer fungsional.
sepp2k
4
@ sepp2k: Dia tidak mengatakan bahwa mereka menciptakan istilah, hanya saja mereka menggunakannya dengan nada yang kira-kira sama dengan yang biasa digunakan untuk merujuk pada anak-anak yang menolak untuk turun dari halaman mereka.
Aaronaught
16
@Aronaain bukan anak-anak di halaman saya yang mengganggu saya, itu efek samping berdarah mereka! Jika mereka berhenti bermutasi di halaman saya, saya tidak akan keberatan sama sekali.
Jimmy Hoffa
10

Saya menemukan intuisi berikut berguna untuk membandingkan OOP dan FP.

Daripada menganggap FP sebagai superset dari OOP, pikirkan OOP dan FP sebagai dua cara alternatif untuk melihat model komputasi yang mendasari yang serupa di mana Anda memiliki:

  1. Beberapa operasi yang dijalankan,
  2. beberapa argumen input ke operasi,
  3. beberapa data / parameter tetap yang dapat mempengaruhi definisi operasi,
  4. beberapa nilai hasil, dan
  5. mungkin efek samping.

Dalam OOP ini ditangkap oleh

  1. Metode yang dieksekusi,
  2. argumen input dari suatu metode,
  3. objek di mana metode dipanggil, berisi beberapa data lokal dalam bentuk variabel anggota,
  4. nilai pengembalian metode (mungkin tidak berlaku),
  5. efek samping metode ini.

Dalam FP ini ditangkap oleh

  1. Penutupan yang dieksekusi,
  2. argumen input penutupan,
  3. variabel yang ditangkap dari penutupan,
  4. nilai pengembalian penutupan,
  5. kemungkinan efek samping penutupan (dalam bahasa murni seperti Haskell, ini terjadi dengan cara yang sangat terkontrol).

Dengan interpretasi ini, suatu objek dapat dilihat sebagai kumpulan penutupan (metodenya) semua menangkap variabel non-lokal yang sama (variabel anggota objek umum untuk semua penutupan dalam koleksi). Pandangan ini juga didukung oleh fakta bahwa dalam bahasa berorientasi objek, penutupan sering dimodelkan sebagai objek dengan tepat satu metode.

Saya pikir sudut pandang yang berbeda berasal dari kenyataan bahwa pandangan berorientasi objek berpusat pada objek (data) sementara pandangan fungsional berpusat pada fungsi / penutupan (operasi).

Giorgio
sumber
8

Itu tergantung pada siapa Anda meminta definisi OOP. Tanyakan lima orang dan Anda mungkin akan mendapatkan enam definisi. Wikipedia mengatakan :

Upaya untuk menemukan definisi konsensus atau teori di balik objek belum terbukti sangat berhasil

Jadi, setiap kali seseorang memberikan jawaban yang sangat pasti, ambillah dengan sebutir garam.

Yang mengatakan, ada argumen yang baik untuk dibuat bahwa, ya, FP adalah superset dari OOP sebagai sebuah paradigma. Secara khusus , definisi Alan Kay tentang pemrograman berorientasi objek tidak bertentangan dengan gagasan ini (tetapi yang dikemukakan Kristen Nygaard ). Semua Kay benar-benar peduli adalah bahwa semuanya adalah objek, dan logika itu diimplementasikan dengan mengirimkan pesan antar objek.

Mungkin lebih menarik untuk pertanyaan Anda, kelas dan objek dapat dipikirkan dalam hal fungsi dan penutupan yang dikembalikan oleh fungsi (yang bertindak sebagai kelas dan konstruktor sekaligus). Ini sangat dekat dengan pemrograman berbasis prototipe, dan sebenarnya JavaScript memungkinkan untuk melakukan hal itu.

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(Tentu saja JavaScript mengizinkan nilai-nilai mutasi yang ilegal dalam pemrograman fungsional murni tetapi juga tidak diperlukan dalam definisi OOP yang ketat.)

Pertanyaan yang lebih penting adalah: Apakah ini klasifikasi OOP yang bermakna ? Apakah membantu memikirkannya sebagai bagian dari pemrograman fungsional? Saya pikir dalam banyak kasus tidak.

Konrad Rudolph
sumber
1
Saya merasa mungkin ada artinya dalam memikirkan di mana garis harus ditarik ketika saya beralih paradigma. Jika dikatakan tidak ada cara untuk mencapai polimorfisme subtipal dalam fp, maka saya tidak akan repot-repot mencoba menggunakan fp dalam memodelkan sesuatu yang cocok dengan itu. Namun jika itu mungkin, saya dapat meluangkan waktu untuk mencapai cara yang baik untuk melakukannya (meskipun cara yang baik mungkin tidak mungkin) ketika bekerja sangat keras dalam ruang fp tetapi menginginkan polimorfisme subtipal dalam beberapa ruang khusus. Tentunya jika mayoritas sistem cocok dengan itu, maka akan lebih baik menggunakan OOP.
Jimmy Hoffa
6

FP seperti OO bukan istilah yang didefinisikan dengan baik. Ada sekolah dengan definisi yang berbeda, terkadang saling bertentangan. Jika Anda memiliki kesamaan, Anda turun ke:

  • pemrograman fungsional adalah pemrograman dengan fungsi kelas satu

  • Pemrograman OO adalah pemrograman dengan polimorfisme inklusi yang dikombinasikan dengan setidaknya suatu bentuk kelebihan pemuatan yang diselesaikan secara dinamis. (Catatan tambahan: dalam lingkaran OO polimorfisme biasanya dianggap polimorfisme inklusi , sedangkan sekolah FP biasanya berarti polimorfisme parametrik .)

Segala sesuatu yang lain ada di tempat lain, atau tidak ada dalam beberapa kasus.

FP dan OO adalah dua alat pembuatan abstraksi. Mereka masing-masing memiliki kekuatan dan kelemahan mereka sendiri (misalnya mereka memiliki arah ekstensi yang lebih disukai dalam masalah ekspresi), tetapi tidak ada yang secara intrinsik lebih kuat daripada yang lain. Anda dapat membangun sistem OO di atas kernel FP (CLOS adalah salah satu sistem tersebut). Anda dapat menggunakan kerangka kerja OO untuk mendapatkan fungsi-fungsi kelas satu (lihat bagaimana fungsi lambda didefinisikan dalam C ++ 11 misalnya).

Pemrogram
sumber
Saya pikir maksud Anda 'fungsi kelas satu' daripada 'fungsi urutan pertama'.
dan_waterworth
Errr ... C ++ 11 lambdas hampir tidak memiliki fungsi kelas satu: Setiap lambda memiliki tipe ad-hoc sendiri (untuk semua tujuan praktis, struct anonim), tidak kompatibel dengan jenis pointer fungsi asli. Dan std::function, di mana fungsi pointer dan lambda dapat ditugaskan, jelas generik, tidak berorientasi objek. Ini tidak mengherankan, karena merek polimorfisme yang terbatas pada objek-orientasi (subtype polymorphism) sangat kurang kuat daripada polimorfisme parametrik (bahkan Hindley-Milner, apalagi Sistem F-omega penuh).
pyon
Saya tidak punya banyak pengalaman dengan bahasa fungsional purist tetapi jika Anda dapat mendefinisikan kelas metode-satu-statis dalam penutupan dan membagikannya ke berbagai konteks, saya akan mengatakan Anda (mungkin canggung) setidaknya setengah jalan ada pada opsi gaya fungsional. Ada banyak cara untuk mengatasi params ketat di sebagian besar bahasa.
Erik Reppen
2

Tidak; OOP dapat dilihat sebagai superset pemrograman prosedural dan berbeda secara fundamental dari paradigma fungsional karena telah diwakili negara dalam bidang contoh. Dalam paradigma fungsional, variabel adalah fungsi yang diterapkan pada data konstan untuk mendapatkan hasil yang diinginkan.

Sebenarnya Anda dapat mempertimbangkan pemrograman fungsional bagian dari OOP; jika Anda membuat semua kelas Anda tidak dapat diubah, Anda mungkin menganggap Anda memiliki beberapa jenis pemrograman fungsional.

m3th0dman
sumber
3
Kelas yang tidak dapat berubah tidak membuat fungsi urutan yang lebih tinggi, daftar pemahaman, atau penutupan. Fp bukan subset.
Jimmy Hoffa
1
@ Jimmy Hoffa: Anda dapat dengan mudah mensimulasikan fungsi oreder yang lebih tinggi dengan membuat kelas yang memiliki metode tunggal yang mengambil atau lebih banyak objek dari jenis yang sama dan juga mengembalikan objek dari jenis yang sama ini (jenis yang memiliki metode dan tanpa bidang) . Daftar comperhension bukan sesuatu yang terkait dengan bahasa pemrograman bukan paradigma (Smalltalk mendukungnya dan OOP). Penutupan hadir dalam C # dan akan dimasukkan di Jawa juga.
m3th0dman
ya C # memiliki penutup, tetapi itu karena multi-paradigma, penutupan ditambahkan bersama dengan potongan fp lainnya ke C # (yang saya syukuri selamanya) tetapi kehadiran mereka dalam bahasa oop tidak membuat mereka oop. Poin bagus tentang fungsi orde yang lebih tinggi, enkapsulasi suatu metode di kelas memang memungkinkan perilaku yang sama.
Jimmy Hoffa
2
Ya, tetapi jika gunakan penutupan menggunakan untuk mengubah keadaan, apakah Anda masih memprogram dalam paradigma fungsional? Intinya adalah - paradigma fungsional adalah tentang kurangnya negara bukan tentang fungsi tingkat tinggi, rekursi atau penutupan.
m3th0dman
pemikiran menarik tentang definisi fp .. Saya harus memikirkan lebih lanjut tentang ini, terima kasih telah berbagi pengamatan Anda.
Jimmy Hoffa
2

Menjawab:

Wikipedia memiliki artikel yang bagus tentang Pemrograman Fungsional dengan beberapa contoh yang Anda minta. @Konrad Rudolph sudah menyediakan tautan ke artikel OOP .

Saya tidak berpikir satu paradigma adalah super-set yang lain. Mereka adalah perspektif yang berbeda pada pemrograman dan beberapa masalah lebih baik diselesaikan dari satu perspektif dan beberapa dari yang lain.

Pertanyaan Anda semakin rumit dengan semua implementasi FP dan OOP. Setiap bahasa memiliki kebiasaan sendiri yang relevan dengan jawaban yang baik untuk pertanyaan Anda.

Rambling Semakin Tangensial:

Saya suka gagasan bahwa bahasa seperti Scala mencoba memberi Anda yang terbaik dari kedua dunia. Saya khawatir itu memberi Anda komplikasi dari kedua dunia juga.

Java adalah bahasa OO, tetapi versi 7 menambahkan fitur "coba-dengan-sumber daya" yang dapat digunakan untuk meniru semacam penutupan. Di sini ia meniru memperbarui variabel lokal "a" di tengah fungsi lain, tanpa membuatnya terlihat oleh fungsi itu. Dalam hal ini bagian pertama dari fungsi lainnya adalah konstruktor ClosureTry () dan bagian kedua adalah metode close ().

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

Keluaran:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

Ini bisa berguna untuk tujuan yang dimaksudkan membuka aliran, menulis ke aliran, dan menutupnya dengan andal, atau untuk hanya memasangkan dua fungsi dengan cara yang Anda tidak lupa untuk memanggil yang kedua setelah melakukan beberapa pekerjaan di antara mereka . Tentu saja, ini sangat baru dan tidak biasa sehingga programmer lain dapat menghapus blok try tanpa menyadari mereka melanggar sesuatu, jadi saat ini semacam anti-pola, tetapi menarik bahwa itu dapat dilakukan.

Anda dapat mengekspresikan loop apa saja dalam sebagian besar bahasa imperatif sebagai rekursi. Objek dan variabel dapat dibuat tidak berubah. Pengadaan dapat ditulis untuk meminimalkan efek samping (walaupun saya berpendapat bahwa fungsi sebenarnya tidak mungkin pada komputer - waktu yang diperlukan untuk mengeksekusi dan sumber daya prosesor / disk / sistem yang digunakannya adalah efek samping yang tidak dapat dihindari). Beberapa bahasa fungsional dapat dibuat untuk melakukan banyak jika tidak semua operasi berorientasi objek juga. Mereka tidak harus saling eksklusif, meskipun beberapa bahasa memiliki batasan (seperti tidak mengizinkan pembaruan variabel) yang mencegah pola tertentu (seperti bidang yang bisa berubah).

Bagi saya, bagian yang paling berguna dari pemrograman berorientasi objek adalah penyembunyian data (enkapsulasi), memperlakukan objek yang cukup mirip dengan yang sama (polimorfisme), dan mengumpulkan data dan metode yang beroperasi pada data tersebut bersama-sama (objek / kelas). Warisan mungkin merupakan andalan OOP, tetapi bagi saya itu adalah bagian yang paling tidak penting dan paling jarang digunakan.

Bagian yang paling berguna dari pemrograman fungsional adalah ketidakmampuan (token / nilai alih-alih variabel), fungsi (tidak ada efek samping), dan penutupan.

Saya tidak berpikir itu berorientasi objek, tetapi saya harus mengatakan bahwa salah satu hal yang paling berguna dalam ilmu komputer adalah kemampuan untuk mendeklarasikan sebuah antarmuka, kemudian memiliki berbagai fungsi dan data mengimplementasikan antarmuka itu. Saya juga suka memiliki beberapa bagian data yang bisa berubah untuk dikerjakan, jadi saya kira saya tidak sepenuhnya nyaman dalam bahasa yang hanya berfungsi secara eksklusif, meskipun saya mencoba membatasi mutabilitas dan efek samping dalam semua desain program saya.

GlenPeterson
sumber