Acho que não entendo classes de tipo. Eu li em algum lugar que pensar em classes de tipo como "interfaces" (do OO) implementadas por um tipo é errado e enganoso. O problema é que estou tendo problemas para vê-los como algo diferente e como isso está errado.
Por exemplo, se eu tiver uma classe de tipo (na sintaxe Haskell)
class Functor f where
fmap :: (a -> b) -> f a -> f b
Como isso é diferente da interface [1] (na sintaxe Java)
interface Functor<A> {
<B> Functor<B> fmap(Function<B, A> fn)
}
interface Function<Return, Argument> {
Return apply(Argument arg);
}
Uma possível diferença em que posso pensar é que a implementação da classe de tipo usada em uma determinada invocação não é especificada, mas sim determinada a partir do ambiente - por exemplo, examinando os módulos disponíveis para uma implementação para esse tipo. Esse parece ser um artefato de implementação que pode ser endereçado em uma linguagem OO; como o compilador (ou o tempo de execução) poderia procurar um wrapper / extensor / monkey-patcher que exponha a interface necessária no tipo.
o que estou perdendo?
[1] Observe que o f a
argumento foi removido fmap
porque, como é uma linguagem OO, você chamaria esse método em um objeto. Essa interface assume que o f a
argumento foi corrigido.
C
sem a presença de downcasts?Além da excelente resposta de Andreas, lembre-se de que as classes de tipo visam otimizar a sobrecarga , o que afeta o espaço de nome global. Não há sobrecarga no Haskell além do que você pode obter através das classes de tipo. Por outro lado, quando você usa interfaces de objeto, apenas as funções declaradas para receber argumentos dessa interface precisam se preocupar com os nomes das funções nessa interface. Portanto, as interfaces fornecem espaços de nomes locais.
Por exemplo, você tinha
fmap
em uma interface de objeto chamada "Functor". Seria perfeitamente bom ter outrofmap
em outra interface, diga "Structor". Cada objeto (ou classe) pode escolher qual interface ele deseja implementar. Por outro lado, em Haskell, você pode ter apenas umfmap
em um contexto específico. Você não pode importar as classes do tipo Functor e Structor para o mesmo contexto.As interfaces de objeto são mais semelhantes às assinaturas ML padrão do que às classes de tipo.
fonte
No seu exemplo concreto (com a classe de tipo Functor), as implementações Haskell e Java se comportam de maneira diferente. Imagine que você tenha Talvez o tipo de dados e queira que seja Functor (é realmente um tipo de dados popular no Haskell, que também pode ser facilmente implementado em Java). No seu exemplo de Java, você fará com que a classe Maybe implemente sua interface do Functor. Então, você pode escrever o seguinte (apenas pseudo-código, porque eu tenho apenas c # background):
Observe que
res
tem o tipo Functor, não Talvez. Portanto, isso torna a implementação do Java quase inutilizável, porque você perde informações concretas e precisa fazer projeções. (pelo menos falhei ao escrever uma implementação em que tipos ainda estavam presentes). Com as classes do tipo Haskell, você obterá Talvez Int como resultado.fonte
Maybe<Int>
.