Sistem tipe C # tidak memiliki beberapa fitur yang diperlukan untuk mengimplementasikan kelas tipe dengan benar sebagai antarmuka.
Mari kita mulai dengan contoh Anda, tetapi kuncinya menunjukkan akun yang lebih lengkap tentang apa itu typeclass dan apa yang dilakukan, dan kemudian mencoba memetakannya ke bit C #.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Ini adalah definisi kelas tipe, atau mirip dengan antarmuka. Sekarang mari kita lihat definisi dari suatu tipe dan implementasi dari kelas tipe itu.
data Awesome a = Awesome a a
instance Functor Awesome where
fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)
Sekarang kita bisa melihat dengan jelas satu fakta berbeda dari kelas tipe yang tidak bisa Anda lakukan miliki dengan antarmuka. Implementasi kelas tipe bukan bagian dari definisi tipe. Di C #, untuk mengimplementasikan antarmuka, Anda harus mengimplementasikannya sebagai bagian dari definisi tipe yang mengimplementasikannya. Ini berarti Anda tidak dapat mengimplementasikan antarmuka untuk jenis yang tidak Anda laksanakan sendiri, namun di Haskell Anda dapat menerapkan kelas jenis untuk jenis apa pun yang Anda akses.
Itu mungkin yang terbesar dengan segera, tetapi ada perbedaan lain yang cukup signifikan yang membuat padanan C # benar-benar tidak berfungsi, dan Anda menyentuhnya dalam pertanyaan Anda. Ini tentang Polimorfisme. Juga ada beberapa hal yang relatif umum yang Haskell memungkinkan Anda lakukan dengan kelas tipe yang langsung tidak menerjemahkan terutama ketika Anda mulai melihat jumlah genericism dalam tipe eksistensial atau ekstensi GHC lainnya seperti Generic ADTs.
Anda lihat, dengan Haskell Anda dapat mendefinisikan functors
data List a = List a (List a) | Terminal
data Tree a = Tree val (Tree a) (Tree a) | Terminal
instance Functor List where
fmap :: (a -> b) -> List a -> List b
fmap f (List a Terminal) = List (f a) Terminal
fmap f (List a rest) = List (f a) (fmap f rest)
instance Functor Tree where
fmap :: (a -> b) -> Tree a -> Tree b
fmap f (Tree val Terminal Terminal) = Tree (f val) Terminal Terminal
fmap f (Tree val Terminal right) = Tree (f val) Terminal (fmap f right)
fmap f (Tree val left Terminal) = Tree (f val) (fmap f left) Terminal
fmap f (Tree val left right) = Tree (f val) (fmap f left) (fmap f right)
Kemudian dalam konsumsi Anda dapat memiliki fungsi:
mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar
Di sinilah letak masalahnya. Dalam C # bagaimana Anda menulis fungsi ini?
public Tree<a> : Functor<a>
{
public a Val { get; set; }
public Tree<a> Left { get; set; }
public Tree<a> Right { get; set; }
public Functor<b> fmap<b>(Func<a,b> f)
{
return new Tree<b>
{
Val = f(val),
Left = Left.fmap(f);
Right = Right.fmap(f);
};
}
}
public string Show<a>(Showwable<a> ror)
{
return ror.Show();
}
public Functor<String> mapsSomething<a,b>(Functor<a> rar) where a : Showwable<b>
{
return rar.fmap(Show<b>);
}
Jadi ada beberapa hal yang salah dengan versi C #, untuk satu hal yang saya bahkan tidak yakin itu akan memungkinkan Anda untuk menggunakan <b>
kualifikasi seperti saya lakukan di sana, tapi tanpa itu saya saya yakin itu tidak akan mengirimkan Show<>
tepat (merasa bebas untuk mencoba dan kompilasi untuk mencari tahu; saya tidak).
Namun masalah yang lebih besar di sini adalah bahwa tidak seperti di atas di Haskell di mana kita memiliki Terminal
definisi kita sebagai bagian dari tipe dan kemudian dapat digunakan sebagai pengganti tipe, karena C # kurang polimorfisme parametrik yang sesuai (yang menjadi sangat jelas segera setelah Anda mencoba untuk interop F # dengan C #) Anda tidak dapat dengan jelas atau bersih membedakan apakah Kanan atau Kiri adalah Terminal
s. Yang terbaik yang dapat Anda lakukan adalah menggunakan null
, tetapi bagaimana jika Anda mencoba membuat tipe nilai a Functor
atau dalam kasus di Either
mana Anda membedakan dua tipe yang sama-sama membawa nilai? Sekarang Anda harus menggunakan satu jenis dan memiliki dua nilai yang berbeda untuk memeriksa dan beralih antara untuk memodelkan diskriminasi Anda?
Kurangnya jumlah jumlah yang tepat, jenis serikat, ADT, apa pun yang Anda ingin menyebutnya benar-benar membuat banyak jenis kacamata memberi Anda murtad karena pada akhirnya mereka membiarkan Anda memperlakukan beberapa jenis (konstruktor) sebagai satu jenis, dan sistem tipe .NET yang mendasarinya tidak memiliki konsep seperti itu.