Pergunta

Digite aulas parecem ser uma grande possibilidade de escrever funções genéricas e reutilizáveis ??em um muito consistente , eficiente e extensível. Mas ainda assim não "dominante-language" fornece-los - Pelo contrário: Conceitos , que são uma idéia bastante analógica, foram excluir a partir da próxima C ++!

O que é o raciocínio contra typeclasses? Aparentemente muitas línguas estão procurando uma maneira de lidar com problemas semelhantes: .NET introduziu restrições genéricas e interfaces como IComparable que permitem funções como

T Max<T>(T a, T b) where T : IComparable<T> { // }

para operar em todos os tipos que implementam a interface.

Scala, em vez usa uma combinação de características e os chamados parâmetros implícitos / vista limites , que são automaticamente passados ??para funções genéricas.

Mas ambos os conceitos mostrados aqui têm grandes desvantagens - Interfaces são à base de herança e, portanto, relativamente lento devido a engano e, além disso, não há possibilidade de deixar um tipo existente implementá-las

.

Se precisávamos de uma abstração para um Monoid, poderíamos muito bem escrever uma interface e deixar os nossos tipos de implementar isso, mas builtin tipos como int nunca poderia operar em suas funções de forma nativa.

parâmetros implícitos vez são inconsistentes com interfaces regulares / traços.

Com classes tipo, não haveria um problema (pseudo-código)

typeclass Monoid of A where
    static operator (+) (x : A, y : A) : A
    static val Zero : A 
end

instance Int of Monoid where
   static operator (+) (x : Int, y : Int) : Int = x + y
   static val Zero : Int = 0
end

Então, por que não usamos classes tipo? Será que eles têm sérias desvantagens afinal?

Editar : Por favor, faça typeclasses não confunda com digitação estrutural, modelos puros C ++ ou digitando pato. A typeclass é explicitamente instanciado por tipos e não apenas satisfeito por convenção . Além disso, pode realizar implementações úteis e não apenas definir uma interface.

Foi útil?

Solução

Conceito foram excluídos porque a comissão não acho que poderia levá-los no tempo certo, e porque eles não eram consideradas essenciais para a liberação. Não é que eles não acho que eles são uma boa idéia, eles só não acho que a expressão deles para C ++ é amadurecer: http://herbsutter.wordpress.com/2009/07/21/trip-report/

tipos estáticos tentar impedi-lo passando um objeto para uma função, que não satisfaz os requisitos da função. Em C ++ este é um enorme grande negócio, porque no momento em que o objeto é acessado pelo código, não há nenhuma verificação de que é a coisa certa.

Conceito tentar impedi-lo de passar um parâmetro de modelo, que não satisfaz os requisitos do modelo. Mas no momento em que o parâmetro do modelo é acessado pelo compilador, já é verificar que é a coisa certa, mesmo sem Conceitos. Se você tentar usá-lo de uma forma que não suporta, você recebe um erro do compilador [*]. No caso do código usando o modelo pesado que você pode obter três telas cheias de colchetes, mas, em princípio, que é uma mensagem informativa. A necessidade de erros de captura antes de uma compilação falhou é menos urgente do que a necessidade de erros de captura antes de comportamento indefinido em tempo de execução.

Conceito tornar mais fácil para especificar interfaces de modelo que irá trabalhar através de múltiplas instâncias . Isso é significativo, mas um problema muito menos pressão do que especificar as interfaces de função que irá trabalhar em múltiplas chamadas.

Em resposta à sua pergunta - qualquer declaração formal "Eu implementar essa interface" tem uma grande desvantagem, que requer a interface para ser inventado antes da implementação é. sistemas de inferência do tipo não, mas eles têm a grande desvantagem que as línguas em geral não podem expressar a totalidade de uma interface usando tipos, e assim você pode ter um objeto que é inferida a ser do tipo correto, mas que não tem a semântica atribuída a esse tipo. Se os seus endereços de linguagem de interfaces em tudo (em particular se ele corresponde a eles para classes), em seguida, AFAIK você tem que tomar uma posição aqui, e escolher a sua desvantagem.

[*] Normalmente. Há algumas exceções, por exemplo, a ++ sistema de tipo C atualmente não impedi-lo de usar um iterador de entrada como se fosse um iterador para a frente. Você precisa iterador traços para isso. Pato digitando sozinho não impede a transmissão de um objeto que anda, nada e grasna, mas na inspeção próxima na verdade não fazer nenhuma dessas coisas a forma como um pato faz, e fica surpreso ao saber que você pensou que seria ;-)

