¿Cuál es la diferencia entre los rasgos en Rust y las clases de tipos en Haskell?

157

Los rasgos en Rust parecen al menos superficialmente similares a las clases tipográficas en Haskell, sin embargo, he visto a personas escribir que hay algunas diferencias entre ellos. Me preguntaba exactamente cuáles son estas diferencias.

Cadenas lógicas
fuente
8
No sé mucho sobre Rust. Pero los escollos comunes para tecnologías similares en otros idiomas son tipos superiores (p. Ej., ¿Los rasgos pueden variar sobre tipos parametrizados, pero no sus parámetros?) Y el polimorfismo de tipo de retorno (p. Ej., ¿Puede aparecer un tipo de rasgo en el resultado de una función, pero no en ningún lugar en los argumentos? Un ejemplo de lo primero en Haskell es class Functor f where fmap :: (a -> b) -> (f a -> f b); Un ejemplo de esto último es class Bounded a where maxBound :: a.
Daniel Wagner
44
GHC también admite clases de tipos de parámetros múltiples (es decir, rasgos que involucran varios tipos) y dependencias funcionales, aunque esto no es parte de la especificación oficial de Haskell. A juzgar por la sintaxis de Rust sugerida en su enlace, solo puede admitir rasgos que varían sobre un tipo a la vez, aunque ese juicio nuevamente no se basa en una experiencia profunda.
Daniel Wagner
44
@DanielWagner Existe un polimorfismo de tipo de retorno (p std::default. Ej. ), Y los rasgos multiparamétricos tipo de trabajo (incluido un análogo de dependencias funcionales), aunque AFAIK uno necesita evitar el primer parámetro que se privilegia. Sin embargo, no hay HKT. Están en la lista de deseos del futuro lejano pero aún no en el horizonte.
44
Otra diferencia es el tratamiento de casos huérfanos. Rust intenta tener reglas de coherencia más estrictas sobre dónde se puede escribir una nueva implicación para un rasgo. Vea esta discusión para más detalles (en particular aquí )
Paolo Falabella
1
Rust admite tipos asociados y restricciones de igualdad ahora, aunque no son tan poderosos como las familias de tipos de Haskell. También tiene tipos existenciales a través de objetos de rasgos .
Lambda Fairy

Respuestas:

61

En el nivel básico, no hay mucha diferencia, pero todavía están allí.

Haskell describe funciones o valores definidos en una clase de tipos como 'métodos', del mismo modo que los rasgos describen métodos OOP en los objetos que encierran. Sin embargo, Haskell se ocupa de estos de manera diferente, tratándolos como valores individuales en lugar de fijarlos a un objeto como OOP lo llevaría a hacerlo. Esta es la diferencia de nivel de superficie más obvia que existe.

Lo único que Rust no pudo hacer por un tiempo fueron los rasgos mecanografiados de orden superior , como las clases infames Functory Monadtipográficas.

Esto significa que los rasgos de óxido solo podrían describir lo que a menudo se llama un "tipo concreto", en otras palabras, uno sin un argumento genérico. Haskell desde el principio podría crear clases de tipos de orden superior que usan tipos similares a cómo las funciones de orden superior usan otras funciones: usar una para describir otra. Durante un período de tiempo esto no fue posible en Rust, pero desde que se implementaron los elementos asociados , estos rasgos se han vuelto comunes e idiomáticos.

Entonces, si ignoramos las extensiones, no son exactamente las mismas, pero cada una puede aproximarse a lo que la otra puede hacer.

También es mencionable, como se dijo en los comentarios, que GHC (compilador principal de Haskell) admite más opciones para clases de tipos, incluidas clases de tipos de parámetros múltiples (es decir, muchos tipos involucrados) y dependencias funcionales , una opción encantadora que permite cálculos a nivel de tipo , y lleva a escribir familias . Que yo sepa, Rust no tiene funDeps ni familias de tipos, aunque puede que en el futuro. †

En general, los rasgos y las clases de tipos tienen diferencias fundamentales que, debido a la forma en que interactúan, los hacen actuar y parecer bastante similares al final.


† Puede encontrar un buen artículo sobre las clases de tipos de Haskell (incluidas las de tipo superior) aquí , y el capítulo Rust by Example sobre rasgos puede encontrarse aquí

AJFarmar
fuente
1
La herrumbre todavía no tiene ninguna forma de tipos amables superiores. "Infame" necesita justificación. Functor es increíblemente dominante y útil como concepto. Las familias de tipos son iguales a los tipos asociados. Las dependencias funcionales son esencialmente redundantes con los tipos asociados (incluso en Haskell). La cosa que Rust carece de wrt. fundeps es anotaciones de inyectividad. Lo tienes al revés, los rasgos de Rust y las clases de tipo de Haskell son diferentes en la superficie, pero muchas diferencias se evaporan cuando miras debajo. Las diferencias que quedan son inherentes a los diferentes dominios en los que operan los idiomas.
Centril
Los artículos asociados ahora se consideran idomáticos en muchas circunstancias, ¿verdad?
Vaelus
@Vaelus Tienes razón: esta respuesta debe actualizarse un poco. Editando ahora.
AJFarmar
19

Creo que las respuestas actuales pasan por alto las diferencias más fundamentales entre los rasgos de óxido y las clases de tipo Haskell. Estas diferencias tienen que ver con la forma en que los rasgos están relacionados con las construcciones de lenguaje orientado a objetos. Para obtener información sobre esto, consulte el libro Rust .

  1. Una declaración de rasgo crea un tipo de rasgo . Esto significa que puede declarar variables de ese tipo (o más bien, referencias del tipo). También puede usar tipos de rasgos como parámetros en funciones, campos de estructura e instancias de parámetros de tipo.

    Una variable de referencia de rasgo puede en tiempo de ejecución contener objetos de diferentes tipos, siempre que el tipo de tiempo de ejecución del objeto referenciado implemente el rasgo.

    // 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();

    No es así como funcionan las clases de tipos. Las clases de tipos no crean tipos , por lo que no puede declarar variables con el nombre de la clase. Las clases de tipos actúan como límites en los parámetros de tipo, pero los parámetros de tipo deben instanciarse con un tipo concreto, no con la clase de tipo en sí.

    No puede tener una lista de diferentes cosas de diferentes tipos que implementan la misma clase de tipo. (En cambio, los tipos existenciales se usan en Haskell para expresar algo similar). Nota 1

  2. Los métodos de rasgos pueden ser enviados dinámicamente . Esto está fuertemente relacionado con las cosas que se describen en la sección anterior.

    El despacho dinámico significa que el tipo de tiempo de ejecución del objeto al que apunta una referencia se usa para determinar qué método se llama a través de la referencia.

    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());

    Nuevamente, los tipos existenciales se usan para esto en Haskell.

