Qual è la differenza tra i tratti in Rust e quelli in Haskell?


157

I tratti di Rust sembrano almeno superficialmente simili alle macchine da scrivere di Haskell, tuttavia ho visto persone scrivere che ci sono alcune differenze tra loro. Mi chiedevo esattamente quali siano queste differenze.


8
Non so molto di Rust. Ma i comuni ostacoli per tecnologie simili in altre lingue sono tipi più elevati (ad es. I tratti possono variare rispetto ai tipi parametrizzati, ma non i loro parametri?) E polimorfismo del tipo di ritorno (ad es. Un tipo di tratto può apparire nel risultato di una funzione, ma non ovunque negli argomenti?). Un esempio del primo in Haskell è class Functor f where fmap :: (a -> b) -> (f a -> f b); un esempio di quest'ultimo è class Bounded a where maxBound :: a.
Daniel Wagner,

4
GHC supporta anche classi di tipi multiparametriche (ovvero tratti che coinvolgono diversi tipi) e dipendenze funzionali, sebbene ciò non faccia parte delle specifiche ufficiali di Haskell. A giudicare dalla sintassi Rust suggerita al tuo link, può supportare solo tratti che vanno oltre un tipo alla volta, anche se quel giudizio non è ancora basato su una profonda esperienza.
Daniel Wagner,

4
@DanielWagner Esiste un polimorfismo di tipo restituito (ad es. std::default), E tratti di multiparametro come una sorta di lavoro (incluso un analogo di dipendenze funzionali), sebbene AFAIK si debba aggirare il primo parametro privilegiato. Nessun HKT comunque. Sono nella lista dei desideri di un futuro lontano ma non ancora all'orizzonte.

4
un'altra differenza è il trattamento delle istanze orfane. Rust cerca di avere regole di coerenza più rigorose su dove può essere scritto un nuovo impl per un tratto. Vedi questa discussione per maggiori dettagli (in particolare qui )
Paolo Falabella,

1
Rust ora supporta tipi associati e vincoli di uguaglianza , anche se non sono potenti come le famiglie di tipi di Haskell. Ha anche tipi esistenziali tramite oggetti tratto .
Lambda Fairy,

Risposte:


61

A livello base, non c'è molta differenza, ma sono ancora lì.

Haskell descrive funzioni o valori definiti in una typeclass come 'metodi', proprio come i tratti descrivono i metodi OOP negli oggetti che racchiudono. Tuttavia, Haskell si occupa di questi in modo diverso, trattandoli come valori individuali anziché fissarli a un oggetto come OOP porterebbe a fare. Si tratta della differenza di livello superficiale più evidente che ci sia.

L'unica cosa che Rust non poteva fare per un po 'erano tratti tipizzati di ordine superiore , come il famigerato Functore le Monadmacchine da scrivere.

Ciò significa che i tratti Rust potrebbero solo descrivere quello che spesso viene chiamato un "tipo concreto", in altre parole, senza un argomento generico. Haskell sin dall'inizio potrebbe creare macchine da scrivere di ordine superiore che usano tipi simili a come le funzioni di ordine superiore usano altre funzioni: usarne una per descriverne un'altra. Per un periodo di tempo ciò non è stato possibile in Rust, ma da quando gli articoli associati sono stati implementati, tali tratti sono diventati banali e idiomatici.

Quindi, se ignoriamo le estensioni, non sono esattamente le stesse, ma ognuna può approssimare ciò che l'altra può fare.

È anche menzionabile, come detto nei commenti, che GHC (il compilatore principale di Haskell) supporta ulteriori opzioni per le typeclass, tra cui le typeclass multiparametriche (cioè molti tipi coinvolti) e le dipendenze funzionali , una deliziosa opzione che consente calcoli a livello di tipo e porta a digitare famiglie . Per quanto ne sappia, Rust non ha né divertimenti né famiglie di tipi, anche se potrebbe in futuro. †

Tutto sommato, i tratti e le macchine da scrivere hanno differenze fondamentali, che a causa del modo in cui interagiscono, le fanno agire e sembrare abbastanza simili alla fine.


† Un bell'articolo sulle macchine da scrivere di Haskell (comprese quelle di tipo più alto) può essere trovato qui , e il capitolo Rust by Example sui tratti può essere trovato qui