Outras dicas

Interfaces não precisam ser baseadas em herança ... isso é uma decisão diferente e separada design. O novo Go língua tem interfaces, mas não tem herança, por exemplo: "satisfaz um tipo automaticamente qualquer interface que especifica um subconjunto de seus métodos", como o Go FAQ coloca. Simionato do reflexões sobre herança e interfaces, solicitado pelo recente lançamento do Go, pode valer a pena a leitura.

Concordo que typeclasses são ainda mais poderoso, essencialmente porque, como abstrato classes base , eles permitem que você também especificar código útil (definindo um método extra X em termos de outros para todos os tipos que de outra forma correspondem aos baseclass mas não definem X-se) - sem a bagagem herança que ABCs (diferente das interfaces) quase inevitavelmente levar. não Quase , inevitavelmente, porque, por exemplo, ABCs do Python "faz de conta" que envolvem herança, em termos de conceituação eles oferecem ... mas, na verdade, eles precisam ser-herança base (muitos são apenas verificar a presença e assinatura de certos métodos, assim como interfaces de lá).

Como para por que um designer da linguagem (como Guido, no caso de Python) escolher tais "lobos em pele de cordeiro", como ABCs do Python, sobre o mais simples Haskell-like typeclasses que eu tinha proposto uma vez em 2002, essa é uma pergunta difícil de responder. Afinal, não é como se Python tem qualquer escrúpulo contra conceitos de empréstimos obtidos a partir de Haskell (por exemplo, compreensões lista / gerador de expressões - Python precisa de uma dualidade aqui, enquanto Haskell não, porque Haskell é "preguiçoso"). A melhor hipótese que eu posso oferecer é que, até agora, a herança é tão familiar para a maioria dos programadores que a maioria dos projetistas da linguagem sentem que podem ganhar aceitação mais fácil lançando as coisas dessa maneira (embora os designers da Go deve ser elogiado por não fazer isso).

Deixe-me começar negrito: Compreendo perfeitamente a motivação de tê-lo e não pode compreender a motivação de algumas pessoas para argumentar contra isso ...

O que você quer é nonvirtual polimorfismo ad hoc.

  • ad hoc: implementação pode variar
  • nonvirtual: por motivos de desempenho; expedição compiletime

O resto é açúcar na minha opinião.

C ++ já tem ad hoc polimorfismo através de modelos. "Conceitos", no entanto seria esclarecer que tipo de funcionalidade polimórfica ad hoc é usado por qual entidade definida pelo usuário.

C # só não tem qualquer maneira de fazê-lo. Uma abordagem que não seria nonvirtual : Se tipos como flutuador seria apenas implementar algo como "INumeric" ou "IAddable" (...) teríamos pelo menos ser capaz de escrever um genérico min, max, a seiva e com base no que braçadeira, maprange, bezier (...). No entanto, não seria rápido. Você não quer isso.

maneiras de corrigir isto: Desde .NET faz a compilação JIT de qualquer maneira também gera código diferente para List<int> do que para List<MyClass> (devido às diferenças de valor e tipos de referência) que provavelmente não gostaria de acrescentar que muito de uma sobrecarga para também gerar código diferente para as peças polimórficos ad hoc. A linguagem C # só precisa uma maneira de expressá-la. Uma maneira é o que você desenhou-se.

Outra maneira seria adicionar restrições de tipo para a função usando uma função polimórfica ad hoc:

    U SuperSquare<T, U>(T a) applying{ 
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    }
    {
        return Foo(a * a);
    }

Claro que você pode acabar com mais e mais restrições na aplicação Bar que usa Foo. Então você pode querer um mecanismo para dar um nome a vários constrangimentos que você usa regularmente ... No entanto, este novo é o açúcar e uma maneira de abordá-lo seria apenas para usar o conceito typeclass ...

Dar um nome a várias restrições é como definir uma classe tipo, mas eu gostaria de olhar apenas para ele como algum tipo de mecanismo abreviatura - açúcar para uma coleção arbitrária de restrições de tipo de função:

    // adhoc is like an interface: it is about collecting signatures
    // but it is not a type: it dissolves during compilation 
    adhoc AMyNeeds<T, U>
    {
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    } 

    U SuperSquare<T, U>(T a) applying AMyNeeds<T, U>        
    {
        return Foo(a * a);
    }

