packages feed

fused-effects-mwc-random-0.1.0.0: src/Control/Carrier/Random/Lifted.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}

-- | This carrier lifts the internals of its random number generation into
-- a 'LiftC' constraint, assuming the parameter to that 'LiftC' implements
-- 'PrimMonad'. In practice, this means that your effect stack must terminate
-- with @LiftC IO@ or @LiftC (ST s)@.
module Control.Carrier.Random.Lifted
  ( RandomC (..),
    runRandomSystem,
    runRandomSeeded,

    -- * Random effect
    module Control.Effect.Random,
  )
where

import Control.Algebra
import Control.Carrier.Lift
import Control.Carrier.Reader
import Control.Effect.Random
import Control.Effect.Sum
import Control.Monad.Fail
import Control.Monad.IO.Class
import Control.Monad.Primitive
import qualified System.Random.MWC as MWC
import qualified System.Random.MWC.Distributions as Dist

newtype RandomC prim m a = RandomC {runRandomC :: ReaderC (MWC.Gen (PrimState prim)) m a}
  deriving (Applicative, Functor, Monad, MonadFail, MonadIO)

instance (Algebra sig m, Member (Lift n) sig, PrimMonad n) => Algebra (Random :+: sig) (RandomC n m) where
  alg hdl sig ctx = do
    gen <- RandomC ask
    case sig of
      L (Random dist) -> do
        let act = case dist of
              Uniform -> MWC.uniform
              UniformR r -> MWC.uniformR r
              Normal m d -> Dist.normal m d
              Standard -> Dist.standard
              Exponential s -> Dist.exponential s
              TruncatedExp s r -> Dist.truncatedExp s r
              Gamma s h -> Dist.gamma s h
              ChiSquare d -> Dist.chiSquare d
              Beta a b -> Dist.beta a b
              Categorical w -> Dist.categorical w
              LogCategorical lw -> Dist.logCategorical lw
              Geometric0 p -> Dist.geometric0 p
              Geometric1 p -> Dist.geometric1 p
              Bernoulli p -> Dist.bernoulli p
              Dirichlet t -> Dist.dirichlet t
              Permutation n -> Dist.uniformPermutation n
              Shuffle v -> Dist.uniformShuffle v
        res <- sendM @n (act gen)
        pure (res <$ ctx)
      L Save -> do
        res <- sendM @n (MWC.save gen)
        pure (res <$ ctx)
      R other -> RandomC (alg (runRandomC . hdl) (R other) ctx)
  {-# INLINE alg #-}

-- | Run a computation, seeding its random values from the system random number generator.
--
-- This is the de facto standard way to use this carrier. Keep in mind that seeding the RNG
-- may be a computationally intensive process.
runRandomSystem :: MonadIO m => RandomC IO m a -> m a
runRandomSystem (RandomC act) = do
  rand <- liftIO MWC.createSystemRandom
  runReader rand act

-- | Run a computation, seeding its random values from an existing 'MWC.Seed'.
runRandomSeeded :: forall m n sig a. (Has (Lift n) sig m, PrimMonad n) => MWC.Seed -> RandomC n m a -> m a
runRandomSeeded s (RandomC act) = do
  rand <- sendM @n (MWC.restore s)
  runReader rand act