L'interfaccia di Java e la classe di tipo di Haskell: differenze e somiglianze?


112

Mentre sto imparando Haskell, ho notato la sua classe di tipo , che dovrebbe essere una grande invenzione che ha avuto origine da Haskell.

Tuttavia, nella pagina di Wikipedia sulla classe del tipo :

Il programmatore definisce una classe di tipo specificando un insieme di nomi di funzioni o costanti, insieme ai rispettivi tipi, che devono esistere per ogni tipo che appartiene alla classe.

Che mi sembra piuttosto vicino all'interfaccia di Java (citando la pagina dell'interfaccia di Wikipedia (Java) ):

Un'interfaccia nel linguaggio di programmazione Java è un tipo astratto che viene utilizzato per specificare un'interfaccia (nel senso generico del termine) che le classi devono implementare.

Questi due sembrano piuttosto simili: la classe di tipo limita il comportamento di un tipo, mentre l'interfaccia limita il comportamento di una classe.

Mi chiedo quali siano le differenze e le somiglianze tra la classe di tipo in Haskell e l'interfaccia in Java, o forse sono fondamentalmente diverse?

EDIT: ho notato che anche haskell.org ammette che sono simili . Se sono così simili (o lo sono?), Allora perché la classe di tipo è trattata con tanto clamore?

ALTRE MODIFICHE: Wow, tante ottime risposte! Immagino che dovrò lasciare che sia la comunità a decidere qual è il migliore. Tuttavia, leggendo le risposte, sembra che tutti dicano semplicemente che "ci sono molte cose che typeclass può fare mentre l'interfaccia non può o deve far fronte ai generici" . Non posso fare a meno di chiedermi, c'è qualcosa che le interfacce possono fare mentre le typeclasses no? Inoltre, ho notato che Wikipedia afferma che la classe di caratteri è stata originariamente inventata nel documento del 1989 * "Come rendere il polimorfismo ad-hoc meno ad hoc", mentre Haskell è ancora nella sua culla, mentre il progetto Java è stato avviato nel 1991 e rilasciato per la prima volta nel 1995 Quindi forse invece di typeclass essere simile alle interfacce, è il contrario, che le interfacce siano state influenzate dalla typeclass?Esistono documenti / documenti che supportano o confutano questo? Grazie per tutte le risposte, sono tutte molto illuminanti!

Grazie per tutti gli stimoli!


3
No, non c'è davvero nulla che le interfacce possano fare che le classi di tipo non possano fare, con l'avvertenza principale che le interfacce generalmente appaiono in linguaggi che hanno funzionalità incorporate non presenti in Haskell. Se le classi di tipo fossero aggiunte a Java, sarebbero in grado di utilizzare anche quelle funzionalità.
CA McCann

8
Se hai più domande, dovresti porre più domande, non cercare di raggrupparle tutte in un'unica domanda. Ad ogni modo, per rispondere alla tua ultima domanda: la principale influenza di Java è Objective-C (e non C ++ come spesso viene falsamente riportato), le cui principali influenze a loro volta sono Smalltalk e C. Le interfacce di Java sono un adattamento dei protocolli di Objective-C che sono di nuovo un formalizzazione dell'idea di protocollo in OO, che a sua volta si basa sull'idea di protocolli in rete, nello specifico ARPANet. Tutto questo è successo molto prima del giornale che hai citato. ...
Jörg W Mittag

1
... L'influenza di Haskell su Java è arrivata molto più tardi ed è limitata ai Generics, che sono stati, dopo tutto, co-progettati da uno dei designer di Haskell, Phil Wadler.
Jörg W Mittag

5
Questo è un articolo su Usenet di Patrick Naughton, uno dei progettisti originali di Java: Java era fortemente influenzato da Objective-C e non da C ++ . Sfortunatamente, è così vecchio che il messaggio originale non compare nemmeno negli archivi di Google.
Jörg W Mittag

8
C'è un'altra domanda che è stata chiusa come un duplicato esatto di questa, ma ha una risposta molto più approfondita: stackoverflow.com/questions/8122109/…
Ben

Risposte:


50

