Interface do Java e classe de tipo de Haskell: diferenças e semelhanças?

111

Enquanto estou aprendendo Haskell, notei sua classe de tipo , que é considerada uma grande invenção originada de Haskell.

No entanto, na página da Wikipedia sobre classe de tipo :

O programador define uma classe de tipo especificando um conjunto de nomes de funções ou constantes, junto com seus respectivos tipos, que devem existir para cada tipo que pertence à classe.

O que me parece bastante semelhante à interface do Java (citando a página Interface (Java) da Wikipedia ):

Uma interface na linguagem de programação Java é um tipo abstrato usado para especificar uma interface (no sentido genérico do termo) que as classes devem implementar.

Esses dois parecem bastante semelhantes: a classe de tipo limita o comportamento de um tipo, enquanto a interface limita o comportamento de uma classe.

Eu me pergunto quais são as diferenças e semelhanças entre a classe de tipo em Haskell e a interface em Java, ou talvez sejam fundamentalmente diferentes?

EDIT: Eu percebi que até haskell.org admite que eles são semelhantes . Se eles são tão semelhantes (ou são?), Então por que a classe de tipos é tratada com tanto exagero?

MAIS EDITAR: Uau, tantas respostas ótimas! Acho que vou ter que deixar a comunidade decidir qual é o melhor. No entanto, ao ler as respostas, todos eles parecem apenas dizer que "há muitas coisas que o typeclass pode fazer enquanto a interface não pode ou tem que lidar com os genéricos" . Não posso deixar de me perguntar: há algo que as interfaces podem fazer enquanto as typeclasses não podem? Além disso, percebi que a Wikipedia afirma que a typeclass foi originalmente inventada no artigo de 1989 * "Como tornar o polimorfismo ad-hoc menos ad hoc", enquanto Haskell ainda está em seu berço, enquanto o projeto Java foi iniciado em 1991 e lançado pela primeira vez em 1995 Então, talvez em vez de a typeclass ser semelhante às interfaces, seja o contrário, as interfaces foram influenciadas pela typeclass?Existem documentos / papéis que apóiam ou desmentem isso? Obrigado por todas as respostas, todas são muito esclarecedoras!

Obrigado por todas as entradas!

zw324
fonte
3
Não, não há realmente nada que as interfaces possam fazer que as classes de tipo não possam, com a principal ressalva de que as interfaces geralmente aparecem em linguagens que possuem recursos internos não encontrados em Haskell. Se as classes de tipo fossem adicionadas ao Java, eles também poderiam usar esses recursos.
CA McCann
8
Se tiver várias perguntas, você deve fazer várias perguntas, não tente amontoá-las todas em uma única pergunta. De qualquer forma, para responder à sua última pergunta: a principal influência do Java é Objective-C (e não C ++ como muitas vezes é falsamente relatado), cujas principais influências, por sua vez, são Smalltalk e C. As interfaces de Java são uma adaptação dos protocolos de Objective-C, que são novamente um formalização da ideia de protocolo em OO, que por sua vez é baseada na ideia de protocolos em redes, especificamente a ARPANet. Tudo isso aconteceu muito antes do artigo que você citou. ...
Jörg W Mittag
1
... A influência de Haskell no Java veio muito mais tarde, e é limitada aos Genéricos, que foram, afinal, co-projetados por um dos designers de Haskell, Phil Wadler.
Jörg W Mittag
5
Este é um artigo da Usenet de Patrick Naughton, um dos designers originais de Java: Java foi fortemente influenciado por Objective-C e não C ++ . Infelizmente, é tão antigo que a postagem original nem aparece nos arquivos do Google.
Jörg W Mittag
8
Há outra pergunta que foi fechada como uma duplicata exata desta, mas tem uma resposta muito mais detalhada: stackoverflow.com/questions/8122109/…
Ben

Respostas:

49

Eu diria que uma interface é como uma classe de tipo SomeInterface tonde todos os valores têm o tipo t -> whatever(onde whatevernão contém t). Isso ocorre porque, com o tipo de relacionamento de herança em Java e linguagens semelhantes, o método chamado depende do tipo de objeto no qual são chamados e nada mais.