Em algum lugar "principais" todos os argumentos de tipo são conhecidos e tudo se torna concreto e pode ser compilado em conjunto.

O que falta ainda é a falta de criação de diferentes implementações. No exemplo superior nós apenas usado funções polimórficas e deixar todo mundo sabe ...

Implementação em seguida, novamente poderia seguir o caminho de métodos de extensão - na sua capacidade de adicionar funcionalidade a qualquer classe em qualquer ponto:

 public static class SomeAdhocImplementations
 {
    public nonvirtual int Foo(float x)
    {
        return round(x);
    }
 }

Na principal, você agora pode escrever:

    int a = SuperSquare(3.0f); // 3.0 * 3.0 = 9.0 rounded should return 9

O compilador verifica todos os "não-virtuais" ad hoc funções, encontra tanto um operador de built-in float (*) e uma int Foo (float) e, portanto, é capaz de compilar essa linha.

polimorfismo ad hoc do curso vem com a desvantagem que você tem que recompilar para cada tipo de tempo de compilação para que as implementações corretas se inserido. E, provavelmente, IL não suporta o que está sendo colocado em uma dll. Mas talvez eles trabalhar nele de qualquer maneira ...

Eu não vejo nenhuma necessidade real de instanciação de uma construção tipo classe. Se alguma coisa iria falhar na compilação obtemos os erros dos constrangimentos ou se aqueles que foram boundled juntamente com um "adhoc" codeclock a mensagem de erro poderia ficar ainda mais legível.

    MyColor a = SuperSquare(3.0f); 
    // error: There are no ad hoc implementations of AMyNeeds<float, MyColor> 
    // in particular there is no implementation for MyColor Foo(float)

Mas, claro, também a instanciação de uma classe / tipo de "interface de polimorfismo ad hoc" é pensável. A mensagem de erro, então, afirmar: "The AMyNeeds constraint of SuperSquare has not been matched. AMyNeeds is available as StandardNeeds : AMyNeeds<float, int> as defined in MyStandardLib". Também seria possível colocar a implementação em uma classe em conjunto com outros métodos e adicionar o "adhoc de interface" para a lista de interfaces suportadas.

Mas independente do projeto de linguagem especial: eu não vejo a desvantagem de adicioná-los de uma forma ou de outra. Salvar linguagens de tipagem estática sempre precisa empurrar o limite do poder expressivo, desde que começaram, permitindo que muito pouco, o que tende a ser um conjunto menor de poder expressivo uma normaal programador teria esperado para ser possível ...

TLDR: estou do seu lado. Coisas como esta suga principais linguagens de tipagem estática. Haskell mostrou o caminho.

O que é o raciocínio contra typeclasses?

complexidade de implementação para escritores de compiladores é sempre uma preocupação quando se considera novos recursos de linguagem. C ++ já cometi esse erro e já sofreu anos de buggy de C ++ compiladores como conseqüência.

Interfaces são à base de herança e, portanto, relativamente lento devido a engano e, além disso, não há possibilidade de deixar um tipo existente implementá-las

Não é verdade. Olhada sistema de objetos estruturalmente digitado do OCaml, por exemplo:

# let foo obj = obj#bar;;
val foo : < bar : 'a; .. > -> 'a = <fun>

Essa função foo aceita qualquer objeto de qualquer tipo que fornece o método bar necessário.

Mesmo para sistema de módulos de ordem superior do ML. De fato, há mesmo uma equivalência formal entre isso e classes tipo. Na prática, classes tipo são melhores para abstrações de pequena escala, como a sobrecarga de operador enquanto módulos de ordem superior são melhores para abstrações de grande escala, tais como parametrização de Okasaki de listas catenable mais filas.

Será que eles têm sérias desvantagens afinal?

Olhe para o seu próprio exemplo, aritmética genérico. F # realmente pode já lidar com esse caso especial agradecimento à interface INumeric. A F # tipo Matrix ainda usa essa abordagem.

