2010-10-22 5 views
7

(Scusatemi in anticipo se la domanda è stupida o ovvia - non ho molta esperienza con Haskell).In Haskell, c'è un modo per esprimere che un tipo dovrebbe essere un'istanza di un typeclass in più di un modo?

C'è un modo per esprimere che un tipo dovrebbe essere un'istanza di un typeclass in più di un modo? Questo è meglio illustrato con un esempio (che è probabilmente un po 'sciocco): in matematica, possiamo dire che un semiring è un insieme che è un monoide commutativo sotto una operazione (che chiameremo addizione, identità 0) e un monoide sotto un altro (che chiameremo moltiplicazione) insieme ai requisiti che la moltiplicazione distribuisce oltre l'aggiunta e che 0 annienta tutti gli elementi in moltiplicazione. Le ultime parti non sono importanti qui.

Supponiamo ora che ho un typeclass Monoid (da non confondere con Data.Monoid),

class Monoid m where 
    unit :: m 
    operation :: m -> m -> m 

e vorrei creare un typeclass Semiring. Dalla definizione di cui sopra, vorrei dire "se il tipo r è un monoid in due modi (distinti), lo chiameremo semiring". Quindi vorrei qualcosa come

class (Monoid r, Monoid r) => Semiring r where ... 

che naturalmente non funziona. Certo, l'esempio diventa un po 'strano dal momento che non ci sono più funzioni che vorremmo richiedere per le semine, quindi il typeclass sarebbe vuoto, ma spero che illustri quello che sto chiedendo (o pretendiamo semplicemente che abbiamo bisogno di qualche funzione f:r->r per Semiring r).

Così, nel contesto generale, mi sto chiedendo: Dato un typeclass A, c'è un modo per parametrizzare un typeclass B a con il requisito che a essere un'istanza di A in due modi (il che significa che a dovrebbe attuare la funzioni specificate da A in due modi)?

+0

Grazie a tutti coloro che hanno risposto finora. Praticamente tutte le risposte potrebbero essere accettate, ma sono andato con quello con il maggior numero di voti. – gspr

risposta

11

Una possibilità è quella di definire i propri monoidi per le due operazioni di un semianello:

class AdditiveMonoid m where 
    zero :: m 
    (<+>) :: m -> m -> m 

class MultiplicativeMonoid m where 
    one :: m 
    (<*>) :: m -> m -> m 

e poi combinarli:

class (MultiplicativeMonoid m, AdditiveMonoid m) => Semiring m 

Il problema è che non è possibile esprimere le leggi monoid o il fatto che un'operazione è commutativa. Il meglio che puoi ottenere è definire proprietà quickcheck per le leggi.

Per qualche ispirazione controllare la carta numeric prelude e this.

+0

Grazie mille! Sembra che la risposta breve sia "no, devi duplicare il codice", quindi? Sono consapevole del preludio numerico, ma è così massiccio che può essere difficile imparare da quando sei nuovo, ma la carta sembra molto interessante, grazie! – gspr

7

Per Monoid, in particolare, questo viene fatto con i tipi di wrapper. Se si guarda nel modulo Data.Monoid, troverete due diverse strutture monoidali per Bool valori: Any e All, così come due strutture diverse per i tipi che implementano Num: Sum e Product e due strutture per Maybe tipi: First e Last.

Potrai esegue in problemi con il tuo esempio semiring, tuttavia, dal momento che le strutture monoidali per Sum e Product sia fornire implementazioni di mempty (versione Haskell del unit) e mappend (versione Haskell del operation).

+0

Hmm ... grazie per le informazioni, ma sembra che la risposta si riduca a "no", quindi? E 'un peccato :-( – gspr

3

Vedere anche la sezione "ritmica" di Conor McBride: http://www.haskell.org/pipermail/libraries/2008-January/008917.html, sebbene sia al livello di valore e non sia di aiuto con le classi di tipi.

biblioteca monoidi di Kmett (prima spogliato fuori la roba Ring) implementato qualcosa di simile al metodo di Daniel Velkov: http://hackage.haskell.org/package/monoids-0.1.36

Vorrei aggiungere che la cosa bella di questo approccio è che definendo additivo e moltiplicativo su un tipo di dati distintamente, puoi catturare che non sono la stessa cosa - cioè, quest'ultima distribuisce sulla prima.

2

La tecnica comune per questo è, come altre risposte menzionano, wrapper newtype. In molti casi questo mi sembra qualcosa di un abuso del concetto di classe del tipo. Le classi di tipi sono "assiomi" logici che affermano che un fatto è vero per un tipo; per esempio, che Forse è un Monade, o che Int è un Num, o che gli elenchi sono ordinati quando i loro elementi sono. Spesso, come nel caso di Eq e Ord, ci sono altre definizioni ragionevoli ma quelle scelte sono in qualche modo "canoniche". Altre volte, come nel caso di Monoid, non c'è.

Nel caso di Monoid e di altre strutture altamente astratte come questa, sono dell'opinione che una dichiarazione data sarebbe più utile. Ad esempio, data Monoid a = Monoid {mempty :: a ; mappend :: a -> a -> a}. Quindi, abbiamo addition :: Num a => Monoid a, liftMaybe :: Monoid a -> Monoid (Maybe a), ecc.

La stessa tecnica potrebbe essere utilizzata per implementare il tuo Semiring. Ad esempio (utilizzando un tipo di dati monoide come prima): data Semiring a = Semiring { addition, multiplication :: Monoid a }.

+0

In effetti, se capisco le cose correttamente, questo è fondamentalmente ciò che GHC fa sotto le nostre menti per implementare il polimorfismo di classe tipo.Non sono convinto del beneficio in user- codice di livello, però. –

4

altre risposte hanno detto newtype involucri, ma non dato una soluzione esplicita che li utilizzano:

-- export these newtypes from the module defining Semiring 
newtype Add a = Add a 
newtype Multiply a = Multiply a 

class (Monoid (Add a), Monoid (Multiply a)) => Semiring a where 
    -- empty 

instance Monoid (Add Integer) where 
    unit = Add 0 
    Add a `operation` Add b = Add (a + b) 

-- etc. 

avrete bisogno di alcune estensioni GHC come FlexibleContexts.

+0

Grazie per l'esempio di codice esplicito. Rende tutto molto più chiaro, anche se, mi sento quasi con la sensazione che spesso mi confondono con quando si dovrebbero creare tipeclass e quando si dovrebbero creare tipi. Infatti, per superare il problema in questione, avevo creato me * typeclass * Additive e Multiplicative: il tuo modo di procedere sembra migliore, ma ciò significa che ho una comprensione sbagliata del rapporto tra ciò che "dovrebbe" essere un typeclass e che cosa dovrebbe "essere" . Può essere Farò un'altra domanda :-) – gspr

Problemi correlati