Isso significa que é muito difícil fazer coisas como add :: t -> t -> tuma interface, onde é polimórfica em mais de um parâmetro, porque não há como a interface especificar que o tipo de argumento e o tipo de retorno do método são do mesmo tipo que o tipo de o objeto que é chamado (ou seja, o tipo "self"). Com os Genéricos, existem maneiras de fingir isso criando uma interface com parâmetro genérico que deve ser do mesmo tipo que o próprio objeto, como como Comparable<T>faz, onde se espera que você use Foo implements Comparable<Foo>para que o compareTo(T otherobject)tipo tenha tipo t -> t -> Ordering. Mas isso ainda requer que o programador siga esta regra, e também causa dor de cabeça quando as pessoas querem fazer uma função que usa essa interface, elas têm que ter parâmetros de tipo genérico recursivo.

Além disso, você não terá coisas como empty :: tporque não está chamando uma função aqui, portanto, não é um método.

newacct
fonte
1
Os traços Scala (basicamente interfaces) permitem this.type para que você possa retornar ou aceitar parâmetros do "tipo próprio". O Scala tem um recurso completamente separado que eles chamam de "tipos próprios" que não tem nada a ver com isso. Nada disso é uma diferença conceitual, apenas diferença de implementações.
argila
44

O que é semelhante entre interfaces e classes de tipo é que elas nomeiam e descrevem um conjunto de operações relacionadas. As próprias operações são descritas por meio de seus nomes, entradas e saídas. Da mesma forma, pode haver muitas implementações dessas operações que provavelmente serão diferentes em sua implementação.

Com isso resolvido, aqui estão algumas diferenças notáveis:

  • Os métodos das interfaces estão sempre associados a uma instância do objeto. Em outras palavras, há sempre um parâmetro 'this' implícito que é o objeto no qual o método é chamado. Todas as entradas para uma função de classe de tipo são explícitas.
  • Uma implementação de interface deve ser definida como parte da classe que implementa a interface. Por outro lado, uma classe de tipo 'instância' pode ser definida completamente separada de seu tipo associado ... mesmo em outro módulo.

Em geral, acho que é justo dizer que as classes de tipo são mais poderosas e flexíveis do que as interfaces. Como você definiria uma interface para converter uma string em algum valor ou instância do tipo de implementação? Certamente não é impossível, mas o resultado não seria intuitivo ou elegante. Você já desejou que fosse possível implementar uma interface para um tipo em alguma biblioteca compilada? Ambos são fáceis de realizar com classes de tipo.

Daniel Pratt
fonte
1
Como você estenderia as typeclasses? Typeclasses podem estender outras typeclasses exatamente como as interfaces podem estender interfaces?
CMCDragonkai
10
Pode valer a pena atualizar esta resposta à luz das implementações padrão do Java 8 em interfaces.
Reintegrar Monica
1
@CMCDragonkai Sim, você pode, por exemplo, dizer "class (Foo a) => Bar a onde ..." para especificar que a classe do tipo Bar estende a classe do tipo Foo. Como Java, Haskell tem herança múltipla aqui.
Reintegrar Monica em
Nesse caso, a classe de tipo não é o mesmo que os protocolos em Clojure, mas com segurança de tipo?
nawfal
24

As classes de tipo foram criadas como uma forma estruturada de expressar "polimorfismo ad-hoc", que é basicamente o termo técnico para funções sobrecarregadas . Uma definição de classe de tipo se parece com isto:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

O que isso significa é que, quando você usa aplica a função fooa alguns argumentos de um tipo que pertencem à classe Foobar, ela procura uma implementação fooespecífica para aquele tipo e a usa. Isso é muito semelhante à situação com sobrecarga de operador em linguagens como C ++ / C #, exceto mais flexível e generalizado.