No entanto, você só substituiu o código de máquina para add com expedição dinâmica para uma função separada, tornando ordens aritméticas de magnitude mais lenta. Para a maioria das aplicações, ou seja inutilmente lento. Você pode resolver esse problema através da realização de otimizações programa inteiro, mas que tem desvantagens óbvias. Além disso, há pouca semelhança entre os métodos numéricos para int vs float devido à robustez numérica para que a sua abstração também é praticamente inútil.

A questão deve certamente ser: alguém pode fazer um caso convincente para a adoção de classes tipo

?

Mas ainda não "dominante-language" fornece [classes tipo.]

Quando essa pergunta foi feita, isso pode ter sido verdade. Hoje, há um interesse muito forte em linguagens como Haskell e Clojure. Haskell tem classes de tipo (class / instance), Clojure 1.2+ tem protocolos (defprotocol / extend).

O que é o raciocínio contra [classes tipo]?

Eu não acho que as classes de tipo são objetivamente "pior" do que outros mecanismos de polimorfismo; eles apenas seguem uma abordagem diferente. Portanto, a verdadeira questão é, eles se encaixam bem em uma linguagem de programação particular?

Vamos brevemente considerar como classes tipo são diferentes interfaces em linguagens como Java ou C #. Em línguas, uma classe só suporta interfaces que são explicitamente mencionados e implementados nessa classe definição. classes tipo, no entanto, são interfaces que podem ser posteriormente anexadas a qualquer tipo já definido, mesmo em outro módulo. Este tipo de tipo de extensibilidade é, obviamente, bastante diferente dos mecanismos em determinadas "mainstream" linguagens OO.


agora considerar classes tipo para alguns tradicional linguagens de programação. Let

Haskell :. Não há necessidade de dizer que este idioma tem classes tipo

Clojure :. Como dito acima, Clojure tem algo como classes tipo na forma de protocolos

C ++ :. Como você mesmo disse, conceitos foram lançadas a partir do C ++ 11 especificação

Pelo contrário: Conceitos, que são uma ideia bastante analógica, foram excluídos da próxima C ++

Eu não seguiram todo o debate em torno desta decisão. Pelo que tenho lido, conceitos não eram "pronto ainda": Há ainda um debate sobre mapas conceituais. No entanto, os conceitos não foram desistido completamente, espera-se que eles vão fazê-lo na próxima versão do C ++.

C # : Com uma linguagem versão 3, C # tem, essencialmente, tornar-se um híbrido dos paradigmas de orientação a objetos e programação funcional. Uma adição foi feita para o idioma que é conceitualmente muito semelhante ao tipo classes: métodos de extensão . A principal diferença é que você é (aparentemente) anexando novos métodos para um tipo existente, não interfaces.

(Concedido, o mecanismo de método de extensão não é tão elegante como sintaxe instance … where de Haskell. Métodos extensões não são "verdadeiramente" ligado a um tipo, eles são implementados como uma transformação sintática. No final, no entanto, isso não faz uma diferença muito grande prático.)

Eu não acho que isso vai acontecer tão cedo - a linguagem Designers provavelmente não vai mesmo adicionar extensão propriedades para o idioma e extensão Interfaces faria mesmo ir um passo além do que isso.

( VB.NET : Microsoft tem sido "co-evolução" do C # e VB.NET línguas por algum tempo, por isso as minhas declarações sobre C # acontecer de ser válido para VB.NET, também. )

Java : Eu não sei Java muito bem, mas as linguagens C ++, C # e Java, é provavelmente a "mais pura" linguagem OO. Eu não vejo como classes tipo caberia naturalmente para essa linguagem.

F # : Eu encontrei um post no fórum explicando por classes tipo pode nunca ter introduzido na F # . Que os centros de explicação em torno do fato de que F # tem um nominativas, e não um sistema de tipo estrutural. (Embora eu não estou certo se esta é uma razão suficiente para F # não ter aulas de tipo.)

Tente definir um matróide, que é o que fazemos (logcally e não por via oral dizer uma matróide), e ainda é provavelmente algo como um struct C. Liskov princípio (último medalhista de turing) fica muito abstrato, muito categórica, muito teórico, menos o tratamento real de dados e de classe sistema teórico mais pura, para resolução de problemas pragmática handson, brevemente olhou-o que parecia PROLOG, código sobre o código sobre o código sobre o código ... enquanto um algoritmo descreve seqüências e travelsals entendemos em papel ou lousa. Depende de qual objetivo que você tem, resolvendo problema com código mínimo ou mais abstrato.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top