2013-05-03 19 views
6

Sono nuovo di Haskell e un po 'confuso su come funzionano le classi di tipi. Ecco un esempio semplificato di una cosa che sto cercando di fare:Come devono essere usati i tipi nelle classi di tipi Haskell?

data ListOfInts = ListOfInts {value :: [Int]} 
data ListOfDoubles = ListOfDoubles {value :: [Double]} 

class Incrementable a where 
    increment :: a -> a 

instance Incrementable ListOfInts where 
    increment ints = map (\x -> x + 1) ints 

instance Incrementable ListOfDoubles where 
    increment doubles = map (\x -> x + 1) doubles 

(. Mi rendo conto che l'incremento ogni elemento di una lista può essere fatto in modo molto semplice, ma questo è solo una versione semplificata di un problema più complesso)

Il compilatore mi informa che ho più dichiarazioni di value. Se cambio le definizioni di ListOfInts e ListOfDoubles come segue:.

type ListOfInts = [Int] 
type ListOfDoubles = [Double] 

Poi il compilatore dice "dichiarazione di istanza non ammesso per il 'ListOfInts incrementabile'" (e allo stesso modo per ListOfDoubles Se uso Newtype, ad esempio, newtype ListOfInts = ListOfInts [Int], poi la il compilatore mi dice "Impossibile corrispondere al tipo atteso" ListOfInts "con tipo effettivo" [b0] "" (e analogamente per ListOfDoubles.

La mia comprensione delle classi di tipi è che facilitano il polimorfismo, ma mi manca chiaramente qualcosa Nel primo esempio sopra, il compilatore vede solo che il parametro type a si riferisce a un reco con un campo chiamato value e sembra che sto cercando di definire increment per questo tipo in più modi (piuttosto che vedere due tipi diversi, uno che ha un campo il cui tipo di un elenco di Int s, e l'altro il cui tipo è una lista di Double s)? E allo stesso modo per gli altri tentativi?

Grazie in anticipo.

+1

mappa si aspetta un elenco, lo stai dando ListOfInts – Arjan

risposta

17

Stai davvero vedendo due problemi separati, quindi li affronterò come tale.

Il primo è con il campo value. I record di Haskell funzionano in un modo un po 'particolare: quando si nomina un campo, questo viene automaticamente aggiunto all'ambito corrente come una funzione. In sostanza, si può pensare di

data ListOfInts = ListOfInts {value :: [Int]} 

come lo zucchero sintassi per:

data ListOfInts = ListOfInts [Int] 

value :: ListOfInt -> [Int] 
value (ListOfInts v) = v 

Quindi, avendo due record con lo stesso nome del campo è proprio come avere due funzioni diverse con lo stesso nome - hanno sovrapposizione. Questo è il motivo per cui il tuo primo errore ti dice che hai dichiarato values più volte.

Il modo per risolvere questo problema sarebbe quello di definire i tipi senza utilizzando la sintassi di registrazione, come ho fatto in precedenza:

data ListOfInts = ListOfInts [Int] 
data ListOfDoubles = ListOfDoubles [Double] 

Quando si è utilizzato type invece di data, semplicemente creato un tipo sinonimo piuttosto di un nuovo tipo. Utilizzando

type ListOfInts = [Int] 

significa che ListOfInts è lo stesso come appena [Int]. Per vari motivi, per impostazione predefinita non è possibile utilizzare i sinonimi di tipo nelle istanze di classe. Questo ha senso: sarebbe molto facile commettere un errore come provare a scrivere un'istanza per [Int] e una per ListOfInts, che si interromperà.

Utilizzo data avvolgere un singolo tipo come [Int] o [Double] è lo stesso che con newtype. Tuttavia, newtype ha il vantaggio che non comporta affatto sovraccarico di runtime. Quindi il modo migliore per scrivere questo tipo sarebbe davvero con newtype:

newtype ListOfInts = ListOfInts [Int] 
newtype ListOfDoubles = ListOfDoubles [Double] 

Una cosa importante da notare è che quando si utilizza data o newtype, si hanno anche per "scartare" del tipo, se si vuole arrivare a il suo contenuto. Si può fare questo con il pattern matching:

instance Incrementable ListOfInts where 
    increment (ListOfInts ls) = ListOfInts (map (\ x -> x + 1) ls) 

Questa scarta il ListOfInts, mappe di una funzione rispetto ai suoi contenuti e avvolge il backup.

Finché si scartano i valori in questo modo, le istanze dovrebbero funzionare.

Su una nota a margine, è possibile scrivere map (\ x -> x + 1) come map (+ 1), utilizzando qualcosa che viene chiamato una "sezione operatore". Tutto ciò significa che crei implicitamente un riempimento lambda in qualsiasi argomento dell'operatore manchi. La maggior parte delle persone trova la versione map (+ 1) più facile da leggere perché c'è meno rumore inutile.

+0

Grazie mille, Tikhon, questo è molto chiaro. – Chris

Problemi correlati