As interfaces têm um propósito semelhante em linguagens OO, mas o conceito subjacente é um pouco diferente; As linguagens OO vêm com uma noção embutida de hierarquias de tipo que Haskell simplesmente não tem, o que complica as coisas de algumas maneiras porque as interfaces podem envolver tanto sobrecarga por subtipagem (ou seja, métodos de chamada em instâncias apropriadas, subtipos que implementam interfaces de seus supertipos) e por despacho baseado em tipo simples (já que duas classes que implementam uma interface podem não ter uma superclasse comum que também a implementa). Dada a enorme complexidade adicional introduzida pela subtipagem, sugiro que seja mais útil pensar nas classes de tipo como uma versão aprimorada de funções sobrecarregadas em uma linguagem não OO.

Também vale a pena notar que as classes de tipo têm meios muito mais flexíveis de despacho - as interfaces geralmente se aplicam apenas à única classe que as implementa, enquanto as classes de tipo são definidas para um tipo , que pode aparecer em qualquer lugar na assinatura das funções da classe. O equivalente a isso em interfaces OO seria permitir que a interface defina maneiras de passar um objeto dessa classe para outras classes, definir métodos estáticos e construtores que selecionariam uma implementação com base em que tipo de retorno é necessário no contexto de chamada, definir métodos que pegar argumentos do mesmo tipo que a classe que implementa a interface e várias outras coisas que realmente não se traduzem.

Resumindo: eles servem a propósitos semelhantes, mas a maneira como funcionam é um pouco diferente, e as classes de tipo são significativamente mais expressivas e, em alguns casos, mais simples de usar por trabalharem em tipos fixos em vez de partes de uma hierarquia de herança.

CA McCann
fonte
Eu estava lutando para entender as hierarquias de tipos em Haskell, o que você acha dos tipos de sistemas como o Omega? Eles poderiam simular hierarquias de tipo?
CMCDragonkai
@CMCDragonkai: Não estou familiarizado o suficiente com o Omega para realmente dizer, sinto muito.
CA McCann
16

Eu li as respostas acima. Sinto que posso responder um pouco mais claramente:

Uma "classe de tipo" Haskell e uma "interface" Java / C # ou um "traço" Scala são basicamente análogos. Não há distinção conceitual entre eles, mas existem diferenças de implementação:

  • As classes do tipo Haskell são implementadas com "instâncias" que são separadas da definição do tipo de dados. Em C # / Java / Scala, as interfaces / características devem ser implementadas na definição da classe.
  • As classes de tipo Haskell permitem que você retorne este tipo ou tipo próprio. Os traços de Scala também (this.type). Observe que os "tipos próprios" no Scala são um recurso completamente não relacionado. Java / C # requerem uma solução alternativa complicada com genéricos para aproximar esse comportamento.
  • As classes de tipo Haskell permitem definir funções (incluindo constantes) sem um parâmetro de tipo de entrada "this". Interfaces Java / C # e características Scala requerem um parâmetro de entrada "this" em todas as funções.
  • As classes do tipo Haskell permitem definir implementações padrão para funções. O mesmo ocorre com os traços Scala e as interfaces Java 8+. C # pode aproximar algo assim com métodos de extensões.
