2016-04-28 20 views
15

Secondo Harper (https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/), sembra che le Classi di tipo semplicemente non offrano lo stesso livello di astrazione che i Moduli offrono e sto avendo difficoltà a capire esattamente perché. E non ci sono esempi in questo collegamento, quindi è difficile per me vedere le differenze chiave. Ci sono anche altri documenti su come tradurre tra i moduli e classi Tipo (http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf), ma questo in realtà non hanno nulla a che fare con l'implementazione in prospettiva del programmatore (si dice solo che non ci sia qualcosa che si può fare che la altro non può emulare).(ML) Moduli vs (Haskell) Tipo Classi

particolare, nel first link:

La prima è che insistono che un tipo può implementare una classe tipo esattamente un modo. Ad esempio, secondo la filosofia di classi tipo, gli interi possono essere ordinati in modo esattamente una (il solito ordine), ma ovviamente ci sono molti ordinamenti (diciamo, da divisibilità) di interesse. Il secondo è che confondono due problemi separati: specificando come un tipo implementa una classe di caratteri e specificando quando tale specifica dovrebbe essere usata durante l'inferenza di tipo.

Non capisco neanche. Un tipo può implementare una classe di tipo in più di 1 modo in ML? Come avresti gli interi ordinati per divisibilità per esempio senza creare un nuovo tipo? In Haskell, dovresti fare qualcosa come usare i dati e avere il instance Ord per offrire un ordinamento alternativo.

E il secondo, non sono i due sono distinti in Haskell? specificando "quando una tale precisazione deve essere usato durante l'inferenza di tipo" può essere fatto qualcosa di simile:

blah :: BlahType b => ... 

dove BlahType è la classe in uso durante l'inferenza di tipo e non la classe di attuazione. Considerando che "come un tipo implementa una classe di tipo" è fatto usando instance.

qualcuno può spiegare ciò che il link è veramente cercando di dire? Semplicemente non sto capendo perché i moduli sarebbero meno restrittivi rispetto alle classi di tipo.

risposta

11

Per comprendere ciò che l'articolo dice, prendere un momento per considerare le Monoid typeclass a Haskell. Un monoid è qualsiasi tipo, T, che ha una funzione mappend :: T -> T -> T e identità elemento empty :: T per cui vale quanto segue.

a `mappend` (b `mappend` c) == (a `mappend` b) `mappend` c 
a `mappend` mempty == mempty `mappend` a == a 

Ci sono molti tipi di Haskell che si adattano a questa definizione. Un esempio che mi viene subito in mente sono gli interi, per i quali possiamo definire quanto segue.

instance Monoid Integer where 
    mappend = (+) 
    mempty = 0 

È possibile confermare che tutti i requisiti sono validi.

In effetti, queste condizioni valgono per tutti i numeri oltre l'aggiunta, quindi possiamo definire anche quanto segue.

instance Num a => Monoid a where 
    mappend = (+) 
    mempty = 0 

Così ora, in GHCi, possiamo fare le seguenti.

> mappend 3 5 
8 
> mempty 
0 

lettori particolarmente osservanti (o quelli con un background in mathemetics) avranno probabilmente notato da ora che possiamo anche definire un'istanza Monoid per i numeri sopra moltiplicazione.

instance Num a => Monoid a where 
    mappend = (*) 
    mempty = 1 

a * (b * c) == (a * b) * c 
a * 1 == 1 * a == a 

Ma ora il compilatore incontra un problema. Quale definizione di mappend dovrebbe usare per i numeri? mappend 3 5 equivale a 8 o 15? Non c'è modo di decidere. Questo è il motivo per cui Haskell non consente più istanze di un singolo typeclass. Tuttavia, il problema rimane valido. Quale istanza Monoid di Num dovremmo usare? Entrambi sono perfettamente validi e hanno senso per determinate circostanze. La soluzione è di non usare nessuno dei due. Se si guarda Monoid in Hackage, si noterà che non è presente l'istanza Monoid di Num o Integer, Int, Float o Double. Invece, ci sono Monoid istanze di Sum e Product. Sum e Product sono definiti come segue.

newtype Sum a = Sum { getSum :: a } 
newtype Product a = Product { getProduct :: a } 

instance Num a => Monoid (Sum a) where 
    mappend (Sum a) (Sum b) = Sum $ a + b 
    mempty = Sum 0 

instance Num a => Monoid (Product a) where 
    mappend (Product a) (Product b) = Product $ a * b 
    mempty = Product 1 

Ora, se si desidera utilizzare un numero come Monoid si devono avvolgere sia in un tipo o di SumProduct. Quale tipo di utilizzo determina quale istanza di Monoid viene utilizzata. Questa è l'essenza di ciò che l'articolo stava cercando di descrivere. Non esiste un sistema integrato nel sistema TypClass di Haskell che consente di scegliere tra più livelli. Invece devi saltare attraverso i cerchi avvolgendoli e scartandoli nei tipi di scheletri. Ora, indipendentemente dal fatto che tu consideri questo un problema è una grande parte di ciò che determina se preferisci Haskell o ML.

ML aggira questo consentendo che più "istanze" della stessa classe e tipo siano definite in moduli diversi. Quindi, quale modulo si importa determina quale "istanza" si utilizza. (In senso stretto, ML non ha classi e istanze, ma ha firme e strutture, che possono agire quasi allo stesso modo. Per un confronto approfondito, leggi this paper).

+1

In che modo ML risolve questa ambiguità? Sì, in Haskell finirai per doverlo avvolgere in un altro tipo. Ma come si comporta ML? "Un tipo può implementare una classe di tipo in più di 1 modo in ML? Come faresti gli interi ordinati per divisibilità per esempio senza creare un nuovo tipo?" non è davvero la risposta da questo. –

+1

@RahulManne Non un programmatore ML * affatto *, ma credo che in sostanza ti permetta di nominare "istanze" e in particolare scegliere quale portare in ambito (usando moduli di prima classe per questo scopo piuttosto che classi di tipi). – Ben

+0

@ Ben se questo è il caso, mi chiedo, è possibile fare una cosa simile dove si definisce l'istanza in un file diverso, e l'importazione di quel file utilizzerà solo quell'istanza? Ci proverò. –