Sim. Isso é chamado de "estilo de passagem de dicionário". Às vezes, quando estou fazendo algumas coisas especialmente complicadas, preciso descartar uma classe e transformá-la em dicionário, porque a passagem de dicionário é mais poderosa 1 , mas muitas vezes bastante complicada, tornando o código conceitualmente simples bastante complicado. Às vezes, uso o estilo de passagem de dicionário em idiomas que não são Haskell para simular classes tipográficas (mas aprendi que geralmente não é uma idéia tão boa quanto parece).
Obviamente, sempre que houver uma diferença no poder expressivo, haverá uma troca. Embora você possa usar uma determinada API de mais maneiras, se for escrita usando DPS, a API obtém mais informações, se você não puder. Uma das maneiras pelas Data.Set
quais isso aparece na prática é o fato de existir apenas um Ord
dicionário por tipo. Ele Set
armazena seus elementos classificados de acordo com Ord
, e se você criar um conjunto com um dicionário e, em seguida, inserir um elemento usando outro, como seria possível com o DPS, poderá quebrar Set
a invariante e causar a falha. Esse problema de exclusividade pode ser atenuado usando um método existencial fantasmadigite para marcar o dicionário, mas, novamente, à custa de um pouco de complexidade irritante na API. Isso também aparece da mesma maneira na Typeable
API.
A parte da singularidade não aparece com muita frequência. O que as classes tipográficas são ótimas é escrever código para você. Por exemplo,
catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g
que usa dois "processadores" que recebem uma entrada e podem dar uma saída e os concatenam, achatando Nothing
, teriam que ser escritos no DPS, algo assim:
catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g
Basicamente, tivemos que especificar o tipo em que o estamos usando novamente, mesmo que já o tenhamos explicado na assinatura do tipo, e isso foi redundante porque o compilador já conhece todos os tipos. Como existe apenas uma maneira de construir um dado Semigroup
em um tipo, o compilador pode fazer isso por você. Isso tem um efeito de tipo "juros compostos" quando você começa a definir várias instâncias paramétricas e a usar a estrutura de seus tipos para calcular para você, como nos Data.Functor.*
combinadores, e isso é usado com grande efeito, deriving via
onde você pode obter basicamente todas as estrutura algébrica "padrão" do seu tipo, escrita para você.
E nem me inicie nos MPTC e nos fundeps, que alimentam as informações de volta em digitação e inferência. Eu nunca tentei converter uma coisa dessas para DPS - suspeito que envolva repassar muitas provas de igualdade de tipo - mas, de qualquer forma, tenho certeza de que seria muito mais trabalho para o meu cérebro do que seria confortável com.
-
1 U NLES você usa reflection
caso em que eles se tornam equivalentes em poder -, mas reflection
também pode ser complicado de usar.