Qual é a diferença entre traços em Rust e classes de letras em Haskell?

157

Traços em Rust parecem pelo menos superficialmente semelhantes às classes de letras em Haskell, no entanto, vi pessoas escreverem que existem algumas diferenças entre eles. Fiquei me perguntando exatamente quais são essas diferenças.

LogicChains
fonte
8
Eu não sei muito sobre Rust. Porém, obstáculos comuns para tecnologias semelhantes em outros idiomas são de tipos mais altos (por exemplo, os traços podem variar entre tipos parametrizados, mas não os parâmetros?) E o polimorfismo do tipo retorno (por exemplo, um tipo de traço pode aparecer no resultado de uma função, mas não em qualquer lugar nos argumentos?). Um exemplo do primeiro em Haskell é class Functor f where fmap :: (a -> b) -> (f a -> f b); um exemplo do último é class Bounded a where maxBound :: a.
Daniel Wagner
4
O GHC também suporta classes de tipos com vários parâmetros (ou seja, características que envolvem vários tipos) e dependências funcionais, embora isso não faça parte da especificação oficial de Haskell. A julgar pela sintaxe Rust sugerida no seu link, ele pode suportar apenas características que variam de um tipo por vez, embora esse julgamento não seja baseado em uma experiência profunda.
Daniel Wagner
4
@DanielWagner Existe um polimorfismo de retorno (por exemplo std::default), e traços multiparâmetros funcionam como um tipo de trabalho (incluindo um análogo de dependências funcionais), embora o AFAIK precise solucionar o primeiro parâmetro privilegiado. No entanto, HKT. Eles estão na lista de desejos de futuro distante, mas ainda não estão no horizonte.
4
Outra diferença é o tratamento de instâncias órfãs. Rust tenta ter regras de coerência mais rígidas sobre onde um novo implemento para uma característica pode ser escrito. Veja esta discussão para mais detalhes (em particular aqui )
Paolo Falabella
1
O Rust agora suporta tipos associados e restrições de igualdade , embora não sejam tão poderosos quanto as famílias de tipos de Haskell. Também possui tipos existenciais por meio de objetos de características .
Fada Lambda

Respostas:

61

No nível básico, não há muita diferença, mas eles ainda estão lá.

Haskell descreve funções ou valores definidos em uma classe de tipo como 'métodos', assim como os traços descrevem os métodos OOP nos objetos que eles incluem. No entanto, Haskell lida com isso de maneira diferente, tratando-os como valores individuais, em vez de fixá-los a um objeto, como o OOP levaria a pessoa a fazer. Essa é a diferença mais óbvia no nível da superfície que existe.

A única coisa que Rust não pôde fazer por um tempo foram os caracteres tipificados de ordem superior , como as infames Functore as Monadclasses tipográficas.

Isso significa que os traços de Rust só poderiam descrever o que é chamado de "tipo concreto", em outras palavras, um sem argumento genérico. Desde o início, Haskell pode criar classes de ordem superior, que usam tipos semelhantes a como as funções de ordem superior usam outras funções: usar uma para descrever outra. Por um período de tempo, isso não foi possível no Rust, mas desde que os itens associados foram implementados, esses traços se tornaram comuns e idiomáticos.

Portanto, se ignorarmos as extensões, elas não serão exatamente iguais, mas cada uma poderá se aproximar do que a outra pode fazer.

Também é mencionável, como dito nos comentários, que o GHC (compilador principal de Haskell) suporta mais opções para classes de tipos, incluindo classes de tipos multiparâmetros (isto é, muitos tipos envolvidos) e dependências funcionais , uma opção adorável que permite cálculos em nível de tipo , e leva a digitar famílias . Que eu saiba, Rust não tem departamentos divertidos nem famílias de tipos, embora possa no futuro. †

Em suma, traços e classes de tipos têm diferenças fundamentais, que devido à maneira como interagem, os fazem agir e parecem bastante semelhantes no final.


† Um bom artigo sobre as classes de tipo de Haskell (incluindo as de tipo mais alto) pode ser encontrado aqui , e o capítulo Rust by Example sobre características pode ser encontrado aqui

