Ao compilar meu aplicativo Haskell com a -Wall
opção, GHC reclama sobre instâncias órfãs, por exemplo:
Publisher.hs:45:9:
Warning: orphan instance: instance ToSElem Result
A classe de tipo ToSElem
não é minha, ela é definida por HStringTemplate .
Agora eu sei como consertar isso (mover a declaração da instância para o módulo onde o Resultado é declarado) e sei por que o GHC prefere evitar as instâncias órfãs , mas ainda acredito que meu jeito é melhor. Eu não me importo se o compilador é incomodado - ao invés disso do que eu.
O motivo pelo qual desejo declarar minhas ToSElem
instâncias no módulo Publisher é porque é o módulo Publisher que depende de HStringTemplate, não dos outros módulos. Estou tentando manter uma separação de interesses e evitar que todos os módulos dependam do HStringTemplate.
Achei que uma das vantagens das classes de tipo de Haskell, quando comparadas, por exemplo, às interfaces Java, é que elas são abertas em vez de fechadas e, portanto, as instâncias não precisam ser declaradas no mesmo lugar que o tipo de dados. O conselho do GHC parece ser ignorar isso.
Portanto, o que estou procurando é alguma validação de que meu pensamento é correto e que eu teria justificativa para ignorar / suprimir esse aviso, ou um argumento mais convincente contra fazer as coisas do meu jeito.
Respostas:
Eu entendo por que você quer fazer isso, mas, infelizmente, pode ser apenas uma ilusão que as aulas de Haskell pareçam ser "abertas" da maneira que você diz. Muitas pessoas acham que a possibilidade de fazer isso é um bug na especificação Haskell, pelos motivos que explicarei a seguir. De qualquer forma, se realmente não for apropriado para a instância, você precisa ser declarado no módulo onde a classe é declarada ou no módulo onde o tipo é declarado, provavelmente é um sinal de que você deve usar um
newtype
ou algum outro invólucro em torno do seu tipo.As razões pelas quais as instâncias órfãs precisam ser evitadas são muito mais profundas do que a conveniência do compilador. Este assunto é bastante controverso, como você pode ver nas outras respostas. Para equilibrar a discussão, vou explicar o ponto de vista de que nunca se deve escrever instâncias órfãs, que eu acho que é a opinião da maioria entre os Haskellers experientes. Minha opinião está em algum lugar no meio, o que explicarei no final.
O problema decorre do fato de que, quando mais de uma declaração de instância existe para a mesma classe e tipo, não há mecanismo no Haskell padrão para especificar qual usar. Em vez disso, o programa é rejeitado pelo compilador.
O efeito mais simples disso é que você poderia ter um programa funcionando perfeitamente que iria parar de compilar repentinamente por causa de uma alteração que outra pessoa faz em alguma dependência remota do seu módulo.
Pior ainda, é possível que um programa em funcionamento comece a falhar no tempo de execução devido a uma mudança distante. Você pode estar usando um método que está assumindo que vem de uma determinada declaração de instância e pode ser substituído silenciosamente por uma instância diferente que é diferente o suficiente para fazer com que seu programa comece a falhar inexplicavelmente.
As pessoas que desejam garantias de que esses problemas nunca acontecerão com elas devem seguir a regra de que se alguém, em qualquer lugar, já declarou uma instância de uma determinada classe para um determinado tipo, nenhuma outra instância deve ser declarada novamente em qualquer programa escrito por qualquer um. Obviamente, existe a solução alternativa de usar um
newtype
para declarar uma nova instância, mas isso sempre é pelo menos um pequeno inconveniente e, às vezes, um grande inconveniente. Portanto, nesse sentido, aqueles que escrevem instâncias órfãs intencionalmente estão sendo bastante indelicados.Então, o que deve ser feito sobre esse problema? O campo de instância anti-órfã diz que o aviso do GHC é um bug, precisa ser um erro que rejeita qualquer tentativa de declarar uma instância órfã. Nesse ínterim, devemos exercer autodisciplina e evitá-los a todo custo.
Como você viu, existem aqueles que não estão tão preocupados com esses problemas potenciais. Na verdade, eles encorajam o uso de instâncias órfãs como uma ferramenta para separação de interesses, como você sugere, e dizem que deve-se apenas ter certeza, caso a caso, de que não há problema. Já fui incomodado várias vezes por casos de órfãos de outras pessoas para estar convencido de que essa atitude é muito arrogante.
Acho que a solução certa seria adicionar uma extensão ao mecanismo de importação de Haskell que controlaria a importação de instâncias. Isso não resolveria os problemas completamente, mas ajudaria a proteger nossos programas contra os danos das instâncias órfãs que já existem no mundo. E então, com o tempo, posso me convencer de que, em certos casos limitados, talvez uma instância órfã não seja tão ruim. (E essa mesma tentação é a razão de alguns no campo da instância anti-órfã se oporem à minha proposta.)
Minha conclusão de tudo isso é que, pelo menos por enquanto, eu recomendo fortemente que você evite declarar quaisquer instâncias órfãs, para ter consideração para com os outros, se não por outro motivo. Use um
newtype
.fonte
Vá em frente e suprima este aviso!
Você está em boa companhia. Conal faz isso em "TypeCompose". "chp-mtl" e "chp-transformers" fazem isso, "control-monad-exception-mtl" e "control-monad-exception-monadsfd" fazem isso, etc.
btw você provavelmente já sabe disso, mas para aqueles que não sabem e tropeçam na sua pergunta em uma pesquisa:
{-# OPTIONS_GHC -fno-warn-orphans #-}
Editar:
Eu reconheço os problemas que Yitz mencionou em sua resposta como problemas reais. No entanto, não vejo o uso de instâncias órfãs como um problema também, e tento escolher o "menor de todos os males", que é melhor usar instâncias órfãs com prudência.
Usei apenas um ponto de exclamação em minha resposta curta porque sua pergunta mostra que você já está bem ciente dos problemas. Caso contrário, eu teria ficado menos entusiasmado :)
Um pouco de diversão, mas o que eu acredito é a solução perfeita em um mundo perfeito sem concessões:
Eu acredito que os problemas que Yitz menciona (não saber qual instância é escolhida) poderiam ser resolvidos em um sistema de programação "holístico" onde:
De volta do mundo da fantasia (ou esperançosamente do futuro), agora: Eu recomendo tentar evitar instâncias órfãs enquanto ainda as usa quando você "realmente precisa"
fonte
Instâncias órfãs são um incômodo, mas, em minha opinião, às vezes são necessárias. Costumo combinar bibliotecas onde um tipo vem de uma biblioteca e uma classe vem de outra biblioteca. É claro que não se pode esperar que os autores dessas bibliotecas forneçam instâncias para todas as combinações concebíveis de tipos e classes. Então, eu tenho que provê-los, e eles são órfãos.
A ideia de que você deve envolver o tipo em um novo tipo quando precisa fornecer uma instância é uma ideia com mérito teórico, mas é muito tediosa em muitas circunstâncias; é o tipo de ideia apresentada por pessoas que não escrevem código Haskell para viver. :)
Então vá em frente e forneça instâncias órfãs. Eles são inofensivos.
Se você conseguir travar o ghc com instâncias órfãs, isso é um bug e deve ser relatado como tal. (O bug que o ghc teve / tem sobre não detectar várias instâncias não é tão difícil de corrigir.)
Mas esteja ciente de que em algum momento no futuro outra pessoa poderá adicionar alguma instância como você já fez e você poderá obter um erro (em tempo de compilação).
fonte
(Ord k, Arbitrary k, Arbitrary v) ⇒ Arbitrary (Map k v)
ao usar QuickCheck.Nesse caso, acho que o uso de instâncias órfãs está bem. A regra geral para mim é - você pode definir uma instância se "possuir" a typeclass ou se "possuir" o tipo de dados (ou algum componente dele - ou seja, uma instância para Maybe MyData também é adequada, pelo menos às vezes). Dentro dessas restrições, onde você decide colocar a instância é da sua conta.
Há mais uma exceção - se você não possui a typeclass ou o tipo de dados, mas está produzindo um binário e não uma biblioteca, tudo bem também.
fonte
(Eu sei que estou atrasado para a festa, mas isso ainda pode ser útil para outras pessoas)
Você poderia manter as instâncias órfãs em seu próprio módulo; então, se alguém importar esse módulo, é especificamente porque precisa deles e pode evitar importá-los se causar problemas.
fonte
Junto com essas linhas, eu entendo a posição das bibliotecas WRT do campo de instância anti-órfã, mas para destinos executáveis, as instâncias órfãs não deveriam servir?
fonte