Qual è la differenza tra le classi di tipi di Haskell e le interfacce di Go?

8

Mi chiedo se ci sia una differenza tra le classi di tipi di Haskell e le interfacce di Go. Entrambi definiscono i tipi in base alle funzioni, in questo modo, che un valore corrisponde a un tipo, se per il valore è definita una funzione richiesta dal tipo.

Ci sono delle differenze o ci sono solo due nomi per la stessa cosa?

    
posta ceving 20.12.2016 - 15:15
fonte

3 risposte

5

I due concetti sono molto simili. Nei normali linguaggi OOP, colleghiamo un vtable (o per le interfacce: itable) a ciascun oggetto:

| this
v
+---+---+---+
| V | a | b | the object with fields a, b
+---+---+---+
  |
  v
 +---+---+---+
 | o | p | q | the vtable with method slots o(), p(), q()
 +---+---+---+

Questo ci permette di invocare metodi simili a this->vtable.p(this) .

In Haskell, la tabella dei metodi è più simile a un argomento nascosto implicito:

method :: Class a => a -> a -> Int

assomiglierebbe alla funzione C ++

template<typename A>
int method(Class<A>*, A*, A*)

dove Class<A> è un'istanza di typeclass Class per type A . Un metodo sarebbe invocato come

typeclass_instance->p(value_ptr);

L'istanza è separata dai valori. I valori mantengono ancora il loro tipo effettivo. Mentre i typeclass permettono un certo polimorfismo, questo non è il polimorfismo di sottotipizzazione. Ciò rende impossibile creare un elenco di valori che soddisfano un Class . Per esempio. supponendo che abbiamo instance Class Int ... e instance Class String ... , non possiamo creare un tipo di lista eterogeneo come [Class] che ha valori come [42, "foo"] . (Ciò è possibile quando si utilizza l'estensione "tipi esistenziali", che passa in effetti all'approccio Vai).

In Go, un valore non implementa un set fisso di interfacce. Di conseguenza non può avere un puntatore vtable. Invece, i puntatori ai tipi di interfaccia sono implementati come indicatori di grasso che includono un puntatore ai dati, un altro puntatore all'endable:

    'this' fat pointer
    +---+---+
    |   |   |
    +---+---+
 ____/    \_________
v                   v
+---+---+---+       +---+---+
| o | p | q |       | a | b | the data with
+---+---+---+       +---+---+ fields a, b
itable with method
slots o(), p(), q()

this.itable->p(this.data_ptr)

L'itable è combinato con i dati in un puntatore grasso quando si esegue il cast da un valore normale a un tipo di interfaccia. Una volta che hai un tipo di interfaccia, il tipo effettivo di dati è diventato irrilevante. Infatti, non è possibile accedere direttamente ai campi senza passare attraverso i metodi o downcasting dell'interfaccia (che potrebbe non riuscire).

L'approccio di Go alla distribuzione dell'interfaccia ha un costo: ogni puntatore polimorfico è due volte più grande di un normale puntatore. Inoltre, la trasmissione da un'interfaccia all'altra comporta la copia dei puntatori del metodo su un nuovo vtable. Ma una volta che abbiamo costruito l'itable, questo ci permette di spedire a buon mercato le chiamate di metodo a molte interfacce, qualcosa a cui le tradizionali lingue OOP soffrono. Qui m è il numero di metodi nell'interfaccia di destinazione e b è il numero di classi di base:

  • C ++ affetta gli oggetti o ha bisogno di inseguire i puntatori di ereditarietà virtuale durante la trasmissione, ma ha un semplice accesso vtable. O (1) o O (b) costo del upcasting, ma O (1) invio del metodo.
  • La Java Hotspot VM non deve fare nulla durante l'upcasting, ma dopo la ricerca del metodo di interfaccia esegue una ricerca lineare attraverso tutti gli oggetti utilizzabili da quella classe. O (1) upcasting, ma invio di metodo O (b).
  • Python non deve fare nulla durante l'upcasting, ma usa una ricerca lineare attraverso un elenco di classi base linearizzate C3. O (1) upcasting, ma invio del metodo O (b²)? Non sono sicuro di quale sia la complessità algoritmica di C3.
  • .NET CLR utilizza un approccio simile a Hotspot ma aggiunge un altro livello di riferimento indiretto nel tentativo di ottimizzare l'utilizzo della memoria. O (1) upcasting, ma invio di metodo O (b).

La complessità tipica per la distribuzione dei metodi è molto migliore poiché spesso la ricerca dei metodi può essere memorizzata nella cache, ma le complessità del caso peggiore sono piuttosto orribili.

In confronto, Go ha O (1) o O (m) upcasting e l'invio del metodo O (1). Haskell non ha upcasting (vincolare un tipo con una classe di tipo è un effetto in fase di compilazione) e un invio di metodo O (1).

    
risposta data 20.12.2016 - 16:07
fonte
7

Ci sono molte differenze

  1. I typeclass Haskell sono nominativamente nominati - devi dichiarare che Maybe è un Monad . Le interfacce go sono tipizzate in modo strutturale: se circle dichiara area() float64 e quindi square allora entrambe sono nell'interfaccia shape automaticamente.
  2. Haskell (con estensioni GHC) ha Classi di tipo multi parametro e (come il mio Maybe a esempio) classi di tipi per tipi più elevati. Go non ha equivalenti per questi.
  3. In Haskell, le classi di tipi vengono utilizzate con un polimorfismo associato che offre vincoli inesprimibili con Go. Ad esempio + :: Num a => a -> a -> a , che garantisce che non proverai ad aggiungere virgola mobile e quaternioni, non è indicativo in Vai.
risposta data 20.12.2016 - 15:29
fonte
3

Sono completamente diversi. Le interfacce Go definiscono un protocollo per i valori, le classi di tipi Haskell definiscono un protocollo per i tipi. (È per questo che sono chiamate "classi di tipi", dopotutto classificano i tipi, a differenza delle classi OO (o delle interfacce di Go), che classificano i valori.)

Le interfacce Go sono solo noiose vecchie tipizzazioni strutturali, nient'altro.

    
risposta data 20.12.2016 - 15:56
fonte

Leggi altre domande sui tag