O sistema de tipos do C # carece de alguns recursos necessários para implementar adequadamente as classes de tipos como uma interface.
Vamos começar com o seu exemplo, mas a chave está mostrando uma conta mais completa do que uma classe de tipo é e faz e, em seguida, tentando mapeá-las para bits C #.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Essa é a definição da classe de tipo ou semelhante à interface. Agora, vamos olhar para uma definição de um tipo e sua implementação dessa classe de tipo.
data Awesome a = Awesome a a
instance Functor Awesome where
fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)
Agora podemos ver muito obviamente um fato distinto das classes de tipos que você não pode ter com interfaces. A implementação da classe type não faz parte da definição do tipo. No C #, para implementar uma interface, você deve implementá-la como parte da definição do tipo que a implementa. Isso significa que você não pode implementar uma interface para um tipo que você não implementa, mas no Haskell você pode implementar uma classe de tipo para qualquer tipo ao qual tenha acesso.
Essa é provavelmente a maior imediatamente, mas há outra diferença bastante significativa que faz com que o equivalente em C # realmente não funcione tão bem, e você está abordando isso na sua pergunta. É sobre polimorfismo. Além disso, há algumas coisas relativamente genéricas que Haskell permite que você faça com classes de tipo que não traduzem diretamente, especialmente quando você começa a analisar a quantidade de genérico em tipos existenciais ou outras extensões do GHC, como ADTs genéricos.
Veja bem, com Haskell você pode definir os functores
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)
Então, no consumo, você pode ter uma função:
mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar
Aqui reside o problema. Em C #, como você escreve essa função?
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>);
}
Portanto, há algumas coisas erradas na versão C #, por um lado, nem tenho certeza de que permitirá que você use o <b>
qualificador como eu fiz lá, mas sem ele, tenho certeza de que não enviaria Show<>
adequadamente (sinta-se à vontade para tentar e compilar para descobrir; eu não fiz).
O maior problema aqui, porém, é que, diferentemente de Haskell, onde tínhamos nossos Terminal
s definidos como parte do tipo e utilizáveis no lugar do tipo, devido ao C # carecer de polimorfismo paramétrico apropriado (que se torna super óbvio assim que você tenta interoperar F # com C #) você não pode distinguir clara ou claramente se Direita ou Esquerda são Terminal
s. O melhor que você pode fazer é usar null
, mas e se você estiver tentando transformar um valor em um tipo Functor
ou no caso de Either
distinguir dois tipos que carregam um valor? Agora você precisa usar um tipo e ter dois valores diferentes para verificar e alternar para modelar sua discriminação?
A falta de tipos de soma adequados, tipos de união, ADTs, o que você quiser chamá-los realmente diminui muito o que as classes de tipo oferecem, porque no final do dia elas permitem tratar vários tipos (construtores) como um único tipo, e o sistema de tipos subjacentes do .NET simplesmente não tem esse conceito.