one-liner-instances-0.1.3.0: src/System/Random/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.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 'Random', 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 'Random'.
--
-- Also includes a newtype wrapper that imbues any such data type with
-- instant 'Random' instances, which can one day be used with /DerivingVia/
-- syntax to derive instances automatically.
--
-- @since 0.1.2.1
module System.Random.OneLiner (
-- * Single constructor
-- ** Newtype wrapper
GRandom(..)
-- ** Generics-derived methods
, gRandomR
, gRandom
, gRandomRs
, gRandoms
, gRandomRIO
, gRandomIO
-- * Multiple constructor
-- ** Newtype wrapper
, GRandomSum(..)
-- ** Generics-derived methods
, gRandomRSum
, gRandomSum
, gRandomRSums
, gRandomSums
, gRandomRIOSum
, gRandomIOSum
) where
import Control.Monad
import Data.Coerce
import Data.Data
import Data.Functor.Compose
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe
import GHC.Exts (build)
import GHC.Generics
import Generics.OneLiner
import System.Random
import System.Random.OneLiner.Internal (Pair(..), dePair, State(..))
import qualified Data.List.NonEmpty as NE
-- | If @a@ is a data type with a single constructor whose fields are all
-- instances of 'Random', then @'GRandom' a@ has a 'Random' 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 'GRandomSum'.
--
-- @since 0.1.2.1
newtype GRandom a = GRandom { getGRandom :: a }
deriving (Eq, Ord, Show, Read, Data, Generic, Functor, Foldable, Traversable)
instance ( ADTRecord a
, Constraints a Random
)
=> Random (GRandom a) where
randomR :: forall g. RandomGen g => (GRandom a, GRandom a) -> g -> (GRandom a, g)
randomR = coerce (gRandomR @a @g)
{-# INLINE randomR #-}
random :: forall g. RandomGen g => g -> (GRandom a, g)
random = coerce (gRandom @a @g)
{-# INLINE random #-}
randomRs :: forall g. RandomGen g => (GRandom a, GRandom a) -> g -> [GRandom a]
randomRs = coerce (gRandomRs @a @g)
{-# INLINE randomRs #-}
randoms :: forall g. RandomGen g => g -> [GRandom a]
randoms = coerce (gRandoms @a @g)
{-# INLINE randoms #-}
-- | 'randomR' implemented by sequencing 'randomR' between all components
--
-- Requires the type to have only a single constructor.
--
-- @since 0.1.2.1
gRandomR
:: forall a g. (ADTRecord a, Constraints a Random, RandomGen g)
=> (a, a) -> g -> (a, g)
gRandomR (l, u) = runState $
dialgebra @Random
(State . randomR . dePair)
(Pair l u)
{-# INLINE gRandomR #-}
-- | 'random' implemented by sequencing 'random' for all components.
--
-- Requires the type to have only a single constructor.
--
-- @since 0.1.2.1
gRandom
:: forall a g. (ADTRecord a, Constraints a Random, RandomGen g)
=> g -> (a, g)
gRandom = runState $ createA' @Random (State random)
{-# INLINE gRandom #-}
-- | 'randomRs' implemented by repeatedly calling 'gRandomR'.
--
-- @since 0.1.2.1
gRandomRs
:: forall a g. (ADTRecord a, Constraints a Random, RandomGen g)
=> (a, a) -> g -> [a]
gRandomRs ival g = build (\cons _nil -> buildRandoms cons (gRandomR ival) g)
{-# INLINE gRandomRs #-}
-- | 'randoms' implemented by repeatedly calling 'gRandom'.
--
-- @since 0.1.2.1
gRandoms
:: forall a g. (ADTRecord a, Constraints a Random, RandomGen g)
=> g -> [a]
gRandoms g = build (\cons _nil -> buildRandoms cons gRandom g)
{-# INLINE gRandoms #-}
-- | 'randomRIO' implemented by calling 'gRandomR' on the global seed.
--
-- @since 0.1.2.1
gRandomRIO
:: forall a. (ADTRecord a, Constraints a Random)
=> (a, a) -> IO a
gRandomRIO range = getStdRandom (gRandomR range)
{-# INLINE gRandomRIO #-}
-- | 'randomIO' implemented by calling 'gRandom' on the global seed.
--
-- @since 0.1.2.1
gRandomIO
:: forall a. (ADTRecord a, Constraints a Random)
=> IO a
gRandomIO = getStdRandom gRandom
{-# INLINE gRandomIO #-}
-- | If @a@ is a data type whose fields are all instances of 'Random', then
-- @'GRandom' a@ has a 'Random' instance.
--
-- Will one day be able to be used with /DerivingVia/ syntax, to derive
-- instances automatically.
--
-- A version of 'GRandom' that works for data types with multiple
-- constructors. If your type has only one constructor, it might be more
-- performant to use 'GRandom'.
--
-- Note that the "ranged" variants are partial: if given a range of items
-- made with different constructors, will be 'error'!
--
-- @since 0.1.2.1
newtype GRandomSum a = GRandomSum { getGRandomSum :: a }
deriving (Eq, Ord, Show, Read, Data, Generic, Functor, Foldable, Traversable)
instance ( ADT a
, Constraints a Random
)
=> Random (GRandomSum a) where
randomR :: forall g. RandomGen g => (GRandomSum a, GRandomSum a) -> g -> (GRandomSum a, g)
randomR = coerce (gRandomRSum @a @g)
{-# INLINE randomR #-}
random :: forall g. RandomGen g => g -> (GRandomSum a, g)
random = coerce (gRandomSum @a @g)
{-# INLINE random #-}
randomRs :: forall g. RandomGen g => (GRandomSum a, GRandomSum a) -> g -> [GRandomSum a]
randomRs = coerce (gRandomRSums @a @g)
{-# INLINE randomRs #-}
randoms :: forall g. RandomGen g => g -> [GRandomSum a]
randoms = coerce (gRandomSums @a @g)
{-# INLINE randoms #-}
-- | 'randomR' implemented by sequencing 'randomR' between all components.
--
-- If given a range of items made with different constructors, will be
-- 'error'!
--
-- @since 0.1.2.1
gRandomRSum
:: forall a g. (ADT a, Constraints a Random, RandomGen g)
=> (a, a) -> g -> (a, g)
gRandomRSum (l, u) = runState . fromMaybe (error badbad) . getCompose $
zipWithA @Random (\l' u' -> Compose (Just (State (randomR (l', u')))))
l u
where
badbad = "gRandomRSum: Constructors in range do not match."
{-# INLINE gRandomRSum #-}
-- | 'random' implemented by selecting a random constructor and sequencing
-- 'random' for all components.
--
-- @since 0.1.2.1
gRandomSum
:: forall a g. (ADT a, Constraints a Random, RandomGen g)
=> g -> (a, g)
gRandomSum = case options of
Nothing -> (error "gRandomSum: Uninhabited type",)
Just opts -> runState (join (reservoir opts))
where
options = NE.nonEmpty . getCompose $ createA @Random @a $
Compose [State random]
{-# INLINE gRandomSum #-}
-- | 'randomRs' implemented by repeatedly calling 'gRandomRSum'.
--
-- If given a range of items made with different constructors, will be
-- 'error'!
--
-- @since 0.1.2.1
gRandomRSums
:: forall a g. (ADT a, Constraints a Random, RandomGen g)
=> (a, a) -> g -> [a]
gRandomRSums ival g = build (\cons _nil -> buildRandoms cons (gRandomRSum ival) g)
{-# INLINE gRandomRSums #-}
-- | 'randoms' implemented by repeatedly calling 'gRandomSum'.
--
-- @since 0.1.2.1
gRandomSums
:: forall a g. (ADT a, Constraints a Random, RandomGen g)
=> g -> [a]
gRandomSums g = build (\cons _nil -> buildRandoms cons gRandomSum g)
{-# INLINE gRandomSums #-}
-- | 'randomRIO' implemented by calling 'gRandomRSum' on the global seed.
--
-- If given a range of items made with different constructors, will be
-- 'error'!
--
-- @since 0.1.2.1
gRandomRIOSum
:: forall a. (ADT a, Constraints a Random)
=> (a, a) -> IO a
gRandomRIOSum range = getStdRandom (gRandomRSum range)
{-# INLINE gRandomRIOSum #-}
-- | 'randomIO' implemented by calling 'gRandom' on the global seed.
gRandomIOSum
:: forall a. (ADT a, Constraints a Random)
=> IO a
gRandomIOSum = getStdRandom gRandomSum
{-# INLINE gRandomIOSum #-}
buildRandoms :: RandomGen g
=> (a -> as -> as) -- ^ E.g. '(:)' but subject to fusion
-> (g -> (a,g)) -- ^ E.g. 'random'
-> g -- ^ A 'RandomGen' instance
-> as
buildRandoms cons rand = go
where
-- The seq fixes part of #4218 and also makes fused Core simpler.
go g = x `seq` (x `cons` go g') where (x,g') = rand g
{-# INLINE buildRandoms #-}
-- | Select a random item from a non-empty list in constant memory, using
-- only a single traversal, using reservoir sampling.
reservoir :: RandomGen g => NE.NonEmpty a -> State g a
reservoir (x :| xs) = go 2 x xs
where
go _ y [] = pure y
go !i y (z:zs) = do
j <- State $ randomR @Int (1, i)
if j <= 1
then go (i + 1) z zs
else go (i + 1) y zs
{-# INLINE reservoir #-}