one-liner-instances-0.1.3.0: src/System/Random/Stateful/OneLiner.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
-- |
-- Module : System.Random.Stateful.OneLiner
-- Description : Derived methods for Random.
-- Copyright : (c) Justin Le 2021
-- License : BSD-3
-- Maintainer : justin@jle.im
-- Stability : unstable
-- Portability : portable
--
-- Derived methods for 'Uniform', using "Generics.OneLiner" and
-- "GHC.Generics".
--
-- Can be used for any types (deriving 'Generic') made with a single
-- constructor, where every field is an instance of 'Uniform'.
--
-- Also includes a newtype wrapper that imbues any such data type with
-- instant 'Uniform' instances, which can one day be used with /DerivingVia/
-- syntax to derive instances automatically.
--
-- @since 0.1.3.0
module System.Random.Stateful.OneLiner (
-- * Single constructor
-- ** Newtype wrapper
GUniform(..)
-- ** Generics-derived methods
, gUniformM
, gUniformRM
, gUniformListM
, gRandomM
, gRandomRM
-- * Multiple constructor
-- ** Newtype wrapper
, GUniformSum(..)
-- ** Generics-derived methods
, gUniformSumM
, gUniformRSumM
, gUniformSumListM
, gRandomSumM
, gRandomRSumM
) where
import Control.Monad
import Data.Data
import Data.Functor.Compose
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe
import GHC.Generics
import Generics.OneLiner
import System.Random
import System.Random.OneLiner
import System.Random.OneLiner.Internal (Pair(..), dePair)
import System.Random.Stateful
import qualified Data.List.NonEmpty as NE
-- | If @a@ is a data type with a single constructor whose fields are all
-- instances of 'Uniform', then @'GUniform' a@ has a 'Uniform' instance.
--
-- Will one day be able to be used with /DerivingVia/ syntax, to derive
-- instances automatically.
--
-- Only works with data types with single constructors. If you need it to
-- work with types of multiple constructors, consider 'GUniformSum'.
newtype GUniform a = GUniform { getGUniform :: a }
deriving (Eq, Ord, Show, Read, Data, Generic, Functor, Foldable, Traversable)
instance ( ADTRecord a
, Constraints a Uniform
)
=> Uniform (GUniform a) where
uniformM :: forall g m. StatefulGen g m => g -> m (GUniform a)
uniformM g = GUniform <$> gUniformM g
{-# INLINE uniformM #-}
instance ( ADTRecord a
, Constraints a UniformRange
)
=> UniformRange (GUniform a) where
uniformRM :: forall g m. StatefulGen g m => (GUniform a, GUniform a) -> g -> m (GUniform a)
uniformRM (GUniform l, GUniform r) g = GUniform <$> gUniformRM (l, r) g
{-# INLINE uniformRM #-}
gUniformM
:: forall a g m. (ADTRecord a, Constraints a Uniform, StatefulGen g m)
=> g
-> m a
gUniformM g = createA' @Uniform (uniformM g)
{-# INLINE gUniformM #-}
gUniformRM
:: forall a g m. (ADTRecord a, Constraints a UniformRange, StatefulGen g m)
=> (a, a) -> g -> m a
gUniformRM (l, u) g = dialgebra @UniformRange
((`uniformRM` g) . dePair)
(Pair l u)
{-# INLINE gUniformRM #-}
-- | Uses the 'Random' instance instead of the 'Uniform' instance.
gRandomM
:: forall a g m r. (ADTRecord a, Constraints a Random, RandomGenM g r m)
=> g -> m a
gRandomM = applyRandomGenM gRandom
-- | Uses the 'Random' instance instead of the 'Uniform' instance.
gRandomRM
:: forall a g m r. (ADTRecord a, Constraints a Random, RandomGenM g r m)
=> (a, a) -> g -> m a
gRandomRM r = applyRandomGenM (gRandomR r)
gUniformListM
:: forall a g m. (ADTRecord a, Constraints a Uniform, StatefulGen g m)
=> Int
-> g
-> m [a]
gUniformListM n g = replicateM n (gUniformM g)
-- | If @a@ is a data type whose fields are all instances of 'Uniform', then
-- @'GUniform' a@ has a 'Uniform' instance.
--
-- Will one day be able to be used with /DerivingVia/ syntax, to derive
-- instances automatically.
--
-- A version of 'GUniform' that works for data types with multiple
-- constructors. If your type has only one constructor, it might be more
-- performant to use 'GUniform'.
--
-- Note that the "ranged" variants are partial: if given a range of items
-- made with different constructors, will be 'error'!
newtype GUniformSum a = GUniformSum { getGUniformSum :: a }
deriving (Eq, Ord, Show, Read, Data, Generic, Functor, Foldable, Traversable)
instance ( ADT a
, Constraints a Uniform
)
=> Uniform (GUniformSum a) where
uniformM :: forall g m. StatefulGen g m => g -> m (GUniformSum a)
uniformM g = GUniformSum <$> gUniformSumM g
{-# INLINE uniformM #-}
-- | Note that this is partial: the range limits must have the same
-- constructors.
instance ( ADT a
, Constraints a UniformRange
)
=> UniformRange (GUniformSum a) where
uniformRM :: forall g m. StatefulGen g m => (GUniformSum a, GUniformSum a) -> g -> m (GUniformSum a)
uniformRM (GUniformSum l, GUniformSum u) g = GUniformSum <$> gUniformRSumM (l, u) g
{-# INLINE uniformRM #-}
-- | 'randomR' implemented by sequencing 'randomR' between all components.
--
-- If given a range of items made with different constructors, will be
-- 'error'!
gUniformRSumM
:: forall a g m. (ADT a, Constraints a UniformRange, StatefulGen g m)
=> (a, a) -> g -> m a
gUniformRSumM (l, u) g = fromMaybe (error badbad) . getCompose $
zipWithA @UniformRange
(\l' u' -> Compose (Just (uniformRM (l', u') g)))
l u
where
badbad = "gUniformRSum: Constructors in range do not match."
{-# INLINE gUniformRSumM #-}
-- | 'random' implemented by selecting a random constructor and sequencing
-- 'random' for all components.
gUniformSumM
:: forall a g m. (ADT a, Constraints a Uniform, StatefulGen g m)
=> g -> m a
gUniformSumM g = case options of
Nothing -> pure (error "gUniformSumM: Uninhabited type")
Just opts -> join $ reservoir opts g
where
options = NE.nonEmpty . getCompose $ createA @Uniform @a $
Compose [uniformM g]
{-# INLINE gUniformSumM #-}
-- | Uses the 'Random' instance instead of the 'Uniform' instance.
gRandomSumM
:: forall a g m r. (ADT a, Constraints a Random, RandomGenM g r m)
=> g -> m a
gRandomSumM = applyRandomGenM gRandomSum
-- | Uses the 'Random' instance instead of the 'Uniform' instance.
gRandomRSumM
:: forall a g m r. (ADT a, Constraints a Random, RandomGenM g r m)
=> (a, a) -> g -> m a
gRandomRSumM r = applyRandomGenM (gRandomRSum r)
gUniformSumListM
:: forall a g m. (ADT a, Constraints a Uniform, StatefulGen g m)
=> Int
-> g
-> m [a]
gUniformSumListM n g = replicateM n (gUniformSumM g)
-- | Select a random item from a non-empty list in constant memory, using
-- only a single traversal, using reservoir sampling.
reservoir :: StatefulGen g m => NE.NonEmpty a -> g -> m a
reservoir (x :| xs) g = go 1 x xs
where
go _ y [] = pure y
go !i y (z:zs) = do
j <- uniformWord64R i g
if j <= 0
then go (i + 1) z zs
else go (i + 1) y zs
{-# INLINE reservoir #-}