Masalah apa yang dipecahkan oleh tipe data aljabar?

18

Peringatan yang adil, saya baru mengenal pemrograman fungsional sehingga saya dapat memiliki banyak asumsi buruk.

Saya sudah belajar tentang tipe aljabar. Banyak bahasa fungsional tampaknya memilikinya, dan mereka cukup berguna dalam hubungannya dengan pencocokan pola. Namun, masalah apa yang sebenarnya mereka pecahkan? Saya dapat mengimplementasikan tipe aljabar yang tampaknya (semacam) dalam C # seperti ini:

public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
    public T Value { get; set; }
}

var result = GetSomeValue();
if(result is None)
{
}
else
{
}

Tapi saya pikir sebagian besar akan setuju ini adalah program pemrograman berorientasi objek, dan Anda tidak boleh melakukannya. Jadi, apakah pemrograman fungsional hanya menambahkan sintaks yang lebih bersih yang membuat gaya pemrograman ini tampak kurang kotor? Apa lagi yang saya lewatkan?

ConditionRacer
sumber
6
Pemrograman fungsional adalah paradigma yang berbeda dari pemrograman berorientasi objek.
Basile Starynkevitch
@ BasileStarynkevitch saya sadari, tetapi ada bahasa seperti F # yang sedikit dari keduanya. Pertanyaannya bukan tentang bahasa fungsional, tetapi lebih tentang masalah apa yang dipecahkan oleh tipe data aljabar.
ConditionRacer
7
Apa yang terjadi ketika saya mendefinisikan class ThirdOption : Option{}dan memberi Anda new ThirdOption()tempat yang Anda harapkan Someatau None?
amon
1
@amon bahasa yang memiliki tipe penjumlahan umumnya memiliki cara untuk tidak mengizinkannya. Misalnya, Haskell mendefinisikan data Maybe a = Just a | Nothing(setara dengan data Option a = Some a | Nonedalam contoh Anda): Anda tidak dapat menambahkan kasus ketiga post-hoc. Meskipun Anda dapat mengurutkan jenis penjiplakan dalam C # dengan cara yang Anda perlihatkan, itu bukan yang tercantik.
Martijn
1
Saya pikir itu kurang "masalah apa yang dipecahkan oleh ADT" dan lebih seperti "ADT adalah cara yang berbeda dalam mendekati sesuatu".
MathematicalOrchid

Jawaban:

44

Kelas dengan antarmuka dan warisan menyajikan dunia terbuka: Siapa saja dapat menambahkan jenis data baru. Untuk antarmuka yang diberikan, mungkin ada kelas yang mengimplementasikannya di seluruh dunia, dalam file yang berbeda, dalam proyek yang berbeda, di perusahaan yang berbeda. Mereka membuatnya mudah untuk menambahkan kasus ke struktur data, tetapi karena implementasi antarmuka terdesentralisasi, sulit untuk menambahkan metode baru ke antarmuka. Setelah antarmuka publik, pada dasarnya membeku. Tidak ada yang tahu semua kemungkinan implementasi.

Tipe data aljabar adalah ganda untuk itu, mereka tertutup . Semua kasus data terdaftar di satu tempat dan operasi tidak hanya dapat mencantumkan varian secara mendalam, mereka didorong untuk melakukannya. Akibatnya menulis fungsi baru yang beroperasi pada tipe data aljabar adalah sepele: Cukup tulis fungsi sialan itu. Sebagai gantinya, menambahkan case baru itu rumit karena Anda harus membahas seluruh basis kode dan memperpanjang setiap dasarnya match. Mirip dengan situasi dengan antarmuka, di perpustakaan standar Rust, menambahkan varian baru adalah perubahan besar (untuk tipe publik).

Ini adalah dua sisi masalah ekspresi . Tipe data aljabar adalah solusi yang tidak lengkap untuk mereka, tetapi begitu juga OOP. Keduanya memiliki keuntungan tergantung pada berapa banyak kasus data yang ada, seberapa sering kasus tersebut berubah, dan seberapa sering operasi diperpanjang atau diubah. (Itulah sebabnya banyak bahasa modern menyediakan keduanya, atau sesuatu yang serupa, atau langsung menuju mekanisme yang lebih kuat dan lebih rumit yang mencoba untuk merangkum kedua pendekatan.)


