2015-09-23 11 views
6

Diciamo che ho la classe:classe Generalizzando Parametri

class C a b t where 
    f :: (a, b) -> (t a, t b) 

Ora, con questa definizione, posso definire le istanze per dire:

(a,b) -> (Maybe a, Maybe b) 
(a,b) -> ([a], [b]) 

Ma non per (per quanto ho capito):

(a,b) -> (a,b) 
(a,b) -> ((a, a), (b, b)) 

ho potuto invece cambiare la mia definizione di classe in questo modo:

type family T a b x 

class C a b where 
    f :: (a, b) -> (T a b a, T a b b) 

che mi permettesse di fare quanto sopra, ma poi mi piacerebbe solo essere in grado di dichiarare una f per ogni a e b.

Fondamentalmente voglio essere in grado di passare una famiglia di tipi come t nella definizione originale, che è implicitamente risolta dal correttore di tipi se noto. Non voglio semplicemente renderlo f :: (a, b) -> (c, d) perché voglio mantenere invariato che sia a sia abbiano la stessa cosa fatta a loro, quindi swap . f è lo stesso tipo di f . swap. Sto pensando che potrebbe essere necessario injective type families (da GHC 8.0) ma non ne sono sicuro. Ma forse c'è un altro modo?

+1

Una non risposta: è possibile utilizzare 'Identity' nel primo caso e un newtype appropriato nel secondo (non mi viene in mente nessuno standard al momento). Questa è una soluzione approssimativa, ma potrebbe rivelarsi meno doloroso rispetto al trucco avanzato che questo sembra richiedere. A proposito, il "Io non voglio solo renderlo" f :: (a, b) -> (c, d) 'come voglio mantenere l'invariante ..." suona molto come il problema di * gli obiettivi * lenti sono 'Lens stab' [anche se i quattro parametri sono mutuamente dipendenti] (http://comonad.com/reader/2012/mirrored-lenses) (vedere la sezione" Perché è una famiglia di obiettivi? "di il post). – duplode

+0

Penso che questa sia fondamentalmente la stessa domanda di http://stackoverflow.com/questions/4922560/why-doesnt-typesynonyminstances-allow-partially-applied-type-synonyms-to-be-use –

risposta

5

Questo può o non può rispondere alla domanda a seconda di ciò che è effettivamente necessario fare.

Una soluzione standard consiste nell'utilizzare un newtype per definire nuove istanze. Per esempio.

newtype Pair a = Pair (a, a) 
instance C a b Pair where 
    f = ... 

Tuttavia, ciò causerà f avere tipo

f :: (a, b) -> (Pair a, Pair b) 

invece del desiderato

f :: (a, b) -> ((a, a), (b, b)) 

Quest'ultima può essere recuperato in un modo noioso:

f' :: (a, b) -> ((a, a), (b, b)) 
f' p = case f p of (Pair x, Pair y) -> (x, y) 

Ora, la scrittura di funzioni "adattatore" come f' è ridondante: dopotutto stiamo semplicemente rimuovendo un wrapper newtype, che non modifica la rappresentazione del runtime. Peggio ancora, questo potrebbe essere inefficiente: considerare la trasformazione di [Pair a] in [(a, a)]. Per fare ciò, dovremmo mappare l'intera lista, quindi paghiamo O (n) do essenzialmente nulla.

Un efficiente alternativa possono essere costruiti usando safe coercions:

import Data.Coerce 

f'' :: forall a b. (a, b) -> ((a, a), (b, b)) 
f'' = coerce (f :: (a, b) -> (Pair a, Pair b)) 

Questo consente l'utilizzo di newtype s guidare selezione esempio, eppure rimuoverli quando ottengono nel modo.

Ora, se il tuo obiettivo è davvero quello di definire nuove istanze senza newtype s, questo non aiuta molto. Se invece vuoi un modo semplice per rimuovere i wrapper newtype dai metodi di istanza, può aiutarti.

+0

'Sembra Data.Coerce' molto interessante. Grazie per questa risposta. –