1
La ruggine non ha ancora alcuna forma di tipi più elevati. "Infame" ha bisogno di giustificazione. Functor è incredibilmente pervasivo e utile come concetto. Le famiglie di tipi sono uguali ai tipi associati. Le dipendenze funzionali sono essenzialmente ridondanti con i tipi associati (incluso in Haskell). La cosa a Rust manca di wrt. fundeps è annotazioni di iniettività. Ce l'hai all'indietro, i tratti di Rust e le classi di tipi di Haskell sono diversi in superficie ma molte differenze evaporano quando guardi sotto. Le differenze che rimangono sono per lo più inerenti ai diversi domini in cui operano le lingue.
Centril

Gli articoli associati sono ora considerati idomatici in molte circostanze, giusto?
Vaelus,

@Vaelus Hai ragione: questa risposta dovrebbe essere aggiornata un po '. Modifica ora.
AJFarmar

19

Penso che le risposte attuali trascurino le differenze fondamentali tra i tratti Rust e le classi di tipo Haskell. Queste differenze hanno a che fare con il modo in cui i tratti sono correlati a costrutti linguistici orientati agli oggetti. Per informazioni al riguardo, consultare il libro Rust .

  1. Una dichiarazione di tratto crea un tipo di tratto . Ciò significa che è possibile dichiarare variabili di tale tipo (o meglio, riferimenti del tipo). È inoltre possibile utilizzare i tipi di tratti come parametri su funzioni, campi struct e istanze di parametri di tipo.

    Una variabile di riferimento del tratto in fase di esecuzione può contenere oggetti di tipo diverso, purché il tipo di runtime dell'oggetto a cui viene fatto riferimento implementi il ​​tratto.

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

    Non è così che funzionano le classi di tipi. Le classi di tipi non creano tipi , quindi non è possibile dichiarare variabili con il nome della classe. Le classi di tipi agiscono come limiti sui parametri di tipo, ma i parametri di tipo devono essere istanziati con un tipo concreto, non con la classe di tipo stessa.

    Non puoi avere un elenco di cose diverse di tipi diversi che implementano la stessa classe di tipi. (Invece, i tipi esistenziali sono usati in Haskell per esprimere qualcosa di simile.) Nota 1

  2. I metodi di tratto possono essere inviati in modo dinamico . Questo è fortemente correlato alle cose che sono descritte nella sezione precedente.

    Invio dinamico significa che il tipo di runtime dell'oggetto a cui un punto di riferimento viene utilizzato per determinare quale metodo viene chiamato attraverso il riferimento.

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

    Ancora una volta, i tipi esistenziali sono usati per questo in Haskell.

In conclusione

Mi sembra che i tratti siano per molti aspetti lo stesso concetto delle classi di tipi. Inoltre, hanno la funzionalità di interfacce orientate agli oggetti.

D'altra parte, le classi di tipi di Haskell sono più avanzate. Haskell ha ad esempio tipi e estensioni di tipo superiore come classi di tipi multiparametriche.


Nota 1 : Le versioni recenti di Rust hanno un aggiornamento per differenziare l'uso dei nomi dei tratti come tipi e l'uso dei nomi dei tratti come limiti. In un tipo di tratto il nome è preceduto dalla dynparola chiave. Vedi ad esempio questa risposta per ulteriori informazioni.


2
"Le classi di tipi non creano tipi" - Penso che sia meglio capire dyn Traitcome una forma di tipizzazione esistenziale in quanto si riferiscono a tratti / classi di tipi. Possiamo considerare dynun operatore al limite proiettandolo su tipi, ad es dyn : List Bound -> Type. Alla luce di questa idea di Haskell, e per quanto riguarda "quindi non è possibile dichiarare le variabili con il nome della classe", possiamo farlo indirettamente in Haskell: data Dyn (c :: * -> Constraint) = forall (t :: Type). c t => D t. Dopo averlo definito, potremmo lavorare con [D True, D "abc", D 42] :: [D Show].
Centril,

8

I "tratti" di Rust sono analoghi alle classi di tipi di Haskell.

La differenza principale con Haskell è che i tratti intervengono solo per le espressioni con notazione punto, cioè della forma a.foo (b).

Le classi di tipi Haskell si estendono ai tipi di ordine superiore. I tratti ruggine non supportano solo tipi di ordine superiore perché mancano da tutta la lingua, cioè non è una differenza filosofica tra tratti e classi di tipi


1
I tratti in Rust non "intervengono solo per le espressioni con notazione a punti". Ad esempio, si consideri il Defaulttratto che non ha metodi, ma solo funzioni non associate al metodo.
Centril,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.