AJFarmar
fonte
1
A ferrugem ainda não possui nenhuma forma de tipos superiores. "Infame" precisa de justificativa. Functor é incrivelmente difundido e útil como conceito. As famílias de tipos são iguais aos tipos associados. Dependências funcionais são essencialmente redundantes com tipos associados (inclusive em Haskell). A coisa que Rust não tem. fundeps são anotações de injetividade. Você tem isso de trás para frente, os traços de Rust e as classes de tipo de Haskell são diferentes na superfície, mas muitas diferenças evaporam quando você olha para baixo. Diferenças que permanecem são na maior parte inerente aos diferentes domínios das línguas operam.
Centril
Itens associados agora são considerados idomatic em muitas circunstâncias, certo?
Vaelus 14/10/19
@ Vaelus Você está certo - esta resposta deve ser atualizada um pouco. Editando agora.
AJFarmar 14/10/19
19

Penso que as respostas atuais ignoram as diferenças mais fundamentais entre os traços de Rust e as classes do tipo Haskell. Essas diferenças têm a ver com a maneira como os traços estão relacionados às construções de linguagem orientada a objetos. Para obter informações sobre isso, consulte o livro Rust .

  1. Uma declaração de característica cria um tipo de característica . Isso significa que você pode declarar variáveis ​​desse tipo (ou melhor, referências do tipo). Você também pode usar tipos de características como parâmetros em funções, campos struct e instanciações de parâmetros de tipo.

    Uma variável de referência de característica pode, em tempo de execução, conter objetos de tipos diferentes, desde que o tipo de tempo de execução do objeto referenciado implemente a característica.

    // The shape variable might contain a Square or a Circle, 
    // we don't know until runtime
    let shape: &Shape = get_unknown_shape();
    
    // Might contain different kinds of shapes at the same time
    let shapes: Vec<&Shape> = get_shapes();
    

    Não é assim que as classes de tipos funcionam. As classes de tipos não criam tipos ; portanto, você não pode declarar variáveis ​​com o nome da classe. As classes de tipo agem como limites nos parâmetros de tipo, mas os parâmetros de tipo devem ser instanciados com um tipo concreto, não com a própria classe de tipo.

    Você não pode ter uma lista de coisas diferentes de tipos diferentes que implementam a mesma classe de tipo. (Em vez disso, os tipos existenciais são usados ​​no Haskell para expressar uma coisa semelhante.) Nota 1

  2. Os métodos de características podem ser despachados dinamicamente . Isso está fortemente relacionado às coisas descritas na seção acima.

    A expedição dinâmica significa que o tipo de tempo de execução do objeto que uma referência aponta é usado para determinar qual método é chamado pela referência.

    let shape: &Shape = get_unknown_shape();
    
    // This calls a method, which might be Square.area or
    // Circle.area depending on the runtime type of shape
    print!("Area: {}", shape.area());
    

    Novamente, tipos existenciais são usados ​​para isso em Haskell.

Em conclusão

Parece-me que características são, em muitos aspectos, o mesmo conceito que classes de tipo. Além disso, eles têm a funcionalidade de interfaces orientadas a objetos.

Por outro lado, as classes de tipo de Haskell são mais avançadas. Haskell tem, por exemplo, tipos e extensões de tipo mais alto, como classes de tipo com vários parâmetros.


Nota 1 : As versões recentes do Rust possuem uma atualização para diferenciar o uso de nomes de características como tipos e o uso de nomes de características como limites. Em um tipo de característica, o nome é prefixado pela dynpalavra - chave. Veja, por exemplo, esta resposta para mais informações.

Lii
fonte
2
"Classes de tipos não criam tipos" - acho melhor entender dyn Traitcomo uma forma de digitação existencial, pois elas se relacionam a traços / classes de tipos. Podemos considerar dynum operador dentro dos limites projetando-os para tipos, ie dyn : List Bound -> Type. Tomando essa idéia para Haskell, e com respeito aos "então você não pode declarar variáveis com o nome da classe", podemos fazer isso indiretamente em Haskell: data Dyn (c :: * -> Constraint) = forall (t :: Type). c t => D t. Tendo definido isso, podemos trabalhar [D True, D "abc", D 42] :: [D Show].
Centril 11/04/19
8

As "características" de Rust são análogas às classes de tipo de Haskell.

A principal diferença com Haskell é que os traços intervêm apenas para expressões com notação de ponto, ou seja, da forma a.foo (b).

As classes do tipo Haskell se estendem aos tipos de ordem superior. As características de ferrugem apenas não suportam tipos de ordem superior porque estão ausentes em todo o idioma, ou seja, não é uma diferença filosófica entre características e classes de tipo

Anuj Gupta
fonte
1
Os traços no Rust "não intervêm apenas para expressões com notação de ponto". Por exemplo, considere a Defaultcaracterística que não possui métodos, apenas funções associadas a outros métodos.
Centril