Ada beberapa masalah yang mudah dipecahkan oleh Tipe Data Aljabar, misalnya tipe Daftar dapat dengan sangat ringkas dinyatakan sebagai:
data ConsList a = Empty | ConsCell a (ConsList a)
consmap f Empty = Empty
consmap f (ConsCell a b) = ConsCell (f a) (consmap f b)
l = ConsCell 1 (ConsCell 2 (ConsCell 3 Empty))
consmap (+1) l
Contoh khusus ini ada di Haskell, tetapi akan serupa dalam bahasa lain dengan dukungan asli untuk Tipe Data Aljabar.
Ternyata ada pemetaan yang jelas untuk subtipe gaya OO: tipe data menjadi kelas dasar abstrak dan setiap konstruktor data menjadi subkelas beton. Berikut ini contoh dalam Scala:
sealed abstract class ConsList[+T] {
def map[U](f: T => U): ConsList[U]
}
object Empty extends ConsList[Nothing] {
override def map[U](f: Nothing => U) = this
}
final class ConsCell[T](first: T, rest: ConsList[T]) extends ConsList[T] {
override def map[U](f: T => U) = new ConsCell(f(first), rest.map(f))
}
val l = (new ConsCell(1, new ConsCell(2, new ConsCell(3, Empty)))
l.map(1+)
Satu-satunya hal yang diperlukan di luar subclass naif adalah cara untuk menyegel kelas, yaitu cara untuk membuatnya mustahil untuk menambahkan subclass ke hierarki.
Bagaimana Anda mendekati masalah ini dalam bahasa seperti C # atau Java? Dua batu sandungan yang saya temukan ketika mencoba menggunakan Tipe Data Aljabar di C # adalah:
- Saya tidak tahu apa yang disebut tipe bawah di C # (yaitu saya tidak tahu apa yang harus dimasukkan ke dalam
class Empty : ConsList< ??? >
) - Saya tidak bisa menemukan cara untuk menutup
ConsList
sehingga tidak ada subclass yang dapat ditambahkan ke hierarki
Apa yang akan menjadi cara paling idiomatis untuk mengimplementasikan Tipe Data Aljabar di C # dan / atau Java? Atau, jika itu tidak mungkin, apa yang akan menjadi pengganti idiomatik?
Jawaban:
Ada cara yang mudah, tapi sederhana untuk menyegel kelas di Jawa. Anda menempatkan konstruktor pribadi di kelas dasar lalu membuat kelas dalam subkelas itu.
Tack pada pola pengunjung untuk pengiriman.
Proyek saya jADT: Java Algebraic DataTypes menghasilkan semua boilerplate untuk Anda https://github.com/JamesIry/jADT
sumber
Either
. Lihat pertanyaan sayaAnda dapat mencapai ini dengan menggunakan pola pengunjung , yang akan melengkapi pencocokan pola. Sebagai contoh
dapat ditulis dalam Java sebagai
Sealing dicapai oleh
Visitor
kelas. Setiap metodenya menyatakan bagaimana mendekonstruksi salah satu subclass. Anda dapat menambahkan lebih banyak subclass, tetapi harus mengimplementasikanaccept
dan dengan memanggil salah satuvisit...
metode, sehingga harus berperilaku sukaCons
atau sukaNil
.sumber
Jika Anda menyalahgunakan parameter bernama C # (diperkenalkan dalam C # 4.0), Anda bisa membuat tipe data aljabar yang mudah dicocokkan dengan:
Berikut ini adalah implementasi dari
Either
kelas:sumber
class Right<R> : Either<Bot,R>
:, di mana Either diubah ke antarmuka dengan parameter tipe covariant (out), dan Bot adalah tipe bawah (subtipe dari setiap jenis lainnya, kebalikan dari Object). Saya tidak berpikir C # memiliki tipe terbawah.Dalam C #, Anda tidak dapat memiliki
Empty
tipe itu, karena, karena reifikasi, tipe dasar berbeda untuk tipe anggota yang berbeda. Anda hanya dapat memilikiEmpty<T>
; tidak berguna.Di Jawa, Anda dapat memiliki
Empty : ConsList
karena penghapusan tipe, tetapi saya tidak yakin apakah pemeriksa tipe tidak akan berteriak di suatu tempat.Namun karena kedua bahasa memiliki
null
, Anda dapat menganggap semua jenis referensi mereka sebagai "Apa pun | Null". Jadi, Anda hanya akan menggunakannull
sebagai "Kosong" untuk menghindari harus menentukan apa yang berasal.sumber
null
adalah terlalu umum: ini merepresentasikan tidak adanya apa pun , yaitu kekosongan pada umumnya, tetapi saya ingin mewakili tidak adanya elemen daftar, yaitu daftar kosong pada khususnya. Daftar kosong dan pohon kosong harus memiliki tipe yang berbeda. Juga, daftar kosong perlu menjadi nilai aktual karena masih memiliki perilaku sendiri, sehingga perlu memiliki metode sendiri. Untuk membuat daftar[1, 2, 3]
, saya ingin mengatakanEmpty.prepend(3).prepend(2).prepend(1)
(atau dalam bahasa dengan operator asosiatif kanan1 :: 2 :: 3 :: Empty
), tetapi saya tidak bisa mengatakannyanull.prepend …
.Empty
danEmpty<>
dan menyalahgunakan operator konversi tersirat untuk memungkinkan simulasi yang cukup praktis, jika Anda mau. Pada dasarnya, Anda menggunakanEmpty
kode, tetapi semua jenis tanda tangan dll hanya menggunakan varian generik.Di Jawa kamu tidak bisa. Tetapi Anda dapat mendeklarasikan kelas dasar sebagai paket privat, yang berarti bahwa semua subclass langsung harus memiliki paket yang sama dengan kelas dasar. Jika Anda kemudian mendeklarasikan subclass sebagai final, mereka tidak dapat subclass lebih jauh.
Saya tidak tahu apakah ini akan mengatasi masalah Anda yang sebenarnya ...
sumber
intanceof
pemeriksaan dinamis "pseudo-type-safe" (yaitu: Saya tahu itu aman, bahkan jika kompiler tidak), dengan hanya memastikan bahwa saya selalu periksa kedua kasus itu. Namun, jika orang lain menambahkan subkelas baru, maka saya bisa mendapatkan kesalahan runtime yang tidak saya harapkan.Tipe data
ConsList<A>
dapat direpresentasikan sebagai antarmuka. Antarmuka memperlihatkandeconstruct
metode tunggal yang memungkinkan Anda untuk "mendekonstruksi" nilai dari jenis itu - yaitu, untuk menangani masing-masing konstruktor yang mungkin. Panggilan kedeconstruct
metode analog dengancase of
formulir di Haskell atau ML.The
deconstruct
Metode mengambil "callback" fungsi untuk setiap konstruktor di ADT tersebut. Dalam kasus kami, dibutuhkan fungsi untuk kasus daftar kosong, dan fungsi lain untuk kasus "sel kontra".Setiap fungsi panggilan balik menerima sebagai argumen nilai yang diterima oleh konstruktor. Jadi kasus "daftar kosong" tidak memerlukan argumen, tetapi kasus "kontra sel" mengambil dua argumen: kepala dan ujung daftar.
Kita dapat menyandikan "beberapa argumen" ini menggunakan
Tuple
kelas, atau menggunakan currying. Dalam contoh ini, saya memilih untuk menggunakanPair
kelas sederhana .Antarmuka diimplementasikan sekali untuk setiap konstruktor. Pertama, kami memiliki implementasi untuk "daftar kosong". The
deconstruct
pelaksanaan hanya menyebutemptyCase
fungsi callback.Kemudian kami menerapkan kasus "sel kontra" dengan cara yang sama. Kali ini kelas memiliki properti: kepala dan ekor daftar yang tidak kosong. Dalam
deconstruct
implementasi, properti-properti tersebut diteruskan keconsCase
fungsi callback.Berikut adalah contoh penggunaan penyandian ADT ini: kita dapat menulis sebuah
reduce
fungsi yang merupakan daftar lipat yang biasa.Ini analog dengan implementasi ini di Haskell:
sumber
Tidak ada cara yang baik untuk melakukan ini, tetapi jika Anda bersedia untuk hidup dengan hack mengerikan maka Anda dapat menambahkan beberapa jenis eksplisit memeriksa konstruktor kelas dasar abstrak. Di Jawa, ini akan menjadi sesuatu seperti
Dalam C # itu lebih rumit karena reified generics - pendekatan yang paling sederhana mungkin untuk mengubah tipe menjadi string dan memotong-motong itu.
Perhatikan bahwa di Jawa bahkan mekanisme ini secara teoritis dapat dilewati oleh seseorang yang benar-benar ingin melalui model serialisasi atau
sun.misc.Unsafe
.sumber
Type type = this.GetType(); if (type != typeof(Empty<T>) && type != typeof(ConsCell<T>)) throw new Exception();