C # 'ın tip sistemi, arabirim olarak tip sınıflarını düzgün bir şekilde uygulamak için gereken birkaç özelliğe sahip değildir.
Örneğinizle başlayalım, ancak anahtar, bir typeclass'ın ne olduğunu ve ne yaptığını daha kapsamlı bir şekilde gösteriyor ve ardından bunları C # bitleriyle eşlemeye çalışıyor.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Bu, tür sınıfı tanımıdır veya arabirime benzer. Şimdi bir tür tanımına ve bu tür sınıfın uygulanmasına bakalım.
data Awesome a = Awesome a a
instance Functor Awesome where
fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)
Şimdi , arayüzlerle sahip olamayacağınız tip sınıflarının belirgin bir gerçeğini açıkça görebiliyoruz . Type sınıfının uygulanması, tür tanımının bir parçası değildir. C # 'da, bir arabirimi uygulamak için, arabirimi uygulayan tür tanımının bir parçası olarak uygulamalısınız. Bu, kendiniz uygulamadığınız bir tür için arabirim uygulayamayabileceğiniz anlamına gelir, ancak Haskell'de erişiminiz olan herhangi bir tür için bir tür sınıfı uygulayabilirsiniz.
Muhtemelen hemen en büyüğüdür, ancak C # eşdeğerini gerçekten de işe yaramaz hale getiren oldukça önemli bir fark daha vardır ve sorunuza dokunuyorsunuz. Polimorfizm ile ilgili. Ayrıca Haskell, özellikle varoluşçu türlerde veya Genel ADT'ler gibi diğer GHC uzantılarında jeneriklik miktarına bakmaya başladığınızda doğrudan tercüme edilmeyen tip sınıflarıyla ilgili olarak görece bazı genel şeyler var.
Görüyorsunuz, Haskell ile fonksiyonları tanımlayabilirsiniz
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)
Sonra tüketimde bir fonksiyona sahip olabilirsiniz:
mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar
Burada sorun yatıyor. C # 'da bu işlevi nasıl yazıyorsunuz?
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>);
}
Bu yüzden C # sürümünde birkaç şey yanlış, bir şey için <b>
, orada yaptığım gibi niteleyiciyi kullanmanıza izin vereceğinden emin değilim , ancak onsuz uygun şekilde gönderilmeyeceğinden eminimShow<>
(denemekten çekinmeyin öğrenmek için derledim;
Bununla birlikte, burada daha büyük sorun, Haskell'de, bizim türümüzün bir Terminal
parçası olarak tanımlandığımız ve daha sonra tür yerine kullanılabilir olduğu için, C # 'nin uygun parametrik polimorfizmden yoksun olmasından dolayı (birlikte çalışmayı denediğiniz anda çok belirgin hale gelen) F # ile C #), Sağ veya Sol Terminal
s olup olmadığını net veya temiz bir şekilde ayırt edemezsiniz . Yapabileceğiniz en iyi şey kullanmaktır null
, ancak Functor
ya bir değer türü a yapmaya çalışıyorsanız ya da Either
her ikisi de değer taşıyan iki türü ayırt ettiğinizde ne olur? Şimdi ayrımcılığınızı modellemek için bir tür kullanmanız ve iki farklı değeriniz mi var?
Uygun toplam türlerinin, birleşim türlerinin, ADT'lerin eksikliği, onları ne adlandırmak istersen, tipik olarak size hangi tiplerin düştüğünü bir sürü yapar, çünkü günün sonunda birden fazla türe (yapıcılara) tek bir tür olarak davranmanıza izin verir, ve .NET'in altında yatan tip sisteminin böyle bir konsepti yoktur.