argila
fonte
2
Apenas para adicionar um pouco de literatura a esses pontos de resposta, eu li On (Haskell) Type Classes e (C #) Interfaces hoje, que também se compara com interfaces C # em vez de Javas, mas deve fornecer uma compreensão do lado conceitual dissecando a noção de um interface além das fronteiras do idioma.
daniel.kahlenberg
Acho que algumas aproximações para coisas como implementações padrão são feitas de forma mais eficiente em C # com classes abstratas, talvez?
Arwin
12

Em Master Minds of Programming , há uma entrevista sobre Haskell com Phil Wadler, o inventor das classes de tipo, que explica as semelhanças entre interfaces em Java e classes de tipo em Haskell:

Um método Java como:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

é muito semelhante ao método Haskell:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Portanto, classes de tipo estão relacionadas a interfaces, mas a correspondência real seria um método estático parametrizado com um tipo como acima.

Ewernli
fonte
Esta deve ser a resposta, porque de acordo com a página inicial de Phil Wadler , ele foi o principal designer de Haskell, ao mesmo tempo em que também projetou a extensão Genérica para Java, que mais tarde foi incluída na própria linguagem.
Wong Jia Hau de
10

Assista à palestra de Phillip Wadler Faith, Evolution, and Programming Languages . Wadler trabalhou em Haskell e foi um grande contribuidor para Java Generics.

Mcandre
fonte
1
O material relevante é algo em torno de 25m (embora o começo seja bem engraçado).
Fred Foo
8

Leia Extensão de software e integração com classes de tipo, onde são fornecidos exemplos de como as classes de tipo podem resolver uma série de problemas que as interfaces não podem.

Os exemplos listados no artigo são:

  • o problema de expressão,
  • o problema de integração da estrutura,
  • o problema da extensibilidade independente,
  • a tirania da decomposição dominante, dispersão e emaranhamento.
Channing Walton
fonte
3
Link está morto lá em cima. Em vez disso, tente este .
Justin Leitgeb
1
Bem, agora aquele também está morto. Experimente este
RichardW
7

Eu não posso falar com o nível de "hype", se parece que sim. Mas sim, as classes de tipo são semelhantes de várias maneiras. Uma diferença que posso pensar é que Haskell você pode fornecer comportamento para algumas das operações da classe de tipo :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

que mostra que existem duas operações, igual (==)e diferente (/=), para coisas que são instâncias da Eqclasse de tipo. Mas a operação não igual é definida em termos de iguais (de modo que você só precisa fornecer um) e vice-versa.

Portanto, em Java provavelmente não legal, seria algo como:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

e a maneira como funcionaria é que você só precisaria fornecer um desses métodos para implementar a interface. Portanto, eu diria que a capacidade de fornecer uma espécie de implementação parcial do comportamento que você deseja no nível da interface é uma diferença.

Chris
fonte
6

Eles são semelhantes (leia-se: têm uso semelhante) e provavelmente implementados de forma semelhante: funções polimórficas em Haskell assumem uma 'vtable' listando as funções associadas à typeclass.

Essa tabela geralmente pode ser deduzida em tempo de compilação. Isso provavelmente é menos verdadeiro em Java.

Mas esta é uma tabela de funções , não métodos . Os métodos são vinculados a um objeto, as typeclasses Haskell não.

Veja-os como os genéricos de Java.

Alexandre C.
fonte
3

Como diz Daniel, as implementações de interface são definidas separadamente das declarações de dados. E, como outros apontaram, há uma maneira direta de definir operações que usam o mesmo tipo livre em mais de um lugar. Portanto, é fácil definir Numcomo uma typeclass. Assim, em Haskell, obtemos os benefícios sintáticos da sobrecarga de operadores sem realmente ter nenhum operador sobrecarregado de mágica - apenas typeclasses padrão.

Outra diferença é que você pode usar métodos baseados em um tipo, mesmo quando ainda não há um valor concreto desse tipo por aí!

Por exemplo read :: Read a => String -> a,. Portanto, se você tiver informações de outro tipo suficientes sobre como usará o resultado de uma "leitura", poderá deixar o compilador descobrir qual dicionário usar para você.

Você também pode fazer coisas como o instance (Read a) => Read [a] where...que permite definir uma instância de leitura para qualquer lista de coisas legíveis. Não acho que isso seja possível em Java.

E tudo isso são apenas typeclasses padrão de parâmetro único, sem truques acontecendo. Uma vez que introduzimos typeclasses multiparâmetros, um novo mundo de possibilidades se abre, e ainda mais com dependências funcionais e famílias de tipos, que permitem incorporar muito mais informações e computação no sistema de tipos.

sclv
fonte
1
Classes de tipo normal são para interfaces como classes de tipo multiparâmetro são para despacho múltiplo em OOP; você ganha um aumento correspondente no poder da linguagem de programação e na dor de cabeça do programador.
CA McCann