Mengapa serikat diskriminatif terkait dengan pemrograman fungsional?

30

Dalam bertahun-tahun pemrograman OO saya sudah mengerti apa itu serikat yang diskriminatif, tapi saya tidak pernah benar-benar merindukan mereka. Saya baru-baru ini melakukan beberapa pemrograman fungsional dalam C # dan sekarang saya menemukan saya terus berharap saya memilikinya. Ini membingungkan saya karena di hadapannya, konsep serikat yang didiskriminasi tampaknya cukup independen dari dikotomi fungsional / OO.

Apakah ada sesuatu yang inheren dalam pemrograman fungsional yang membuat serikat yang didiskriminasi menjadi lebih berguna daripada di OO, atau apakah dengan memaksa diri saya untuk menganalisis masalah dengan cara yang "lebih baik", saya telah cukup meningkatkan standar saya dan sekarang menuntut yang lebih baik model?

Andy
sumber
Masalah ekspresi en.wikipedia.org/wiki/Expression_problem mungkin relevan
xji
Ini sebenarnya bukan jawaban yang tepat untuk pertanyaan, jadi saya akan menjawab sebagai komentar saja, tetapi bahasa pemrograman Ceylon memiliki jenis serikat dan mereka tampaknya merangkak ke bahasa paradigma campuran OO / campuran lainnya - TypeScript dan Scala datang ke pikiran saya. Enum bahasa Jawa juga dapat digunakan sebagai semacam implementasi serikat yang didiskriminasi.
Roland Tepp

Jawaban:

45

Serikat yang didiskriminasi benar-benar bersinar bersamaan dengan pencocokan pola, di mana Anda memilih perilaku yang berbeda tergantung pada kasusnya. Tetapi pola ini secara fundamental bertentangan dengan prinsip-prinsip OO murni.

Dalam OO murni, perbedaan perilaku harus didefinisikan oleh tipe (objek) itu sendiri dan dienkapsulasi. Jadi kesetaraan dengan pencocokan pola akan memanggil metode tunggal pada objek itu sendiri, yang kemudian kelebihan beban oleh sub-tipe yang dimaksud untuk mendefinisikan perilaku yang berbeda. Memeriksa jenis objek dari luar (yang dilakukan pencocokan pola) dianggap sebagai antipattern.

Perbedaan mendasar adalah bahwa data dan perilaku terpisah dalam pemrograman fungsional, sedangkan data dan perilaku dirangkum bersama dalam OO.

Inilah alasan historisnya. Bahasa seperti C # berkembang dari bahasa OO klasik ke bahasa multi-paradigma dengan memasukkan lebih banyak fitur fungsi.

JacquesB
sumber
6
Tipe union / sum yang didiskriminasi tidak seperti pohon warisan atau beberapa kelas yang mengimplementasikan antarmuka; itu satu jenis dengan banyak jenis nilai. Mereka juga tidak mencegah enkapsulasi; klien tidak perlu tahu Anda memiliki banyak jenis nilai. Pertimbangkan daftar tertaut, yang bisa berupa simpul kosong, atau nilai dan referensi ke simpul lain. Tanpa tipe penjumlahan, Anda akhirnya meretas bersama serikat yang didiskriminasi dengan memiliki variabel untuk nilai dan referensi, tetapi memperlakukan objek dengan referensi nol sebagai simpul kosong dan menganggap variabel nilai tidak ada.
Doval
2
Secara umum, Anda akhirnya menyertakan variabel untuk semua jenis nilai yang mungkin dalam satu kelas, ditambah semacam bendera atau enum untuk memberi tahu Anda seperti apa nilai instance itu, dan menari di sekitar variabel yang tidak sesuai dengan itu jenis.
Doval
7
@Doval Ada pengkodean "standar" dari tipe data aljabar ke dalam kelas dengan memiliki antarmuka / kelas dasar abstrak mewakili tipe keseluruhan dan dengan memiliki subkelas untuk setiap kasus dari tipe tersebut. Untuk menangani pencocokan pola, Anda memiliki metode yang mengambil fungsi untuk setiap kasus, jadi untuk daftar yang Anda miliki di antarmuka tingkat atas, List<A>metode B Match<B>(B nil, Func<A,List<A>,B> cons). Sebagai contoh, ini adalah pola yang digunakan Smalltalk untuk boolean. Ini juga pada dasarnya bagaimana Scala menanganinya. Beberapa kelas yang digunakan adalah detail implementasi yang tidak perlu diekspos.
Derek Elkins
3
@Doval: Secara eksplisit memeriksa referensi nol juga tidak benar-benar dianggap sebagai OO idiomatik.
JacquesB
@ JacquesB Cek nol adalah detail implementasi. Untuk klien dari daftar tertaut biasanya ada isEmptymetode yang memeriksa apakah referensi ke simpul berikutnya adalah nol.
Doval
35

Setelah diprogram dalam Pascal dan Ada sebelum belajar pemrograman fungsional, saya tidak mengaitkan serikat yang didiskriminasi dengan pemrograman fungsional.

