Polymorphic class-constrained instances
I want to make all types that are instances of Enum
and Bounded
also an instances of Random
. The following code does this and should work (with the appropriate extensions enabled):
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)
But I am aware this is bad style because instance (Enum r, Bounded r) => Random r
creates an instance for all r
, just with type checks for Enum
and Bounded
rather than just putting an instance on types that are Enum
and Bounded
. This effectively means I'm defining an instance for all types :(
.
The alternate is that I have to write standalone functions that give me the behavior I want and write some boilerplate for each type I want to be an instance of 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
rand开发者_开发问答omR = randomBoundedEnumR
random = randomBoundedEnum
data Hygiene = Spotless | Normal | Scruffy | Grubby | Flithy
deriving (Enum, Bounded)
-- Boilerplatey, duplication :(
instance Random Hyigene where
randomR = randomBoundedEnumR
random = randomBoundedEnum
Are there any better alternatives? How should I manage this problem? Should I not even be attempt this at all? Am I overly worried about boilerplate?
Yes, as I just answered to a slightly related question, you can use a newtype wrapper - this is the common and safe way to make such instances without annoying the entire community.
newtype RandomForBoundedEnum a = RfBE { unRfBE :: a}
instance (Enum a, Bounded a) => Random (RandomForBoundedEnum a) where
....
In this manner, users who want to use this instance simply need to wrap (or unwrap) the calls:
first unRfBE . random $ g :: (Side, StdGen)
精彩评论