Direi che un'interfaccia è un po 'come una classe di tipo in SomeInterface tcui tutti i valori hanno il tipo t -> whatever(dove whatevernon contiene t). Questo perché con il tipo di relazione di ereditarietà in Java e linguaggi simili, il metodo chiamato dipende dal tipo di oggetto su cui vengono chiamati e nient'altro.

Ciò significa che è davvero difficile fare cose come add :: t -> t -> tcon un'interfaccia, dove è polimorfica su più di un parametro, perché non c'è modo per l'interfaccia di specificare che il tipo di argomento e il tipo di ritorno del metodo è lo stesso tipo del tipo di l'oggetto su cui è chiamato (cioè il tipo "self"). Con Generics, ci sono modi per simulare questo creando un'interfaccia con un parametro generico che dovrebbe essere lo stesso tipo dell'oggetto stesso, come come lo Comparable<T>fa, dove ci si aspetta che tu usi in Foo implements Comparable<Foo>modo che il compareTo(T otherobject)tipo di abbia tipo t -> t -> Ordering. Ma ciò richiede comunque al programmatore di seguire questa regola e causa anche mal di testa quando le persone vogliono creare una funzione che usi questa interfaccia, devono avere parametri di tipo generico ricorsivo.

Inoltre, non avrai cose come empty :: tperché non stai chiamando una funzione qui, quindi non è un metodo.


1
I tratti di Scala (fondamentalmente le interfacce) consentono this.type in modo da poter restituire o accettare parametri del "tipo self". Scala ha una caratteristica completamente separata che chiamano "tipi di sé" che non ha niente a che fare con questo. Niente di tutto questo è una differenza concettuale, solo una differenza di implementazioni.
argilla

45

Ciò che è simile tra le interfacce e le classi di tipo è che denominano e descrivono un insieme di operazioni correlate. Le operazioni stesse sono descritte tramite i loro nomi, input e output. Allo stesso modo potrebbero esserci molte implementazioni di queste operazioni che probabilmente differiranno nella loro implementazione.

Detto questo, ecco alcune differenze notevoli:

  • I metodi delle interfacce sono sempre associati a un'istanza di oggetto. In altre parole, c'è sempre un parametro "this" implicito che è l'oggetto su cui viene chiamato il metodo. Tutti gli input per una funzione di classe di tipo sono espliciti.
  • Un'implementazione dell'interfaccia deve essere definita come parte della classe che implementa l'interfaccia. Al contrario, una classe di tipo "istanza" può essere definita completamente separata dal suo tipo associato ... anche in un altro modulo.

In generale, penso sia giusto dire che le classi di tipi sono più potenti e flessibili delle interfacce. Come definireste un'interfaccia per convertire una stringa in un valore o un'istanza del tipo di implementazione? Non è certo impossibile, ma il risultato non sarebbe né intuitivo né elegante. Hai mai desiderato che fosse possibile implementare un'interfaccia per un tipo in qualche libreria compilata? Entrambi sono facili da realizzare con le classi di tipo.


1
Come estenderesti le classi di caratteri? Le classi di tipi possono estendere altre classi di tipi proprio come le interfacce possono estendere le interfacce?
CMCDragonkai

10
Potrebbe valere la pena aggiornare questa risposta alla luce delle implementazioni predefinite di Java 8 nelle interfacce.
Ripristina Monica il

1
@CMCDragonkai Sì, puoi ad esempio dire "class (Foo a) => Bar a where ..." per specificare che la classe di tipo Bar estende la classe di tipo Foo. Come Java, Haskell ha eredità multipla qui.
Ripristina Monica il

In questo caso la classe di tipo non è uguale ai protocolli in Clojure ma con l'indipendenza dai tipi?
nawfal

24

Le classi di tipo sono state create come un modo strutturato per esprimere "polimorfismo ad-hoc", che è fondamentalmente il termine tecnico per le funzioni sovraccariche . Una definizione di classe di tipo ha un aspetto simile a questo:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

Ciò significa che, quando si utilizza applica la funzione fooad alcuni argomenti di un tipo che appartengono alla classe Foobar, viene cercata un'implementazione foospecifica di quel tipo e la utilizza. Questo è molto simile alla situazione con l'overload di operatori in linguaggi come C ++ / C #, tranne che più flessibili e generalizzati.