Serikat-serikat yang didiskriminasi dalam beberapa hal merupakan dual warisan Yang pertama memungkinkan untuk dengan mudah menambahkan operasi pada set tipe tetap (yang ada di serikat pekerja), dan pewarisan memungkinkan untuk dengan mudah menambahkan tipe dengan set operasi tetap. (Cara mudah menambahkan keduanya disebut masalah ekspresi ; itu adalah masalah yang sangat sulit untuk bahasa dengan sistem tipe statis.)

Karena penekanan OO pada jenis, dan penekanan ganda pemrograman fungsional pada fungsi, bahasa pemrograman fungsional memiliki afinitas alami untuk jenis serikat dan menawarkan struktur sintaksis untuk memudahkan penggunaannya.

Pemrogram
sumber
7

Teknik pemrograman imperatif, seperti yang sering digunakan dalam OO, sering bergantung pada dua pola:

  1. Berhasil atau lempar pengecualian,
  2. Kembali nulluntuk menunjukkan "tidak ada nilai" atau kegagalan.

Paradigma fungsional biasanya menghindari keduanya, lebih suka mengembalikan tipe majemuk yang menunjukkan alasan keberhasilan / kegagalan atau nilai / tidak ada nilai.

Serikat yang didiskriminasi sesuai dengan tagihan untuk tipe majemuk ini. Misalnya, dalam contoh pertama, Anda mungkin kembali true, atau beberapa struktur data yang menjelaskan kegagalan. Dalam kasus kedua, penyatuan yang berisi nilai, atau none, nildll. Kasus kedua sangat umum, bahwa banyak bahasa fungsional memiliki tipe "mungkin" atau "opsi" untuk mewakili nilai / tidak ada penyatuan tersebut.

Saat beralih ke gaya fungsional dengan misalnya, C #, Anda akan dengan cepat menemukan kebutuhan untuk tipe-tipe majemuk ini. void/throwdan nulltidak merasa benar dengan kode tersebut. Dan serikat pekerja yang didiskriminasi (DU) cocok dengan tagihan. Jadi, Anda mendapati diri Anda menginginkannya, seperti halnya banyak dari kita.

Kabar baiknya adalah bahwa ada banyak perpustakaan di luar sana yang memodelkan DU dalam contoh C # (lihat perpustakaan Succinc <T> saya sendiri misalnya).

David Arno
sumber
2

Jenis penjumlahan akan secara umum kurang bermanfaat dalam bahasa OO arus utama karena mereka memecahkan jenis masalah yang serupa dengan subtipe OO. Salah satu cara untuk melihat mereka adalah bahwa keduanya menangani subtyping tetapi OO adalah openyaitu seseorang dapat menambahkan subtipe sewenang-wenang ke tipe induk dan tipe penjumlahan closedyaitu menentukan menentukan dimuka apa subtipe yang valid.

Sekarang, banyak bahasa OO menggabungkan subtyping dengan konsep-konsep lain seperti struct yang diwarisi, polimorfisme, pengetikan referensi dll untuk menjadikannya secara umum lebih bermanfaat. Konsekuensinya adalah bahwa mereka cenderung lebih banyak bekerja untuk mengatur (dengan kelas dan konstruktor dan yang lainnya) sehingga cenderung tidak digunakan untuk hal-hal seperti Results dan Options dan seterusnya sampai mengetik generik menjadi umum.

Saya juga akan mengatakan bahwa fokus pada hubungan dunia nyata yang dipelajari kebanyakan orang ketika mereka memulai pemrograman OO misalnya Dog isa Animal, berarti Integer isa Result atau Error isa Result tampak agak asing. Padahal idenya sangat mirip.

Seperti mengapa bahasa fungsional mungkin lebih suka mengetik tertutup daripada mengetik terbuka, salah satu alasan yang mungkin adalah bahwa mereka cenderung lebih memilih pencocokan pola. Ini berguna untuk polimorfisme fungsi tetapi juga bekerja sangat baik dengan tipe tertutup karena kompiler dapat memeriksa secara statis bahwa pencocokan mencakup semua subtipe. Ini dapat membuat bahasa terasa lebih konsisten meskipun saya tidak percaya ada manfaat yang melekat (saya bisa salah).

Alex
sumber
BTW: Scala telah menutup warisan. sealedberarti "hanya dapat diperpanjang dalam unit kompilasi yang sama", yang memungkinkan Anda untuk menutup set subclass pada waktu desain.
Jörg W Mittag
-3

Swift dengan senang hati menggunakan serikat diskriminatif, kecuali menyebut mereka "enum". Enum adalah salah satu dari lima kategori dasar objek di Swift, yang terdiri dari kelas, struct, enum, tuple, dan closure. Opsional Swift adalah enum yang merupakan serikat diskriminatif, dan mereka sangat penting untuk kode Swift apa pun.

Jadi premis bahwa "serikat diskriminatif terkait dengan pemrograman fungsional" salah.

gnasher729
sumber