sumber
Apakah Anda mendapatkan kesalahan kompiler jika Anda gagal memperbarui pertandingan, atau hanya pada saat dijalankan Anda mengetahuinya?
Ian
6
@Ian Sebagian besar bahasa fungsional diketik secara statis dan memeriksa kelengkapan pencocokan pola. Namun, jika ada pola "tangkap semua", kompiler senang walaupun fungsinya harus berurusan dengan case baru untuk melakukan tugasnya. Selain itu, Anda harus mengkompilasi ulang semua kode dependen, Anda tidak dapat mengkompilasi hanya satu perpustakaan dan menautkannya kembali ke aplikasi yang sudah dibangun.
Juga, secara statis memeriksa bahwa suatu pola sudah lengkap mahal pada umumnya. Ini memecahkan masalah SAT terbaik dan paling buruk bahasa memungkinkan untuk predikat sewenang-wenang yang membuat ini tidak dapat diputuskan.
usr
@ usr Tidak ada bahasa Saya sadar akan mencoba solusi yang sempurna, baik kompiler memahami bahwa itu lengkap atau Anda terpaksa menambahkan semua kasus di mana Anda jatuh dan terbakar. Saya tidak tahu kaitannya dengan SAT, apakah Anda memiliki tautan ke pengurangan? Terlepas dari itu, untuk kode aktual yang ditulis dalam program nyata, pemeriksaan kelengkapan adalah setetes dalam ember.
Bayangkan Anda cocok di N boolean. Kemudian Anda menambahkan klausa pertandingan seperti (a, b, _, d, ...). Kasing catch-all adalah! Clause1 &&! Clause2 && .... Sepertinya SAT bagi saya.
usr
12

Jadi, apakah pemrograman fungsional hanya menambahkan sintaks yang lebih bersih yang membuat gaya pemrograman ini tampak kurang kotor?

Itu mungkin penyederhanaan, tapi ya.

Apa lagi yang saya lewatkan?

Mari kita perjelas jenis data aljabar apa (ringkasan tautan baik ini dari Learn you as Haskell):

  • Jenis penjumlahan yang mengatakan "nilai ini bisa menjadi A atau B".
  • Jenis produk yang mengatakan "nilai ini adalah A dan B".

contoh Anda hanya benar-benar berfungsi dengan yang pertama.

Apa yang mungkin Anda lewatkan adalah bahwa dengan menyediakan dua operasi dasar ini, bahasa fungsional memungkinkan Anda membangun yang lainnya. C # memiliki struct, kelas, enum, generik di atasnya, dan tumpukan aturan untuk mengatur bagaimana hal-hal ini berperilaku.

Dikombinasikan dengan beberapa sintaks untuk membantu, bahasa fungsional dapat menguraikan operasi ke dua jalur ini, memberikan pendekatan yang bersih, sederhana dan elegan untuk jenis.

Masalah apa yang dipecahkan oleh tipe data aljabar?

Mereka memecahkan masalah yang sama dengan sistem tipe lainnya: "nilai apa yang legal untuk digunakan di sini?" - mereka hanya mengambil pendekatan berbeda untuk itu.

Telastyn
sumber
4
Kalimat terakhir Anda seperti memberi tahu seseorang bahwa "kapal menyelesaikan masalah yang sama dengan pesawat terbang: transportasi - mereka hanya mengambil pendekatan berbeda untuk itu". Ini pernyataan yang sepenuhnya benar, dan juga pernyataan yang tidak berguna.
Mehrdad
@ mrdrdad - Saya pikir itu melebih-lebihkan sedikit.
Telastyn
1
Tidak diragukan lagi Anda memahami tipe data aljabar dengan baik, tetapi daftar berpoin Anda ("Tipe penjumlahan adalah ..." dan "Tipe produk adalah ...") lebih mirip deskripsi jenis penyatuan dan persimpangan, yang tidak hal yang sama seperti jumlah dan jenis produk.
pyon
6

Mungkin mengejutkan Anda mengetahui bahwa pencocokan pola tidak dianggap sebagai cara paling idiomatis untuk bekerja dengan Opsi. Lihat dokumentasi Pilihan Scala untuk lebih lanjut tentang itu. Saya tidak yakin mengapa begitu banyak tutorial KB mendorong penggunaan ini.

Sebagian besar yang Anda lewatkan adalah ada sejumlah fungsi yang dibuat untuk membuat bekerja dengan Opsi lebih mudah. Pertimbangkan contoh utama dari dokumen Scala:

val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

Perhatikan bagaimana mapdan filtermembiarkan Anda menjalankan operasi pada Opsi, tanpa harus memeriksa pada setiap titik apakah Anda memilikinya Noneatau tidak. Kemudian pada akhirnya, Anda gunakan getOrElseuntuk menentukan nilai default. Tidak ada gunanya Anda melakukan sesuatu yang "kotor" seperti memeriksa jenis. Pemeriksaan tipe yang tidak dapat dihindari dilakukan secara internal ke perpustakaan. Haskell memiliki set fungsi analagous sendiri, belum lagi set besar fungsi yang akan bekerja pada monad atau functor.

Tipe data aljabar lainnya memiliki cara idiomatis sendiri untuk bekerja dengannya, dan pencocokan pola rendah pada tiang totem dalam banyak kasus. Demikian juga, ketika Anda membuat tipe Anda sendiri, Anda diharapkan untuk menyediakan fungsi serupa untuk bekerja dengannya.

Karl Bielefeldt
sumber