Saya benar-benar berpikir bahwa polimorfisme tipe kembali adalah salah satu fitur terbaik dari kelas tipe. Setelah menggunakannya sebentar, kadang-kadang sulit bagi saya untuk kembali ke pemodelan gaya OOP di mana saya tidak memilikinya.
Pertimbangkan pengkodean aljabar. Di Haskell kita memiliki kelas tipe Monoid
(mengabaikan mconcat
)
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Bagaimana kita bisa menyandikan ini sebagai antarmuka dalam bahasa OO? Jawaban singkatnya adalah kita tidak bisa. Itu karena tipe mempty
is (Monoid a) => a
alias, return type polymorphism. Memiliki kemampuan memodel aljabar adalah IMO yang sangat berguna. *
Anda memulai posting Anda dengan keluhan tentang "Transparansi Referensial." Ini memunculkan poin penting: Haskell adalah bahasa yang berorientasi nilai. Jadi ekspresi seperti read 3
tidak harus dipahami sebagai hal yang menghitung nilai, mereka juga dapat dipahami sebagai nilai. Apa artinya ini adalah bahwa masalah sebenarnya bukan tipe kembali polimorfisme: itu adalah nilai dengan tipe polimorfik ( []
dan Nothing
). Jika bahasa harus memiliki ini, maka itu benar-benar harus memiliki tipe pengembalian polimorfik untuk konsistensi.
Haruskah kita bisa mengatakan []
tipe forall a. [a]
? Aku pikir begitu. Fitur-fitur ini sangat berguna, dan membuat bahasanya lebih sederhana.
Jika Haskell memiliki subtipe polimorfisme []
bisa menjadi subtipe untuk semua [a]
. Masalahnya adalah, saya tidak tahu cara pengkodean yang tanpa jenis daftar kosong menjadi polimorfik. Pertimbangkan bagaimana hal itu akan dilakukan dalam Scala (lebih pendek daripada melakukannya dalam bahasa OOP yang diketik secara kanonik kanonik, Jawa)
abstract class List[A]
case class Nil[A] extends List[A]
case class Cons[A](h: A. t: List[A]) extends List[A]
Bahkan di sini, Nil()
adalah objek tipe Nil[A]
**
Keuntungan lain dari polimorfisme tipe kembali adalah membuat Curry-Howard lebih sederhana.
Pertimbangkan teorema logis berikut:
t1 = forall P. forall Q. P -> P or Q
t2 = forall P. forall Q. P -> Q or P
Kita dapat dengan mudah menangkap ini sebagai teorema di Haskell:
data Either a b = Left a | Right b
t1 :: a -> Either a b
t1 = Left
t2 :: a -> Either b a
t2 = Right
Singkatnya: Saya suka polimorfisme tipe kembali, dan hanya berpikir itu merusak transparansi referensial jika Anda memiliki gagasan terbatas tentang nilai-nilai (walaupun ini kurang menarik dalam kasus kelas tipe ad hoc). Di sisi lain, saya menemukan poin Anda tentang MR dan ketik defaulting menarik.
*. Dalam komentar ysdx tunjukkan ini tidak sepenuhnya benar: kita bisa mengimplementasikan kembali tipe kelas dengan memodelkan aljabar sebagai tipe lain. Seperti java:
abstract class Monoid<M>{
abstract M empty();
abstract M append(M m1, M m2);
}
Anda kemudian harus melewati objek jenis ini dengan Anda. Scala memiliki gagasan tentang parameter implisit yang menghindari beberapa, tetapi dalam pengalaman saya tidak semua, overhead secara eksplisit mengelola hal-hal ini. Menempatkan metode utilitas Anda (metode pabrik, metode biner, dll) pada tipe F-bounded yang terpisah ternyata merupakan cara yang sangat bagus untuk mengelola berbagai hal dalam bahasa OO yang memiliki dukungan untuk obat generik. Yang mengatakan, saya tidak yakin saya akan mengelus pola ini jika saya tidak memiliki pengalaman memodelkan hal dengan kacamata ketik, dan saya tidak yakin orang lain akan melakukannya.
Ini juga memiliki keterbatasan, di luar kotak tidak ada cara untuk mendapatkan objek yang mengimplementasikan typeclass untuk jenis sewenang-wenang. Anda harus melewati nilai-nilai secara eksplisit, menggunakan sesuatu seperti implisit Scala, atau menggunakan beberapa bentuk teknologi injeksi ketergantungan. Hidup jadi jelek. Di sisi lain, itu menyenangkan bahwa Anda dapat memiliki beberapa implementasi untuk tipe yang sama. Sesuatu dapat menjadi monoid dalam berbagai cara. Juga, membawa sekitar struktur ini secara terpisah memiliki IMO yang lebih modern, konstruktif, merasa secara matematis. Jadi, meskipun saya umumnya masih lebih suka cara Haskell melakukan ini, saya mungkin melebih-lebihkan kasus saya.
Kacamata jenis dengan polimorfisme tipe kembali membuat hal semacam ini mudah ditangani. Itu tidak berarti itu adalah cara terbaik untuk melakukannya.
**. Jörg W Mittag menunjukkan ini bukan cara kanonik untuk melakukan ini di Scala. Sebagai gantinya, kami akan mengikuti perpustakaan standar dengan sesuatu yang lebih seperti:
abstract class List[+A] ...
case class Cons[A](head: A, tail: List[A]) extends List[A] ...
case object Nil extends List[Nothing] ...
Ini mengambil keuntungan dari dukungan Scala untuk tipe bawah, serta paramaters tipe kovarian. Jadi, bukan Nil
tipe . Pada titik ini kita cukup jauh dari Haskell, tetapi menarik untuk dicatat bagaimana Haskell mewakili tipe bawahNil
Nil[A]
undefined :: forall a. a
Artinya, ini bukan subtipe dari semua jenis, itu adalah polimorfis (sp) anggota dari semua jenis.
Namun lebih banyak polimorfisme tipe kembali.