Ho una classe di caratteri Cyclic
per cui vorrei essere in grado di fornire istanze generiche.Derivazione di istanze predefinite utilizzando GHC.Generics
class Cyclic g where
gen :: g
rot :: g -> g
ord :: g -> Int
Dato un tipo somma di costruttori nullaria,
data T3 = A | B | C deriving (Generic, Show)
voglio generare un'istanza equivalente a questo:
instance Cyclic T3 where
gen = A
rot A = B
rot B = C
rot C = A
ord _ = 3
Ho cercato di capire la richiesta Generic
macchinari Così
{-# LANGUAGE DefaultSignatures, FlexibleContexts, ScopedTypeVariables, TypeOperators #-}
import GHC.Generics
class GCyclic f where
ggen :: f a
grot :: f a -> f a
gord :: f a -> Int
instance GCyclic U1 where
ggen = U1
grot _ = U1
gord _ = 1
instance Cyclic c => GCyclic (K1 i c) where
ggen = K1 gen
grot (K1 a) = K1 (rot a)
gord (K1 a) = ord a
instance GCyclic f => GCyclic (M1 i c f) where
ggen = M1 ggen
grot (M1 a) = M1 (grot a)
gord (M1 a) = gord a
instance (GCyclic f, GCyclic g) => GCyclic (f :*: g) where
ggen = ggen :*: ggen
grot (a :*: b) = grot a :*: grot b
gord (a :*: b) = gord a `lcm` gord b
instance (GCyclic f, GCyclic g) => GCyclic (f :+: g) where
ggen = L1 ggen
-- grot is incorrect
grot (L1 a) = L1 (grot a)
grot (R1 b) = R1 (grot b)
gord _ = gord (undefined :: f a)
+ gord (undefined :: g b)
Ora posso fornire implementazioni di default per Cyclic
utilizzando GCyclic
:
class Cyclic g where
gen :: g
rot :: g -> g
ord :: g -> Int
default gen :: (Generic g, GCyclic (Rep g)) => g
gen = to ggen
default rot :: (Generic g, GCyclic (Rep g)) => g -> g
rot = to . grot . from
default ord :: (Generic g, GCyclic (Rep g)) => g -> Int
ord = gord . from
ma i miei GCyclic
casi non sono corretti. Utilizzando T3
dall'alto
λ. map rot [A, B, C] -- == [B, C, A]
[A, B, C]
E 'chiaro perché rot
è equivalente a id
qui. grot
ricorre la struttura (:+:)
di T3
finché non colpisce la base grot U1 = U1
.
È stato suggerito il #haskell
per utilizzare le informazioni del costruttore da M1
in modo che grot
possa scegliere il prossimo costruttore da recitare, ma non sono sicuro di come farlo.
È possibile generare le istanze desiderate di Cyclic
utilizzando GHC.Generics
o qualche altra forma di Scrap Your Boilerplate?
EDIT: Hopotuto scrivere Cyclic
utilizzando Bounded
e Enum
class Cyclic g where
gen :: g
rot :: g -> g
ord :: g -> Int
default gen :: Bounded g => g
gen = minBound
default rot :: (Bounded g, Enum g, Eq g) => g -> g
rot g | g == maxBound = minBound
| otherwise = succ g
default ord :: (Bounded g, Enum g) => g -> Int
ord g = 1 + fromEnum (maxBound `asTypeOf` g)
ma (come è) questo è insoddisfacente, in quanto richiede tutti Bounded
, Enum
e Eq
. Inoltre, Enum
non può essere derivato automaticamente da GHC in alcuni casi mentre il più robusto Generic
può.
Forse questo non corrisponde esattamente al tuo problema, ma puoi fornire impostazioni predefinite per quelle funzioni usando solo Enum e Bounded. Quindi tutto ciò che devi fare è dichiarare l'istanza, non è necessaria alcuna implementazione specifica. Sono al telefono in questo momento, ma posso fornire un esempio più tardi. Ho la sensazione che il tuo caso d'uso reale sia un po 'più complicato. – bheklilr
L'unica cosa che ti serve da 'Bounded' e' Eq' è la capacità di dire quando sei all'ultima voce per iniziare di nuovo iterando da qualche altro 'gen', che è quello che aggiunge la mia risposta. Nota che l'aggiunta di 'glast' a' GCylic' non richiede l'aggiunta di una funzione corrispondente a 'Cyclic' a meno che tu non intenda derivare istanze per' K1' (che dovresti fare totalmente perché è fantastico, l'istanza derivata per '[ T3] potrebbe sorprenderti, mi ha sorpreso). – Cirdec
Se si inizia a passare valori 'non definiti' come proxy per i tipi, tutto ciò che implementa' Cyclic' deve accettare valori 'indefiniti', poiché alcune implementazioni potrebbero passare' indefinite' alle altre. Puoi evitarlo usando invece 'data Proxy a = Proxy' dal pacchetto con tag (http://hackage.haskell.org/package/tagged) e passa invece' (Proxy :: ...) '. Dovresti passare a 'ord :: Proxy a -> Int'. – Cirdec