2010-01-31 20 views
12

In primo luogo, questa domanda non è specifica al 100% per Haskell, sentiti libero di commentare il design generale di typeclasses, interfacce e tipi.Perché Haskell non riesce a dedurre i typeclass del tipo di dati nelle firme delle funzioni?

Sto leggendo LYAH - creating types and typeclasses Quello che segue è il passaggio che sto cercando ulteriori informazioni su:

Data (Ord k) => Map k v = ... 

Tuttavia, si tratta di una convenzione molto forte in Haskell mai aggiungere typeclass vincoli dichiarazioni di dati. Perché? Bene, perché non ne beneficiamo molto, ma finiamo per scrivere più vincoli di classe , anche quando non abbiamo bisogno di loro . Se inseriamo o non inseriamo il vincolo Ord k nella dichiarazione dei dati per Map k v, dobbiamo mettere il vincolo in funzioni che presuppongono che le chiavi in ​​una mappa possano essere ordinate . Ma se non inseriamo il vincolo nella dichiarazione dei dati, non dobbiamo mettere (Ord k) => nelle dichiarazioni di tipo di tipo , non importa se le chiavi possono essere ordinate o non. Un esempio di tale funzione è toList, che prende solo un mapping e lo converte in un elenco associativo . La sua sigla di tipo è listList :: Map k a -> [(k, a)]. Se Map kv aveva un vincolo di tipo nella sua dichiarazione di dati , il tipo di toList dovrebbe essere toList :: (Ord k) => Map ka -> [(k, a)], anche se la funzione non esegue alcun confronto delle chiavi per ordine.

Questo inizialmente sembra logico, ma non c'è un lato positivo per avere la classe di caratteri associata al tipo? Se il typeclass è il comportamento del tipo, allora perché il comportamento dovrebbe essere definito dall'uso del tipo (attraverso le funzioni) e non del tipo stesso? Presumo che ci sia qualche meta-programmazione che potrebbe farne uso, ed è certamente una documentazione del codice piacevole e descrittiva. Al contrario, sarebbe una buona idea in altre lingue? Sarebbe ideale specificare l'interfaccia a cui l'oggetto dovrebbe conformarsi sul metodo, in modo tale che se il metodo non viene utilizzato dal chiamante, l'oggetto non deve conformarsi all'interfaccia? Inoltre, perché Haskell non può dedurre che una funzione che utilizza il tipo Foo, debba inserire i vincoli di classe di caratteri identificati nella dichiarazione del tipo Foo? C'è un pragma per abilitare questo?

La prima volta che l'ho letto, ha evocato un "è una risposta (o una soluzione alternativa)". Alla seconda lettura con qualche pensiero, sembrava intelligente. Alla terza lettura, disegnando un complotto al mondo OO, suonava di nuovo come un hack.

Quindi eccomi.

risposta

4

Il motivo principale per evitare i vincoli di tipografia nelle dichiarazioni di dati è che non ottengono assolutamente nulla; infatti, credo che GHC si riferisca a un contesto di classe come lo "stupido contesto". La ragione di ciò è che il dizionario di classe non viene portato in giro con i valori del tipo di dati, quindi è necessario aggiungerlo a tutte le funzioni che operano comunque sui valori.

Come un modo di "forzare" il vincolo della classe di caratteri sulle funzioni che operano sul tipo di dati, anche in realtà non realizza nulla; le funzioni dovrebbero generalmente essere il più polimorfo possibile, quindi perché forzare il vincolo su cose che non ne hanno bisogno?

A questo punto, si potrebbe pensare che dovrebbe essere possibile cambiare la semantica degli ADT per portare il dizionario in giro con i valori. In effetti, sembra che questo sia l'intero punto di GADT s; per esempio, si può fare:

data Foo a where { Foo :: (Eq a) => a -> Foo a } 
eqfoo :: Foo t -> Foo t -> Bool 
eqfoo (Foo a) (Foo b) = a == b 

Si noti che il tipo di eqfoo non ha bisogno del vincolo Eq, in quanto è "portato" dal tipo di dati Foo stesso.

+0

Sono abbastanza nuovo per Haskell, una breve spiegazione di ciò che un GADT sarebbe stato utile (o collegamento). –

+0

Ho rielaborato quella sezione un po 'e ho aggiunto un collegamento alla documentazione di GHC per GADT. Se qualcun altro vuole prendere una crepa per una spiegazione migliore di ciò che sono GADT, sarebbe il benvenuto! – mithrandi

9

Forse Map k v non è stato il miglior esempio per illustrare il punto. Data la definizione di Map, anche se ci sono alcune funzioni che non necessitano del vincolo (Ord k), non esiste un modo per costruire uno Map senza di esso.

Spesso si scopre che un tipo è abbastanza utilizzabile con il sottoinsieme di funzioni che funzionano senza qualche particolare vincolo, anche quando si immagina che il vincolo sia un aspetto ovvio del progetto originale. In questi casi, aver lasciato il vincolo fuori dalla dichiarazione del tipo, è più flessibile.

Ad esempio, Data.List contiene molte funzioni che richiedono (Eq a), ma ovviamente gli elenchi sono perfettamente utili senza tale vincolo.

+1

"non c'è modo di costruire una mappa senza di essa." - Questo è proprio il motivo per cui ho sempre pensato che cose come i contesti di Ord su funzioni che consumano mappe sarebbero ridondanti, e anche il motivo per cui GADT combacia con l'introduzione di contesti. Il fatto di avere una 'Map k v' è già una prova sufficiente per ordinare k. Se tutto ciò che sto facendo è estrarre cose dalla mappa, ad esempio, perché dovrei aver bisogno di un'istanza di Ord? – mokus

7

La risposta breve è: Haskell lo fa perché è così che viene scritta la specifica della lingua.

La risposta lunga comporta citando il GHC documentation language extensions section:

Qualsiasi tipo di dati che può essere dichiarato normale sintassi Haskell-98 può anche essere dichiarate utilizzando la sintassi in stile GADT. La scelta è in gran parte stilistica, ma le dichiarazioni in stile GADT differiscono in un aspetto importante: trattano i vincoli di classe sui costruttori di dati in modo diverso. In particolare, se al costruttore viene assegnato un contesto classe-tipo, tale contesto è reso disponibile dalla corrispondenza del modello. Per esempio:

data Set a where 
    MkSet :: Eq a => [a] -> Set a 

(...)

Tutto questo comportamento contrasta con il trattamento peculiare di Haskell 98 di contesti su una dichiarazione di tipo di dati (Section 4.2.1 of the Haskell 98 Report). In Haskell 98 la definizione

data Eq a => Set' a = MkSet' [a] 

dà MkSet' dello stesso tipo di MkSet sopra. Ma invece di rendere disponibile un vincolo (Eq a), il pattern matching su MkSet 'richiede un vincolo (Eq a)! GHC implementa fedelmente questo comportamento, anche se è strano. Ma per le dichiarazioni in stile GADT, il comportamento di GHC è molto più utile, oltre che molto più intuitivo.

1

vorrei sottolineare che se siete preoccupati che si potrebbe costruire un oggetto che richiede vincoli per le sue operazioni, ma non lo fa per la sua creazione, diciamo mkFoo, si può sempre artificialmente mettere il vincolo sulla mkFoo funzione per far rispettare l'uso della classe di caratteri da parte di persone che usano il codice. L'idea si estende anche alle funzioni di tipo non mkFoo che funzionano su Foo. Quindi, quando si definisce il modulo, non esportare nulla che non imponga i vincoli.

Anche se devo ammettere, non vedo alcun uso per questo.

Problemi correlati