diff --git a/random.cabal b/random.cabal index 35747c969..055c723b3 100644 --- a/random.cabal +++ b/random.cabal @@ -86,6 +86,8 @@ library System.Random System.Random.Internal System.Random.Stateful + other-modules: + System.Random.GFinite hs-source-dirs: src default-language: Haskell2010 diff --git a/src/System/Random.hs b/src/System/Random.hs index bdd86fbc9..c8a1ba24f 100644 --- a/src/System/Random.hs +++ b/src/System/Random.hs @@ -27,6 +27,8 @@ module System.Random , Random(..) , Uniform , UniformRange + , Finite + -- ** Standard pseudo-random number generator , StdGen , mkStdGen @@ -64,6 +66,7 @@ import Data.Word import Foreign.C.Types import GHC.Exts import System.IO.Unsafe (unsafePerformIO) +import System.Random.GFinite (Finite) import System.Random.Internal import qualified System.Random.SplitMix as SM diff --git a/src/System/Random/GFinite.hs b/src/System/Random/GFinite.hs new file mode 100644 index 000000000..4346134f0 --- /dev/null +++ b/src/System/Random/GFinite.hs @@ -0,0 +1,280 @@ +-- | +-- Module : System.Random.GFinite +-- Copyright : (c) Andrew Lelechenko 2020 +-- License : BSD-style (see the file LICENSE in the 'random' repository) +-- Maintainer : libraries@haskell.org +-- + +{-# LANGUAGE DefaultSignatures #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeOperators #-} + +module System.Random.GFinite + ( Cardinality(..) + , Finite(..) + , GFinite(..) + ) where + +import Data.Bits +import Data.Int +import Data.Void +import Data.Word +import GHC.Exts (Proxy#, proxy#) +import GHC.Generics + +-- | Cardinality of a set. +data Cardinality + = Shift !Int -- ^ Shift n is equivalent to Card (bit n) + | Card !Integer + deriving (Eq, Ord, Show) + +-- | This is needed only as a superclass of 'Integral'. +instance Enum Cardinality where + toEnum = fromIntegral + fromEnum = fromIntegral + succ = (+ 1) + pred = subtract 1 + enumFrom x = map fromInteger (enumFrom (toInteger x)) + enumFromThen x y = map fromInteger (enumFromThen (toInteger x) (toInteger y)) + enumFromTo x y = map fromInteger (enumFromTo (toInteger x) (toInteger y)) + enumFromThenTo x y z = map fromInteger (enumFromThenTo (toInteger x) (toInteger y) (toInteger z)) + +instance Num Cardinality where + fromInteger 1 = Shift 0 -- () + fromInteger 2 = Shift 1 -- Bool + fromInteger n = Card n + {-# INLINE fromInteger #-} + + x + y = fromInteger (toInteger x + toInteger y) + {-# INLINE (+) #-} + + Shift x * Shift y = Shift (x + y) + Shift x * Card y = Card (y `shiftL` x) + Card x * Shift y = Card (x `shiftL` y) + Card x * Card y = Card (x * y) + {-# INLINE (*) #-} + + abs = Card . abs . toInteger + signum = Card . signum . toInteger + negate = Card . negate . toInteger + +-- | This is needed only as a superclass of 'Integral'. +instance Real Cardinality where + toRational = fromIntegral + +instance Integral Cardinality where + toInteger = \case + Shift n -> bit n + Card n -> n + {-# INLINE toInteger #-} + + quotRem x' = \case + Shift n -> (Card (x `shiftR` n), Card (x .&. (bit n - 1))) + Card n -> let (q, r) = x `quotRem` n in (Card q, Card r) + where + x = toInteger x' + {-# INLINE quotRem #-} + +-- | A type class for data with a finite number of inhabitants. +-- This type class is used +-- in default implementations of 'System.Random.Stateful.Uniform' +-- and 'System.Random.Stateful.UniformRange'. +-- +-- Users are not supposed to write instances of 'Finite' manually. +-- There is a default implementation in terms of 'Generic' instead. +-- +-- >>> :set -XDeriveGeneric -XDeriveAnyClass +-- >>> import GHC.Generics (Generic) +-- >>> data MyBool = MyTrue | MyFalse deriving (Generic, Finite) +-- >>> data Action = Code MyBool | Eat (Maybe Bool) | Sleep deriving (Generic, Finite) +-- +class Finite a where + cardinality :: Proxy# a -> Cardinality + toFinite :: Integer -> a + fromFinite :: a -> Integer + + default cardinality :: (Generic a, GFinite (Rep a)) => Proxy# a -> Cardinality + cardinality _ = gcardinality (proxy# :: Proxy# (Rep a)) + + default toFinite :: (Generic a, GFinite (Rep a)) => Integer -> a + toFinite = to . toGFinite + + default fromFinite :: (Generic a, GFinite (Rep a)) => a -> Integer + fromFinite = fromGFinite . from + +class GFinite f where + gcardinality :: Proxy# f -> Cardinality + toGFinite :: Integer -> f a + fromGFinite :: f a -> Integer + +instance GFinite V1 where + gcardinality _ = 0 + {-# INLINE gcardinality #-} + toGFinite = const $ error "GFinite: V1 has no inhabitants" + {-# INLINE toGFinite #-} + fromGFinite = const $ error "GFinite: V1 has no inhabitants" + {-# INLINE fromGFinite #-} + +instance GFinite U1 where + gcardinality _ = 1 + {-# INLINE gcardinality #-} + toGFinite = const U1 + {-# INLINE toGFinite #-} + fromGFinite = const 0 + {-# INLINE fromGFinite #-} + +instance Finite a => GFinite (K1 _x a) where + gcardinality _ = cardinality (proxy# :: Proxy# a) + {-# INLINE gcardinality #-} + toGFinite = K1 . toFinite + {-# INLINE toGFinite #-} + fromGFinite = fromFinite . unK1 + {-# INLINE fromGFinite #-} + +instance GFinite a => GFinite (M1 _x _y a) where + gcardinality _ = gcardinality (proxy# :: Proxy# a) + {-# INLINE gcardinality #-} + toGFinite = M1 . toGFinite + {-# INLINE toGFinite #-} + fromGFinite = fromGFinite . unM1 + {-# INLINE fromGFinite #-} + +instance (GFinite a, GFinite b) => GFinite (a :+: b) where + gcardinality _ = + gcardinality (proxy# :: Proxy# a) + gcardinality (proxy# :: Proxy# b) + {-# INLINE gcardinality #-} + + toGFinite n + | n < cardA = L1 $ toGFinite n + | otherwise = R1 $ toGFinite (n - cardA) + where + cardA = toInteger (gcardinality (proxy# :: Proxy# a)) + {-# INLINE toGFinite #-} + + fromGFinite = \case + L1 x -> fromGFinite x + R1 x -> fromGFinite x + toInteger (gcardinality (proxy# :: Proxy# a)) + {-# INLINE fromGFinite #-} + +instance (GFinite a, GFinite b) => GFinite (a :*: b) where + gcardinality _ = + gcardinality (proxy# :: Proxy# a) * gcardinality (proxy# :: Proxy# b) + {-# INLINE gcardinality #-} + + toGFinite n = toGFinite (toInteger q) :*: toGFinite (toInteger r) + where + cardB = gcardinality (proxy# :: Proxy# b) + (q, r) = Card n `quotRem` cardB + {-# INLINE toGFinite #-} + + fromGFinite (q :*: r) = + toInteger (gcardinality (proxy# :: Proxy# b) * Card (fromGFinite q)) + fromGFinite r + {-# INLINE fromGFinite #-} + +instance Finite Void +instance Finite () +instance Finite Bool +instance Finite Ordering + +instance Finite Char where + cardinality _ = Card $ toInteger (fromEnum (maxBound :: Char)) + 1 + {-# INLINE cardinality #-} + toFinite = toEnum . fromInteger + {-# INLINE toFinite #-} + fromFinite = toInteger . fromEnum + {-# INLINE fromFinite #-} + +cardinalityDef :: forall a. (Num a, FiniteBits a) => Proxy# a -> Cardinality +cardinalityDef _ = Shift (finiteBitSize (0 :: a)) + +toFiniteDef :: forall a. (Num a, FiniteBits a) => Integer -> a +toFiniteDef n + | isSigned (0 :: a) = fromInteger (n - bit (finiteBitSize (0 :: a) - 1)) + | otherwise = fromInteger n + +fromFiniteDef :: (Integral a, FiniteBits a) => a -> Integer +fromFiniteDef x + | isSigned x = toInteger x + bit (finiteBitSize x - 1) + | otherwise = toInteger x + +instance Finite Word8 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Word16 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Word32 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Word64 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Word where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Int8 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Int16 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Int32 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Int64 where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} +instance Finite Int where + cardinality = cardinalityDef + {-# INLINE cardinality #-} + toFinite = toFiniteDef + {-# INLINE toFinite #-} + fromFinite = fromFiniteDef + {-# INLINE fromFinite #-} + +instance Finite a => Finite (Maybe a) +instance (Finite a, Finite b) => Finite (Either a b) +instance (Finite a, Finite b) => Finite (a, b) +instance (Finite a, Finite b, Finite c) => Finite (a, b, c) +instance (Finite a, Finite b, Finite c, Finite d) => Finite (a, b, c, d) +instance (Finite a, Finite b, Finite c, Finite d, Finite e) => Finite (a, b, c, d, e) +instance (Finite a, Finite b, Finite c, Finite d, Finite e, Finite f) => Finite (a, b, c, d, e, f) diff --git a/src/System/Random/Internal.hs b/src/System/Random/Internal.hs index a0b3afc6c..75a8ea26f 100644 --- a/src/System/Random/Internal.hs +++ b/src/System/Random/Internal.hs @@ -10,6 +10,7 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE Trustworthy #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UnliftedFFITypes #-} @@ -52,6 +53,7 @@ module System.Random.Internal -- * Pseudo-random values of various types , Uniform(..) + , uniformViaFiniteM , UniformRange(..) , uniformByteStringM , uniformDouble01M @@ -66,10 +68,13 @@ module System.Random.Internal import Control.Arrow import Control.DeepSeq (NFData) -import Control.Monad.IO.Class +import Control.Monad (when) +import Control.Monad.Cont (ContT, runContT) +import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.ST import Control.Monad.ST.Unsafe -import Control.Monad.State.Strict +import Control.Monad.State.Strict (StateT(..), State, MonadState(..), runState) +import Control.Monad.Trans (lift) import Data.Bits import Data.ByteString.Builder.Prim (word64LE) import Data.ByteString.Builder.Prim.Internal (runF) @@ -80,10 +85,12 @@ import Foreign.C.Types import Foreign.Ptr (plusPtr) import Foreign.Storable (Storable(pokeByteOff)) import GHC.Exts +import GHC.Generics import GHC.IO (IO(..)) import GHC.Word import Numeric.Natural (Natural) import System.IO.Unsafe (unsafePerformIO) +import System.Random.GFinite (Cardinality(..), GFinite(..)) import qualified System.Random.SplitMix as SM import qualified System.Random.SplitMix32 as SM32 #if __GLASGOW_HASKELL__ >= 800 @@ -499,9 +506,79 @@ class Uniform a where -- | Generates a value uniformly distributed over all possible values of that -- type. -- + -- There is a default implementation via 'Generic': + -- + -- >>> :set -XDeriveGeneric -XDeriveAnyClass + -- >>> import GHC.Generics (Generic) + -- >>> import System.Random.Stateful + -- >>> data MyBool = MyTrue | MyFalse deriving (Show, Generic, Finite, Uniform) + -- >>> data Action = Code MyBool | Eat (Maybe Bool) | Sleep deriving (Show, Generic, Finite, Uniform) + -- >>> gen <- newIOGenM (mkStdGen 42) + -- >>> uniformListM 10 gen :: IO [Action] + -- [Code MyTrue,Code MyTrue,Eat Nothing,Code MyFalse,Eat (Just False),Eat (Just True),Eat Nothing,Eat (Just False),Sleep,Code MyFalse] + -- -- @since 1.2.0 uniformM :: StatefulGen g m => g -> m a + default uniformM :: (StatefulGen g m, Generic a, GUniform (Rep a)) => g -> m a + uniformM = fmap to . (`runContT` pure) . guniformM + +-- | Default implementation of 'Uniform' type class for 'Generic' data. +-- It's important to use 'ContT', because without it 'fmap' and '>>=' remain +-- polymorphic too long and GHC fails to inline or specialize it, ending up +-- building full 'Rep' a structure in memory. 'ContT' +-- makes 'fmap' and '>>=' used in 'guniformM' monomorphic, so GHC is able to +-- specialize 'Generic' instance reasonably close to a handwritten one. +class GUniform f where + guniformM :: StatefulGen g m => g -> ContT r m (f a) + +instance GUniform f => GUniform (M1 i c f) where + guniformM = fmap M1 . guniformM + {-# INLINE guniformM #-} + +instance Uniform a => GUniform (K1 i a) where + guniformM = fmap K1 . lift . uniformM + {-# INLINE guniformM #-} + +instance GUniform U1 where + guniformM = const $ return U1 + {-# INLINE guniformM #-} + +instance (GUniform f, GUniform g) => GUniform (f :*: g) where + guniformM g = (:*:) <$> guniformM g <*> guniformM g + {-# INLINE guniformM #-} + +instance (GFinite f, GFinite g) => GUniform (f :+: g) where + guniformM = lift . finiteUniformM + {-# INLINE guniformM #-} + +finiteUniformM :: forall g m f a. (StatefulGen g m, GFinite f) => g -> m (f a) +finiteUniformM = fmap toGFinite . case gcardinality (proxy# :: Proxy# f) of + Shift n + | n <= 64 -> fmap toInteger . unsignedBitmaskWithRejectionM uniformWord64 (bit n - 1) + | otherwise -> boundedByPowerOf2ExclusiveIntegralM n + Card n + | n <= bit 64 -> fmap toInteger . unsignedBitmaskWithRejectionM uniformWord64 (fromInteger n - 1) + | otherwise -> boundedExclusiveIntegralM n +{-# INLINE finiteUniformM #-} + +-- | A definition of 'Uniform' for 'Finite' types. +-- If your data has several fields of sub-'Word' cardinality, +-- this instance may be more efficient than one, derived via 'Generic' and 'GUniform'. +-- +-- >>> :set -XDeriveGeneric -XDeriveAnyClass +-- >>> import GHC.Generics (Generic) +-- >>> import System.Random.Stateful +-- >>> data Triple = Triple Word8 Word8 Word8 deriving (Show, Generic, Finite) +-- >>> instance Uniform Triple where uniformM = uniformViaFiniteM +-- >>> gen <- newIOGenM (mkStdGen 42) +-- >>> uniformListM 5 gen :: IO [Triple] +-- [Triple 60 226 48,Triple 234 194 151,Triple 112 96 95,Triple 51 251 15,Triple 6 0 208] +-- +uniformViaFiniteM :: (StatefulGen g m, Generic a, GFinite (Rep a)) => g -> m a +uniformViaFiniteM = fmap to . finiteUniformM +{-# INLINE uniformViaFiniteM #-} + -- | The class of types for which a uniformly distributed value can be drawn -- from a range. -- @@ -572,6 +649,7 @@ instance Uniform Word where fmap (fromIntegral :: Word64 -> Word) . uniformWord64 | otherwise = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 + {-# INLINE uniformM #-} instance UniformRange Word where {-# INLINE uniformRM #-} @@ -760,6 +838,11 @@ instance UniformRange Char where word32ToChar <$> unbiasedWordMult32RM (charToWord32 l, charToWord32 h) g {-# INLINE uniformRM #-} +instance Uniform () where + uniformM = const $ pure () +instance UniformRange () where + uniformRM = const $ const $ pure () + instance Uniform Bool where uniformM = fmap wordToBool . uniformWord8 where wordToBool w = (w .&. 1) /= 0 @@ -881,6 +964,8 @@ uniformIntegralM (l, h) gen = case l `compare` h of GT -> uniformIntegralM (h, l) gen EQ -> pure l {-# INLINEABLE uniformIntegralM #-} +{-# SPECIALIZE uniformIntegralM :: StatefulGen g m => (Integer, Integer) -> g -> m Integer #-} +{-# SPECIALIZE uniformIntegralM :: StatefulGen g m => (Natural, Natural) -> g -> m Natural #-} -- | Generate an integral in the range @[0, s)@ using a variant of Lemire's -- multiplication method. @@ -913,6 +998,14 @@ boundedExclusiveIntegralM s gen = go else return $ m `shiftR` k {-# INLINE boundedExclusiveIntegralM #-} +-- | boundedByPowerOf2ExclusiveIntegralM s ~ boundedExclusiveIntegralM (bit s) +boundedByPowerOf2ExclusiveIntegralM :: (Bits a, Integral a, StatefulGen g m) => Int -> g -> m a +boundedByPowerOf2ExclusiveIntegralM s gen = do + let n = (s + wordSizeInBits - 1) `quot` wordSizeInBits + x <- uniformIntegralWords n gen + return $ x .&. (bit s - 1) +{-# INLINE boundedByPowerOf2ExclusiveIntegralM #-} + -- | @integralWordSize i@ returns that least @w@ such that -- @i <= WORD_SIZE_IN_BITS^w@. integralWordSize :: (Bits a, Num a) => a -> Int diff --git a/src/System/Random/Stateful.hs b/src/System/Random/Stateful.hs index 5335db1e9..6ab1cde0d 100644 --- a/src/System/Random/Stateful.hs +++ b/src/System/Random/Stateful.hs @@ -70,6 +70,7 @@ module System.Random.Stateful -- $uniform , Uniform(..) , uniformListM + , uniformViaFiniteM , UniformRange(..) -- * Generators for sequences of pseudo-random bytes diff --git a/test/Spec.hs b/test/Spec.hs index 63eb126f3..97380798c 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1,5 +1,7 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE CPP #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -9,8 +11,10 @@ module Main (main) where import Data.ByteString.Short as SBS import Data.Int import Data.Typeable +import Data.Void import Data.Word import Foreign.C.Types +import GHC.Generics import Numeric.Natural (Natural) import System.Random import Test.SmallCheck.Series as SC @@ -42,7 +46,7 @@ main = , integralSpec (Proxy :: Proxy Int) , integralSpec (Proxy :: Proxy Char) , integralSpec (Proxy :: Proxy Bool) -#if __GLASGOW_HASKELL >= 802 +#if __GLASGOW_HASKELL__ >= 802 , integralSpec (Proxy :: Proxy CBool) #endif , integralSpec (Proxy :: Proxy CChar) @@ -141,3 +145,23 @@ runSpec = testGroup "runGenState_ and runPrimGenIO_" -- | Create a StdGen instance from an Int and pass it to the given function. seeded :: (StdGen -> a) -> Int -> a seeded f = f . mkStdGen + +data MyBool = MyTrue | MyFalse + deriving (Eq, Ord, Show, Generic, Finite, Uniform) +instance Monad m => Serial m MyBool + +data MyAction = Code (Maybe MyBool) | Never Void | Eat (Bool, Bool) | Sleep () + deriving (Eq, Ord, Show, Generic, Finite) +instance Monad m => Serial m MyAction +instance Uniform MyAction + +data Foo + = Quux Char + | Bar Int | Baz Word + | Bar8 Int8 | Baz8 Word8 + | Bar16 Int16 | Baz16 Word16 + | Bar32 Int32 | Baz32 Word32 + | Bar64 Int64 | Baz64 Word64 + | Final () + deriving (Eq, Ord, Show, Generic, Finite, Uniform) +instance Monad m => Serial m Foo diff --git a/test/doctests.hs b/test/doctests.hs index a20c7da08..c136088dd 100644 --- a/test/doctests.hs +++ b/test/doctests.hs @@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} module Main where -#if __GLASGOW_HASKELL__ >= 802 +#if __GLASGOW_HASKELL__ >= 802 && __GLASGOW_HASKELL__ != 810 import Test.DocTest (doctest)