Le interfacce hanno uno scopo simile nei linguaggi OO, ma il concetto sottostante è in qualche modo diverso; I linguaggi OO sono dotati di una nozione incorporata di gerarchie di tipi che Haskell semplicemente non ha, il che complica le cose in qualche modo perché le interfacce possono coinvolgere sia il sovraccarico per sottotipizzazione (cioè, chiamare metodi su istanze appropriate, sottotipi che implementano le interfacce dei loro supertipi) e tramite invio flat type-based (poiché due classi che implementano un'interfaccia potrebbero non avere una superclasse comune che la implementa anche). Data l'enorme complessità aggiuntiva introdotta dalla sottotipizzazione, suggerisco che sia più utile pensare alle classi di tipo come una versione migliorata di funzioni sovraccaricate in un linguaggio non OO.

Vale anche la pena notare che le classi di tipo hanno mezzi di invio molto più flessibili: le interfacce generalmente si applicano solo alla singola classe che la implementa, mentre le classi di tipo sono definite per un tipo , che può apparire ovunque nella firma delle funzioni della classe. L'equivalente di ciò nelle interfacce OO sarebbe consentire all'interfaccia di definire modi per passare un oggetto di quella classe ad altre classi, definire metodi statici e costruttori che selezionerebbero un'implementazione in base al tipo di ritorno richiesto nel contesto della chiamata, definire metodi che prendere argomenti dello stesso tipo della classe che implementa l'interfaccia e varie altre cose che non si traducono affatto.

In breve: hanno scopi simili, ma il modo in cui funzionano è in qualche modo diverso, e le classi di tipi sono entrambe significativamente più espressive e, in alcuni casi, più semplici da usare perché lavorano su tipi fissi piuttosto che su parti di una gerarchia di ereditarietà.


Stavo lottando con la comprensione delle gerarchie dei tipi in Haskell, cosa ne pensi dei sistemi dei tipi come Omega? Potrebbero simulare gerarchie di tipo?
CMCDragonkai

@CMCDragonkai: non ho abbastanza familiarità con Omega per dire davvero scusa.
CA McCann

16

Ho letto le risposte di cui sopra. Sento di poter rispondere un po 'più chiaramente:

Una "classe di tipo" Haskell e una "interfaccia" Java / C # o un "tratto" Scala sono sostanzialmente analoghi. Non c'è distinzione concettuale tra di loro ma ci sono differenze di implementazione:

  • Le classi di tipo Haskell vengono implementate con "istanze" separate dalla definizione del tipo di dati. In C # / Java / Scala, le interfacce / i tratti devono essere implementati nella definizione della classe.
  • Le classi di tipo Haskell ti consentono di restituire un tipo di questo tipo o auto. Anche i tratti di Scala (this.type). Nota che i "tipi di sé" in Scala sono una caratteristica completamente non correlata. Java / C # richiedono una soluzione alternativa disordinata con i generici per approssimare questo comportamento.
  • Le classi di tipo Haskell consentono di definire funzioni (comprese le costanti) senza un parametro di tipo "this" di input. Le interfacce Java / C # e le caratteristiche di Scala richiedono un parametro di input "this" su tutte le funzioni.
  • Le classi di tipo Haskell consentono di definire le implementazioni predefinite per le funzioni. Così come i tratti di Scala e le interfacce Java 8+. C # può approssimare qualcosa di simile con i metodi di estensione.

2
Solo per aggiungere un po 'di letteratura a questi punti di risposta, ho letto sulle classi di tipo (Haskell) e sulle interfacce (C #) oggi, che si confronta anche con le interfacce C # invece di Javas, ma dovrebbe bene dare una comprensione sul lato concettuale sezionando la nozione di un interfaccia attraverso i confini linguistici.
daniel.kahlenberg

Penso che alcune approssimazioni per cose come le implementazioni predefinite siano fatte in modo piuttosto più efficiente in C # con classi astratte forse?
Arwin

12

In Master minds of Programming , c'è un'intervista su Haskell con Phil Wadler, l'inventore delle classi di tipo, che spiega le somiglianze tra le interfacce in Java e le classi di tipo in Haskell:

Un metodo Java come:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

è molto simile al metodo Haskell:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Quindi, le classi di tipo sono relative alle interfacce, ma la vera corrispondenza sarebbe un metodo statico parametrizzato con un tipo come sopra.


Questa dovrebbe essere la risposta, perché secondo la homepage di Phil Wadler , è stato il principale progettista di Haskell, allo stesso tempo ha anche progettato l'estensione Generics per Java, che è stata successivamente inclusa nel linguaggio stesso.
Wong Jia Hau


8

Leggere Estensione software e integrazione con classi di tipi in cui vengono forniti esempi di come le classi di tipi possono risolvere una serie di problemi che le interfacce non possono.

Gli esempi elencati nel documento sono:

  • il problema dell'espressione,
  • il problema dell'integrazione del framework,
  • il problema dell'estensibilità indipendente,
  • la tirannia della decomposizione dominante, della dispersione e del groviglio.

3
Link è morto sopra. Prova questo invece .
Justin Leitgeb

1
Bene, ora anche quello è morto. Prova questo
RichardW

7

Non posso parlare al livello di "campagna pubblicitaria", se sembra così a posto. Ma sì, le classi di tipo sono simili in molti modi. Una differenza a cui posso pensare è che Haskell puoi fornire il comportamento per alcune delle operazioni della classe di tipo :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

che mostra che ci sono due operazioni, uguale (==)e non uguale (/=), per cose che sono istanze della Eqclasse di tipo. Ma l'operazione non uguale è definita in termini di uguale (quindi dovresti fornirne solo uno) e viceversa.

Quindi in Java probabilmente non legale sarebbe qualcosa del tipo:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

e il modo in cui funzionerebbe è che avresti solo bisogno di fornire uno di questi metodi per implementare l'interfaccia. Quindi direi che la capacità di fornire una sorta di implementazione parziale del comportamento che si desidera a livello di interfaccia è una differenza.


6

Sono simili (leggi: hanno un uso simile), e probabilmente implementate in modo simile: le funzioni polimorfiche in Haskell prendono sotto il cofano una 'vtable' che elenca le funzioni associate alla typeclass.

Questa tabella può spesso essere dedotta in fase di compilazione. Questo è probabilmente meno vero in Java.

Ma questa è una tabella di funzioni , non di metodi . I metodi sono associati a un oggetto, le classi di tipi Haskell non lo sono.

Guardali un po 'come i generici di Java.


3

Come dice Daniel, le implementazioni dell'interfaccia sono definite separatamente dalle dichiarazioni dei dati. E come altri hanno sottolineato, esiste un modo semplice per definire operazioni che utilizzano lo stesso tipo libero in più di un posto. Quindi è facile definirlo Numcome un typeclass. Così in Haskell otteniamo i vantaggi sintattici del sovraccarico di operatori senza in realtà avere operatori con sovraccarico magico - solo classi di tipi standard.

Un'altra differenza è che puoi usare metodi basati su un tipo, anche quando non hai ancora un valore concreto di quel tipo!

Ad esempio read :: Read a => String -> a,. Quindi, se hai abbastanza informazioni di altro tipo in giro su come utilizzerai il risultato di una "lettura", puoi lasciare che sia il compilatore a capire quale dizionario usare per te.

Puoi anche fare cose come il instance (Read a) => Read [a] where...che ti consente di definire un'istanza di lettura per qualsiasi elenco di cose leggibili. Non penso che sia del tutto possibile in Java.

E tutto questo è solo classi di tipi standard a parametro singolo senza trucchi in corso. Una volta introdotte le classi di tipi multiparametriche, si apre un intero nuovo mondo di possibilità, e ancora di più con le dipendenze funzionali e le famiglie di tipi, che consentono di incorporare molte più informazioni e calcoli nel sistema dei tipi.


1
Le classi di tipo normale stanno alle interfacce come le classi di tipo multiparametrico devono essere inviate più volte in OOP; si ottiene un corrispondente aumento della potenza sia del linguaggio di programmazione che del mal di testa del programmatore.
CA McCann
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.