2010-10-15 4 views
4

Je veux faire tous les types qui sont des instances de Enum et Bounded également des instances de Random. Le code suivant fait cela, et devrait travailler (avec les extensions appropriées ont permis):Instances contraints par classe polymorphes

import System.Random 

instance (Enum r, Bounded r) => Random r where 
    randomR (hi, lo) = inFst toEnum . randomR (fromEnum hi, fromEnum lo) 
     where inFst f (x,y) = (f x, y) 
    random = randomR (maxBound, minBound) 

Mais je suis conscient que c'est mauvais style parce instance (Enum r, Bounded r) => Random r crée une instance pour tous r, juste avec des contrôles de type pour Enum et Bounded plutôt que Il suffit de mettre une instance sur les types Enum et Bounded. Cela signifie que je définis une instance pour tous les types :(.

Le suppléant est que je dois écrire des fonctions autonomes qui me donnent le comportement que je veux et écrire un peu passe-partout pour chaque type que je veux être une instance de Random:

randomBoundedEnum :: (Enum r, Bounded r, RandomGen g) => g -> (r, g) 
randomBoundedEnum = randomRBoundedEnum (minBound, maxBound) 

randomBoundedEnumR :: (Enum r, Bounded r, RandomGen g) => (r, r) -> g -> (r, g) 
randomBoundedEnumR (hi, lo) = inFst toEnum . randomR (fromEnum hi, fromEnum lo) 
    where inFst f (x,y) = (f x, y) 

data Side = Top | Right | Bottom | Left 
    deriving (Enum, Bounded) 

-- Boilerplatey :( 
instance Random Side where 
    randomR = randomBoundedEnumR 
    random = randomBoundedEnum 

data Hygiene = Spotless | Normal | Scruffy | Grubby | Flithy 
    deriving (Enum, Bounded) 

-- Boilerplatey, duplication :(
instance Random Hyigene where 
    randomR = randomBoundedEnumR 
    random = randomBoundedEnum 

Existe-t-il des meilleures solutions de rechange? Comment devrais-je gérer ce problème? Ne devrais-je même pas essayer cela du tout? Suis-je trop inquiet à propos de la plaque?

Répondre

8

Oui, comme je viens de répondre à un slightly related question, vous pouvez utiliser un wrapper newtype - c'est le moyen commun et sûr de faire de telles instances sans ennuyer toute la communauté.

newtype RandomForBoundedEnum a = RfBE { unRfBE :: a} 
instance (Enum a, Bounded a) => Random (RandomForBoundedEnum a) where 
    .... 

De cette manière, les utilisateurs qui veulent utiliser cette instance ont simplement besoin d'envelopper (ou déballer) les appels:

first unRfBE . random $ g :: (Side, StdGen)