En conclusión

Me parece que los rasgos son en muchos aspectos el mismo concepto que las clases de tipos. Además, tienen la funcionalidad de interfaces orientadas a objetos.

Por otro lado, las clases de tipos de Haskell son más avanzadas. Haskell tiene, por ejemplo, tipos y extensiones de tipo superior, como clases de tipos de parámetros múltiples.


Nota 1 : Las versiones recientes de Rust tienen una actualización para diferenciar el uso de nombres de rasgos como tipos y el uso de nombres de rasgos como límites. En un tipo de rasgo, el nombre está prefijado por la dynpalabra clave. Consulte, por ejemplo, esta respuesta para obtener más información.

Lii
fuente
2
"Las clases de tipos no crean tipos": creo que es mejor entenderlo dyn Traitcomo una forma de escritura existencial en relación con los rasgos / clases de tipos. Podemos considerar dynun operador en los límites que los proyecta a tipos, es decir dyn : List Bound -> Type. Tomando esta idea a Haskell, y en lo que respecta a "por lo que no se puede declarar variables con el nombre de clase", podemos hacerlo indirectamente en Haskell: data Dyn (c :: * -> Constraint) = forall (t :: Type). c t => D t. Habiendo definido esto, podemos trabajar con [D True, D "abc", D 42] :: [D Show].
Centril
8

Los "rasgos" de Rust son análogos a las clases de tipos de Haskell.

La principal diferencia con Haskell es que los rasgos solo intervienen para expresiones con notación de puntos, es decir, de la forma a.foo (b).

Las clases de tipo Haskell se extienden a tipos de orden superior. Los rasgos de óxido solo no admiten tipos de orden superior porque faltan en todo el lenguaje, es decir, no es una diferencia filosófica entre rasgos y clases de tipos

Anuj Gupta
fuente
1
Los rasgos en Rust no "intervienen solo para expresiones con notación de puntos". Por ejemplo, considere el Defaultrasgo que no tiene métodos, solo funciones asociadas sin métodos.
Centril