packages feed

polysemy-zoo 0.1.1.0 → 0.1.2.0

raw patch · 8 files changed

+703/−4 lines, 8 filesdep +constraintsdep +mtldep +randomdep ~polysemyPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: constraints, mtl, random, random-fu, random-source, reflection, text

Dependency ranges changed: polysemy

API changes (from Hackage documentation)

+ Polysemy.IdempotentLowering: fixedNat :: forall m n base. Applicative base => ((forall x. m x -> n x) -> forall x. m x -> n x) -> base (forall x. m x -> n x)
+ Polysemy.IdempotentLowering: fixedNat' :: forall m n f base. Applicative base => ((forall x. m x -> n (f x)) -> forall x. m x -> n (f x)) -> base (forall x. m x -> n (f x))
+ Polysemy.MTL: ConstrainedAction :: m x -> ConstrainedAction
+ Polysemy.MTL: Proxy :: Proxy
+ Polysemy.MTL: Sub :: (a -> Dict b) -> (:-) a b
+ Polysemy.MTL: [Dict] :: forall a. a => Dict a
+ Polysemy.MTL: [action] :: ConstrainedAction -> m x
+ Polysemy.MTL: absorb :: forall p r a. IsCanonicalEffect p r => (p (Sem r) => Sem r a) -> Sem r a
+ Polysemy.MTL: absorbError :: forall e r a. Member (Error e) r => (MonadError e (Sem r) => Sem r a) -> Sem r a
+ Polysemy.MTL: absorbReader :: Member (Reader i) r => (MonadReader i (Sem r) => Sem r a) -> Sem r a
+ Polysemy.MTL: absorbState :: Member (State s) r => (MonadState s (Sem r) => Sem r a) -> Sem r a
+ Polysemy.MTL: absorbWriter :: (Monoid w, Member (Writer w) r) => (MonadWriter w (Sem r) => Sem r a) -> Sem r a
+ Polysemy.MTL: canonicalDictionary :: IsCanonicalEffect p r => Dict1 p (Sem r)
+ Polysemy.MTL: class ReifiableConstraint1 p => IsCanonicalEffect p r
+ Polysemy.MTL: class ReifiableConstraint1 p where {
+ Polysemy.MTL: class Reifies (s :: k) a | s -> a
+ Polysemy.MTL: data Dict a
+ Polysemy.MTL: data family Dict1 (p :: (Type -> Type) -> Constraint) (m :: Type -> Type);
+ Polysemy.MTL: data Proxy (t :: k) :: forall k. () => k -> Type
+ Polysemy.MTL: infixr 9 :-
+ Polysemy.MTL: instance (GHC.Base.Monad m, Data.Reflection.Reifies s' (Polysemy.MTL.Dict1 (Control.Monad.Error.Class.MonadError e) m)) => Control.Monad.Error.Class.MonadError e (Polysemy.MTL.ConstrainedAction (Control.Monad.Error.Class.MonadError e) m s')
+ Polysemy.MTL: instance (GHC.Base.Monad m, Data.Reflection.Reifies s' (Polysemy.MTL.Dict1 (Control.Monad.Reader.Class.MonadReader i) m)) => Control.Monad.Reader.Class.MonadReader i (Polysemy.MTL.ConstrainedAction (Control.Monad.Reader.Class.MonadReader i) m s')
+ Polysemy.MTL: instance (GHC.Base.Monad m, Data.Reflection.Reifies s' (Polysemy.MTL.Dict1 (Control.Monad.State.Class.MonadState s) m)) => Control.Monad.State.Class.MonadState s (Polysemy.MTL.ConstrainedAction (Control.Monad.State.Class.MonadState s) m s')
+ Polysemy.MTL: instance (GHC.Base.Monad m, GHC.Base.Monoid w, Data.Reflection.Reifies s' (Polysemy.MTL.Dict1 (Control.Monad.Writer.Class.MonadWriter w) m)) => Control.Monad.Writer.Class.MonadWriter w (Polysemy.MTL.ConstrainedAction (Control.Monad.Writer.Class.MonadWriter w) m s')
+ Polysemy.MTL: instance (GHC.Base.Monoid w, Polysemy.Internal.Union.Member (Polysemy.Writer.Writer w) r) => Polysemy.MTL.IsCanonicalEffect (Control.Monad.Writer.Class.MonadWriter w) r
+ Polysemy.MTL: instance GHC.Base.Applicative m => GHC.Base.Applicative (Polysemy.MTL.ConstrainedAction p m s)
+ Polysemy.MTL: instance GHC.Base.Functor m => GHC.Base.Functor (Polysemy.MTL.ConstrainedAction p m s)
+ Polysemy.MTL: instance GHC.Base.Monad m => GHC.Base.Monad (Polysemy.MTL.ConstrainedAction p m s)
+ Polysemy.MTL: instance GHC.Base.Monoid w => Polysemy.MTL.ReifiableConstraint1 (Control.Monad.Writer.Class.MonadWriter w)
+ Polysemy.MTL: instance Polysemy.Internal.Union.Member (Polysemy.Error.Error e) r => Polysemy.MTL.IsCanonicalEffect (Control.Monad.Error.Class.MonadError e) r
+ Polysemy.MTL: instance Polysemy.Internal.Union.Member (Polysemy.Reader.Reader i) r => Polysemy.MTL.IsCanonicalEffect (Control.Monad.Reader.Class.MonadReader i) r
+ Polysemy.MTL: instance Polysemy.Internal.Union.Member (Polysemy.State.State s) r => Polysemy.MTL.IsCanonicalEffect (Control.Monad.State.Class.MonadState s) r
+ Polysemy.MTL: instance Polysemy.MTL.ReifiableConstraint1 (Control.Monad.Error.Class.MonadError e)
+ Polysemy.MTL: instance Polysemy.MTL.ReifiableConstraint1 (Control.Monad.Reader.Class.MonadReader i)
+ Polysemy.MTL: instance Polysemy.MTL.ReifiableConstraint1 (Control.Monad.State.Class.MonadState s)
+ Polysemy.MTL: newtype (:-) a b
+ Polysemy.MTL: newtype ConstrainedAction (p :: (Type -> Type) -> Constraint) (m :: Type -> Type) (s :: Type) (x :: Type)
+ Polysemy.MTL: reflect :: Reifies s a => proxy s -> a
+ Polysemy.MTL: reifiedInstance :: (ReifiableConstraint1 p, Monad m) => Reifies s (Dict1 p m) :- p (ConstrainedAction p m s)
+ Polysemy.MTL: type family CanonicalEffect (p :: (Type -> Type) -> Constraint) :: (Type -> Type) -> Type -> Type
+ Polysemy.MTL: }
+ Polysemy.Random: [RandomR] :: Random x => (x, x) -> Random m x
+ Polysemy.Random: [Random] :: Random x => Random m x
+ Polysemy.Random: data Random m a
+ Polysemy.Random: random :: forall r_aqze a_aqxt. (Member Random r_aqze, Random a_aqxt) => Sem r_aqze a_aqxt
+ Polysemy.Random: randomR :: forall r_aqzf a_aqxt. (Member Random r_aqzf, Random a_aqxt) => (a_aqxt, a_aqxt) -> Sem r_aqzf a_aqxt
+ Polysemy.Random: runRandom :: forall q r a. RandomGen q => q -> Sem (Random : r) a -> Sem r (q, a)
+ Polysemy.Random: runRandomIO :: Member (Lift IO) r => Sem (Random : r) a -> Sem r a
+ Polysemy.RandomFu: [GetRandomPrim] :: Prim t -> RandomFu m t
+ Polysemy.RandomFu: [SampleRVar] :: RVar t -> RandomFu m t
+ Polysemy.RandomFu: absorbMonadRandom :: Member RandomFu r => (MonadRandom (Sem r) => Sem r a) -> Sem r a
+ Polysemy.RandomFu: data RandomFu m r
+ Polysemy.RandomFu: getRandomPrim :: forall r_atnn r_ati9. Member RandomFu r_atnn => Prim r_ati9 -> Sem r_atnn r_ati9
+ Polysemy.RandomFu: instance (GHC.Base.Monad m, Data.Reflection.Reifies s' (Polysemy.MTL.Dict1 Data.Random.Internal.Source.MonadRandom m)) => Data.Random.Internal.Source.MonadRandom (Polysemy.MTL.ConstrainedAction Data.Random.Internal.Source.MonadRandom m s')
+ Polysemy.RandomFu: instance Polysemy.Internal.Union.Member Polysemy.RandomFu.RandomFu r => Polysemy.MTL.IsCanonicalEffect Data.Random.Internal.Source.MonadRandom r
+ Polysemy.RandomFu: instance Polysemy.MTL.ReifiableConstraint1 Data.Random.Internal.Source.MonadRandom
+ Polysemy.RandomFu: runRandomIO :: forall r a. MonadIO (Sem r) => Sem (RandomFu : r) a -> Sem r a
+ Polysemy.RandomFu: runRandomIOPureMT :: MonadIO (Sem r) => PureMT -> Sem (RandomFu : r) a -> Sem r a
+ Polysemy.RandomFu: runRandomSource :: forall s r a. RandomSource (Sem r) s => s -> Sem (RandomFu : r) a -> Sem r a
+ Polysemy.RandomFu: sampleDist :: (Member RandomFu r, Distribution d t) => d t -> Sem r t
+ Polysemy.RandomFu: sampleRVar :: forall r_atnm r_ati9. Member RandomFu r_atnm => RVar r_ati9 -> Sem r_atnm r_ati9
- Polysemy.KVStore: lookupKV :: forall k_aeVO v_aeVP. forall r_afpZ. Member (KVStore k_aeVO v_aeVP) r_afpZ => k_aeVO -> Sem r_afpZ (Maybe v_aeVP)
+ Polysemy.KVStore: lookupKV :: forall k_acRs v_acRt r_afMP. Member (KVStore k_acRs v_acRt) r_afMP => k_acRs -> Sem r_afMP (Maybe v_acRt)
- Polysemy.KVStore: updateKV :: forall k_aeWG v_aeWH. forall r_afq0. Member (KVStore k_aeWG v_aeWH) r_afq0 => k_aeWG -> Maybe v_aeWH -> Sem r_afq0 ()
+ Polysemy.KVStore: updateKV :: forall k_acRs v_acRt r_afMQ. Member (KVStore k_acRs v_acRt) r_afMQ => k_acRs -> Maybe v_acRt -> Sem r_afMQ ()

Files

ChangeLog.md view
@@ -1,8 +1,17 @@ # Changelog for polysemy-zoo +## 0.1.2.0 (2019-06-01)++- Added `Polysemy.MTL` for inter-op with MTL (thanks to @adamConnerSax)+- Moved `Polysemy.Random` from `polysemy`+- Added `Polysemy.RandomFu` (thanks to @adamConnerSax)+- Added `fixedNat` and `fixedNat'` to `Polysemy.IdempotentLowering` for working+    with higher-order effects.+ ## 0.1.1.0 (2019-05-22)  - Added `Polysemy.IdempotentLowering`   ## Unreleased changes+
polysemy-zoo.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 1c51b9ddcd467cc5950fc9a4c63030c540dc98d4a3f594ef63ac0a3a95d7fc41+-- hash: 07f2fe507c2afe8ef70bcc42b26d3eaa3541eee97656ce45a5afa9ea207334cc  name:           polysemy-zoo-version:        0.1.1.0+version:        0.1.2.0 synopsis:       Experimental, user-contributed effects and interpreters for polysemy description:    Please see the README on GitHub at <https://github.com/isovector/polysemy-zoo#readme> category:       Polysemy@@ -31,7 +31,10 @@   exposed-modules:       Polysemy.IdempotentLowering       Polysemy.KVStore+      Polysemy.MTL       Polysemy.Operators+      Polysemy.Random+      Polysemy.RandomFu   other-modules:       Paths_polysemy_zoo   hs-source-dirs:@@ -40,9 +43,15 @@   ghc-options: -fplugin=Polysemy.Plugin   build-depends:       base >=4.7 && <5+    , constraints     , containers-    , polysemy+    , mtl >=2.0.1.0 && <3.0.0.0+    , polysemy >=0.3     , polysemy-plugin+    , random >=1.1 && <1.2+    , random-fu+    , random-source+    , reflection   default-language: Haskell2010  test-suite polysemy-zoo-test@@ -51,6 +60,8 @@   other-modules:       IdempotentLoweringSpec       KVStoreSpec+      MTLSpec+      RandomFuSpec       Paths_polysemy_zoo   hs-source-dirs:       test@@ -58,9 +69,16 @@   ghc-options: -fplugin=Polysemy.Plugin -threaded -rtsopts -with-rtsopts=-N   build-depends:       base >=4.7 && <5+    , constraints     , containers     , hspec-    , polysemy+    , mtl >=2.0.1.0 && <3.0.0.0+    , polysemy >=0.3     , polysemy-plugin     , polysemy-zoo+    , random >=1.1 && <1.2+    , random-fu+    , random-source+    , reflection+    , text   default-language: Haskell2010
src/Polysemy/IdempotentLowering.hs view
@@ -4,9 +4,11 @@   ( (.@!)   , nat   , liftNat+  , fixedNat   , (.@@!)   , nat'   , liftNat'+  , fixedNat'   ) where  import Polysemy@@ -132,4 +134,38 @@   => (forall x. (forall y. f y -> g y) -> m x -> n (f x))   -> (forall y. f y -> g y) -> base (forall x. m x -> n (f x)) liftNat' z a = nat' $ z a+++------------------------------------------------------------------------------+-- | Like 'nat', but for higher-order interpreters that need access to+-- themselves.+--+-- For example:+--+-- @+-- 'fixedNat' $ \me -> 'Polysemy.interpretH' $ \case+--   SomeEffect -> ...+-- @+fixedNat+    :: forall m n base+     . Applicative base+    => ((forall x. m x -> n x) -> (forall x. m x -> n x))+    -> base (forall x. m x -> n x)+fixedNat f =+  let x :: (forall x. m x -> n x)+      x = f x+   in nat x+++------------------------------------------------------------------------------+-- | 'fixedNat'' is to 'fixedNat' as 'nat'' is to 'nat'.+fixedNat'+    :: forall m n f base+     . Applicative base+    => ((forall x. m x -> n (f x)) -> (forall x. m x -> n (f x)))+    -> base (forall x. m x -> n (f x))+fixedNat' f =+  let x :: (forall x. m x -> n (f x))+      x = f x+   in nat' x 
+ src/Polysemy/MTL.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE AllowAmbiguousTypes         #-}+{-# LANGUAGE ConstraintKinds             #-}+{-# LANGUAGE FlexibleInstances           #-}+{-# LANGUAGE GeneralizedNewtypeDeriving  #-}+{-# LANGUAGE MultiParamTypeClasses       #-}+{-# LANGUAGE ScopedTypeVariables         #-}+{-# LANGUAGE TypeFamilies                #-}+{-# LANGUAGE UndecidableInstances        #-}+{-# LANGUAGE UndecidableSuperClasses     #-}+{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}++module Polysemy.MTL+  (+    -- * Types+    CanonicalEffect+  , ConstrainedAction (..)+  , ReifiableConstraint1 (..)+  , IsCanonicalEffect (..)++    -- * constraint-polymorphic absorber+  , absorb+  +    -- * constraint-monomorphic absorbers+  , absorbReader+  , absorbState+  , absorbWriter+  , absorbError++    -- * Re-exports+  , Reifies+  , (:-)(Sub)+  , Dict(Dict)+  , reflect+  , Proxy (Proxy)+  )+where+++import qualified Control.Monad.Reader.Class as S+import qualified Control.Monad.State.Class as S+import qualified Control.Monad.Writer.Class as S+import qualified Control.Monad.Error.Class as S+import qualified Data.Constraint as C+import           Data.Constraint (Dict(Dict),(:-)(Sub),(\\))+import qualified Data.Constraint.Unsafe as C+import           Data.Proxy (Proxy (..))+import qualified Data.Reflection as R+import           Data.Reflection (Reifies, reflect) +import           Data.Kind (Type, Constraint)++import           Polysemy+import           Polysemy.Reader+import           Polysemy.Writer+import           Polysemy.State+import           Polysemy.Error++------------------------------------------------------------------------------+-- | Open type-family mapping a single constraint of the form+-- @(Type -> Type) -> Constraint@, e.g., @MonadState s@,+-- to a polysemy effect which can be used to re-interpret+-- that constraint, e.g., 'State s'.+type family CanonicalEffect (p :: (Type -> Type) -> Constraint) :: (Type -> Type) -> Type -> Type++type instance  CanonicalEffect (S.MonadReader env) = Reader env+type instance  CanonicalEffect (S.MonadWriter w)   = Writer w+type instance  CanonicalEffect (S.MonadState s)    = State s+type instance  CanonicalEffect (S.MonadError e)    = Error e+++-- | A newtype wrapper for a monadic action, parameterized by+-- a constraint, @p@ on a @(Type -> Type)@ (e.g., a monad); @m@, a specific+-- @(Type -> Type)@; and a polysemy effect type-list @r@. With "Data.Reflection"+-- we can create instances of @p (ConstrainedAction p m r)@ using functions from+-- @Sem r@.+newtype ConstrainedAction (p :: (Type -> Type) -> Constraint)+                          (m :: Type -> Type)+                          (s :: Type)+                          (x :: Type)+  = ConstrainedAction+    { action :: m x+    } deriving (Functor, Applicative, Monad)++-- | For a constraint to be "absorbable" by @Sem r@,+-- there needs to be an instance of this class,+-- containing the dictionary signatures as a record of functions and the+-- reflected entailment of @p (ConstrainedAction p m r)@ from the reified dictionary.+class ReifiableConstraint1 p where+  data Dict1 (p :: (Type -> Type) -> Constraint) (m :: Type -> Type)+  reifiedInstance :: Monad m => R.Reifies s (Dict1 p m) :- p (ConstrainedAction p m s)++-- | This class contains an instance of the dictionary for some set of effects+-- parameterized by a polysemy effect list @r@.+-- Typically, you would write this instance for any @r@+-- satisfying the constraint that the "canonical" effect is a member.  But you+-- could also use it to discharge constraints which require multiple polysemy effects.+class ReifiableConstraint1 p => IsCanonicalEffect p r where+  canonicalDictionary :: Dict1 p (Sem r)++-- | Given a reifiable constraint, and a dictionary to use, discharge the constraint.+using :: forall p m a. (Monad m, ReifiableConstraint1 p)+  => Dict1 p m -> (p m => m a) -> m a+using d m =+  R.reify d $ \(_ :: Proxy s) -> m \\ C.trans+  (C.unsafeCoerceConstraint :: ((p (ConstrainedAction p m s) :- p m))) reifiedInstance+{-# INLINEABLE using #-}++-- | Given a "canonical" dictionary for @p@ using the polysemy effects in @r@,+-- discharge the constraint @p@.+absorb :: forall p r a. IsCanonicalEffect p r => (p (Sem r) => Sem r a) -> Sem r a+absorb = using @p canonicalDictionary+{-# INLINEABLE absorb #-}++------------------------------------------------------------------------------+absorbReader :: Member (Reader i) r+  => (S.MonadReader i (Sem r) => Sem r a) -> Sem r a+absorbReader = absorb @(S.MonadReader _)+{-# INLINEABLE absorbReader #-}++instance ReifiableConstraint1 (S.MonadReader i) where+  data Dict1 (S.MonadReader i) m = MonadReader+    { ask_ :: m i+    , local_ :: forall a. (i -> i) -> m a -> m a+    }+  reifiedInstance = Sub Dict++instance ( Monad m+         , R.Reifies s' (Dict1 (S.MonadReader i) m)+         ) => S.MonadReader i (ConstrainedAction (S.MonadReader i) m s') where+  ask = ConstrainedAction $ ask_ $ R.reflect $ Proxy @s'+  {-# INLINEABLE ask #-}+  local f m = ConstrainedAction $ local_ (R.reflect $ Proxy @s') f $ action m+  {-# INLINEABLE local #-}+  +instance Member (Reader i) r => IsCanonicalEffect (S.MonadReader i) r where+  canonicalDictionary = MonadReader ask local+  {-# INLINEABLE canonicalDictionary #-}+------------------------------------------------------------------------------+absorbState :: Member (State s) r+  => (S.MonadState s (Sem r) => Sem r a) -> Sem r a+absorbState = absorb @(S.MonadState _)+{-# INLINEABLE absorbState #-}++instance ReifiableConstraint1 (S.MonadState s) where+  data Dict1 (S.MonadState s) m = MonadState+    { get_ :: m s+    , put_ :: s -> m ()+    }+  reifiedInstance = Sub Dict++instance ( Monad m+         , R.Reifies s' (Dict1 (S.MonadState s) m)+         ) => S.MonadState s (ConstrainedAction (S.MonadState s) m s') where+  get = ConstrainedAction $ get_ $ R.reflect $ Proxy @s'+  {-# INLINEABLE get #-}  +  put s = ConstrainedAction $ put_ (R.reflect $ Proxy @s') s+  {-# INLINEABLE put #-}++instance Member (State s) r => IsCanonicalEffect (S.MonadState s) r where+  canonicalDictionary = MonadState get put+  {-# INLINEABLE canonicalDictionary #-}+  +--------------------------------------------------------------------------------+absorbWriter :: (Monoid w, Member (Writer w) r)+  => (S.MonadWriter w (Sem r) => Sem r a) -> Sem r a+absorbWriter = absorb @(S.MonadWriter _)+{-# INLINEABLE absorbWriter #-}++instance Monoid w => ReifiableConstraint1 (S.MonadWriter w) where+  data Dict1 (S.MonadWriter w) m = MonadWriter+    { tell_ :: w -> m ()+    , listen_ :: forall a. m a -> m (a, w)+    , pass_ :: forall a. m (a, w -> w) -> m a +    }+  reifiedInstance = Sub Dict++instance ( Monad m+         , Monoid w+         , R.Reifies s' (Dict1 (S.MonadWriter w) m)+         ) => S.MonadWriter w (ConstrainedAction (S.MonadWriter w) m s') where+  tell w = ConstrainedAction $ tell_ (R.reflect $ Proxy @s') w+  {-# INLINEABLE tell #-}  +  listen x = ConstrainedAction $ listen_ (R.reflect $ Proxy @s') (action x)+  {-# INLINEABLE listen #-}  +  pass x = ConstrainedAction $ pass_ (R.reflect $ Proxy @s') (action x)+  {-# INLINEABLE pass #-}  ++{- This one requires a little work since the polysemy writer is a bit different from the+mtl-standard one+-} +instance (Monoid w, Member (Writer w) r) => IsCanonicalEffect (S.MonadWriter w) r where+  canonicalDictionary = MonadWriter tell semListen semPass where+    semListen = fmap (\(x,y) -> (y,x)) . listen+    semPass :: Member (Writer w) r => Sem r (a, w -> w) -> Sem r a +    semPass x = do+      (w, (a, f)) <- listen x+      censor f (tell w >> pure a)+  {-# INLINEABLE canonicalDictionary #-}+  +--------------------------------------------------------------------------------+absorbError :: forall e r a. Member (Error e) r+  => (S.MonadError e (Sem r) => Sem r a) -> Sem r a+absorbError = absorb @(S.MonadError e)+{-# INLINEABLE absorbError #-}++instance ReifiableConstraint1 (S.MonadError e) where+  data Dict1 (S.MonadError e) m = MonadError+    { throwError_ :: forall a. e -> m a+    , catchError_ :: forall a. m a -> (e -> m a) -> m a+    }+  reifiedInstance = Sub Dict++instance ( Monad m+         , R.Reifies s' (Dict1 (S.MonadError e) m)+         ) => S.MonadError e (ConstrainedAction (S.MonadError e) m s') where+  throwError e = ConstrainedAction $ throwError_ (R.reflect $ Proxy @s') e+  {-# INLINEABLE throwError #-}+  catchError x f = ConstrainedAction $ catchError_ (R.reflect $ Proxy @s') (action x) (action . f)+  {-# INLINEABLE catchError #-}+  +instance Member (Error e) r => IsCanonicalEffect (S.MonadError e) r where+  canonicalDictionary = MonadError throw catch +  {-# INLINEABLE canonicalDictionary #-}
+ src/Polysemy/Random.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE BlockArguments  #-}+{-# LANGUAGE TemplateHaskell #-}++module Polysemy.Random+  ( -- * Effect+    Random (..)++    -- * Actions+  , random+  , randomR++    -- * Interpretations+  , runRandom+  , runRandomIO+  ) where++import           Polysemy+import           Polysemy.State+import qualified System.Random as R++------------------------------------------------------------------------------+-- | An effect capable of providing 'R.Random' values.+data Random m a where+  Random :: R.Random x => Random m x+  RandomR :: R.Random x => (x, x) -> Random m x++makeSem ''Random+++------------------------------------------------------------------------------+-- | Run a 'Random' effect with an explicit 'R.RandomGen'.+runRandom+    :: forall q r a+     . R.RandomGen q+    => q+    -> Sem (Random ': r) a+    -> Sem r (q, a)+runRandom q = runState q . reinterpret \case+  Random -> do+    ~(a, q') <- gets @q R.random+    put q'+    pure a+  RandomR r -> do+    ~(a, q') <- gets @q $ R.randomR r+    put q'+    pure a+{-# INLINE runRandom #-}+++------------------------------------------------------------------------------+-- | Run a 'Random' effect by using the 'IO' random generator.+runRandomIO :: Member (Lift IO) r => Sem (Random ': r) a -> Sem r a+runRandomIO m = do+  q <- sendM R.newStdGen+  snd <$> runRandom q m+{-# INLINE runRandomIO #-}+
+ src/Polysemy/RandomFu.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TemplateHaskell       #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE UndecidableInstances  #-}+{-|+Module      : Polysemy.RandomFu+Description : Polysemy random-fu effect++Polysemy "random-fu" effect.+This can be run in a few ways:+1. Directly in 'IO'+2. Using any 'Data.Random.RandomSource' from "random-fu"+3. In 'IO', using a given 'Data.Random.Source.PureMT' source.+('IO' is used to put the source in an 'IORef')++This module also contains the type-class instances to enable "absorbing"+MonadRandom, ala Polysemy.MTL.  See the tests for MTL or RandomFu for+examples of that in use.+-}++module Polysemy.RandomFu+  (+    -- * Effect+    RandomFu (..)++    -- * Actions+  , sampleRVar+  , getRandomPrim+  , sampleDist++    -- * Interpretations+  , runRandomSource+  , runRandomIO+  , runRandomIOPureMT++    -- * Constraint absorber+  , absorbMonadRandom+  )+where++import           Polysemy+import           Polysemy.MTL++import           Data.IORef                     ( newIORef )+import qualified Data.Random                   as R+import qualified Data.Random.Source            as R+import qualified Data.Random.Internal.Source   as R+import qualified Data.Random.Source.PureMT     as R+import           Control.Monad.IO.Class         ( MonadIO(..) )+++------------------------------------------------------------------------------+{- | An effect capable of sampling from a "random-fu" RVar or generating a+single random-variate of any type, @t@ with a+@Data.Random.Prim t@ constructor, currently one of @Word8@, @Word16@,+@Word32@, @Word64@, @Double@ or N-byte integer.+-}+data RandomFu m r where+  SampleRVar ::  R.RVar t -> RandomFu m t+  GetRandomPrim :: R.Prim t -> RandomFu m t++makeSem ''RandomFu++------------------------------------------------------------------------------+-- | use the 'RandomFu` effect to sample from a "random-fu" @Distribution@.+sampleDist+  :: (Member RandomFu r, R.Distribution d t) => d t -> Sem r t+sampleDist = sampleRVar . R.rvar+{-# INLINEABLE sampleDist #-}++------------------------------------------------------------------------------+-- | Run a 'Random' effect using a given 'R.RandomSource'+runRandomSource+  :: forall s r a+   . R.RandomSource (Sem r) s+  => s+  -> Sem (RandomFu ': r) a+  -> Sem r a+runRandomSource source = interpret $ \case+    SampleRVar    rv -> R.runRVar (R.sample rv) source+    GetRandomPrim pt -> R.runRVar (R.getRandomPrim pt) source+{-# INLINEABLE runRandomSource #-}++------------------------------------------------------------------------------+-- | Run a 'Random` effect by using the default "random-fu" 'IO' source+runRandomIO+  :: forall r a+   . MonadIO (Sem r)+  => Sem (RandomFu ': r) a+  -> Sem r a+runRandomIO = interpret $ \case+    SampleRVar    rv -> liftIO $ R.sample rv+    GetRandomPrim pt -> liftIO $ R.getRandomPrim pt+{-# INLINEABLE runRandomIO #-}++------------------------------------------------------------------------------+-- | Run in 'IO', using the given 'R.PureMT' source, stored in an 'IORef'+runRandomIOPureMT+  :: MonadIO (Sem r)+  => R.PureMT+  -> Sem (RandomFu ': r) a+  -> Sem r a+runRandomIOPureMT source re =+  liftIO (newIORef source) >>= flip runRandomSource re+{-# INLINEABLE runRandomIOPureMT #-}++------------------------------------------------------------------------------+-- | "Absorb" an 'R.MonadRandom' constraint.+-- That is, use a @Member RandomFu r@ constraint to satisfy  the @MonadRandom@+-- constraint in a @(forall m. MonadRandom m => m a), returning a @Sem r a@.+-- See 'Polysemy.MTL' for details.+absorbMonadRandom+  :: Member RandomFu r => (R.MonadRandom (Sem r) => Sem r a) -> Sem r a+absorbMonadRandom = absorb @R.MonadRandom+{-# INLINEABLE absorbMonadRandom #-}++instance ReifiableConstraint1 (R.MonadRandom) where+  data Dict1 R.MonadRandom m = MonadRandom+    {+      getRandomPrim_ :: forall t. R.Prim t -> m t+    }+  reifiedInstance = Sub Dict+++$(R.monadRandom [d|+      instance ( Monad m+               , Reifies s' (Dict1 R.MonadRandom m)+               ) => R.MonadRandom (ConstrainedAction R.MonadRandom m s') where+          getRandomPrim t = ConstrainedAction+            $ getRandomPrim_ (reflect $ Proxy @s') t+          {-# INLINEABLE getRandomPrim #-}+  |])++instance Member RandomFu r => IsCanonicalEffect R.MonadRandom r where+  canonicalDictionary = MonadRandom getRandomPrim+  {-# INLINEABLE canonicalDictionary #-}
+ test/MTLSpec.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+module MTLSpec where++import           Polysemy+import           Polysemy.Reader+import           Polysemy.Writer+import           Polysemy.State+import           Polysemy.Error+import           Polysemy.MTL++import qualified Data.Text                     as T+import           Test.Hspec+import           Control.Monad                 as M++import qualified Control.Monad.Reader.Class    as S+import qualified Control.Monad.Writer.Class    as S+import qualified Control.Monad.State.Class     as S+import qualified Control.Monad.Error.Class     as S+++{-+We could re-write these to use polysemy directly.  Imagine, though+that these come from external libraries so you can't so easily+re-write them.+-}+getEnvLength :: S.MonadReader T.Text m => m Int+getEnvLength = S.ask >>= return . T.length++replicateTell :: S.MonadWriter [Int] m => Int -> Int -> m ()+replicateTell n m = M.replicateM_ n $ S.tell [m]++retrieveAndUpdateN :: S.MonadState Int m => Int -> m Int+retrieveAndUpdateN n = do+  m <- S.get+  S.put n+  return m++-- this one is exceptionally boring+throwOnZero :: S.MonadError T.Text m => Int -> m Int+throwOnZero n = do+  M.when (n == 0) $ S.throwError "Zero!"+  return n++someOfAll+  :: (S.MonadReader T.Text m, S.MonadWriter [Int] m, S.MonadState Int m)+  => m T.Text+someOfAll = do+  n <- S.get+  S.tell [n]+  S.ask+------------------------------------------------------------------------------++spec :: Spec+spec = describe "MTL" $ do+  it+      (  "should absorb reader twice, thus returning 9, "+      ++ "the sum of lengths of the strings provided to run (\"Text\")"+      ++ " and then to local (\"Text2\")"+      )+    $ do+        flip shouldBe 9 . run . runReader "Text" $ do+          a <- absorbReader getEnvLength+          b <- local (const "Text2") $ absorbReader getEnvLength+          return (a + b)++  it+      (  "should return the sum, after censoring, of all things told."+      ++ " In this case, 16, the sum of \"init [1,5,5,5,5]\""+      )+    $ do+        flip shouldBe 16 . sum . fst . run . runWriter $ do+          tell [1]+          absorbWriter $ replicateTell 2 5+          censor init $ absorbWriter $ replicateTell 2 5++  it "same as above but with absorbWriter on the outside of the do block" $ do+    flip shouldBe 16 . sum . fst . run . runWriter $ absorbWriter $ do+      S.tell [1]+      replicateTell 2 5+      S.pass $ do+        x <- replicateTell 2 5+        return (x, init)++  it "Should return 0 (since 10 - (20 `div` 2) = 0)" $ do+    flip shouldBe 0 . fst . run . runState 0 $ do+      put 20+      n <- absorbState $ retrieveAndUpdateN 10+      modify (\m -> m - (n `div` 2))+      return ()++  it "should return (Left \"Zero!\")." $ do+    flip shouldBe (Left "Zero!") . run . runError $ absorbError $ throwOnZero 0++  let runRWS+        :: T.Text+        -> Int+        -> Sem '[Reader T.Text, State Int, Writer [Int]] a+        -> ([Int], (Int, a))+      runRWS env0 s0 = run . runWriter . runState s0 . runReader env0++  it "All of them, singly" $ do+    flip shouldBe ([20, 20], (10, 6)) . runRWS "RunAll" 0 $ do+      put 20+      n <- absorbState $ retrieveAndUpdateN 10+      absorbWriter $ replicateTell 2 n+      a <- absorbReader getEnvLength+      return a++  let+    absorbRWS+      :: (Monoid w, Members '[Reader env, Writer w, State s] r)+      => (  ( S.MonadReader env (Sem r)+           , S.MonadWriter w (Sem r)+           , S.MonadState s (Sem r)+           )+         => Sem r a+         )+      -> Sem r a+    absorbRWS x = absorbReader $ absorbWriter $ absorbState x++  it "All of them, one absorber" $ do+    flip shouldBe ([20, 20], (10, 6)) . runRWS "RunAll" 0 $ do+      put 20+      n <- absorbRWS $ retrieveAndUpdateN 10+      absorbRWS $ replicateTell 2 n+      a <- absorbRWS getEnvLength+      return a++  it "All of them, one absorber, absorbRWS outside do block." $ do+    flip shouldBe ([20, 20], (10, 6)) . runRWS "RunAll" 0 $ absorbRWS $ do+      S.put 20+      n <- retrieveAndUpdateN 10+      replicateTell 2 n+      a <- getEnvLength+      return a++  it "absorb a stack" $ do+    flip shouldBe ([10, 20], (20, "RunAll")) . runRWS "RunAll" 0 $ do+      put 20+      tell [10]+      absorbRWS someOfAll++  it "absorb a stack, absorb outside do." $ do+    flip shouldBe ([10, 20], (20, "RunAll"))+      . runRWS "RunAll" 0+      $ absorbRWS+      $ do+          S.put 20+          S.tell [10]+          someOfAll
+ test/RandomFuSpec.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+module RandomFuSpec where++import           Polysemy+import           Polysemy.RandomFu++import           Test.Hspec+import           Control.Monad                 as M+import           Control.Monad.IO.Class         ( liftIO )++import qualified Data.Random                   as R+import qualified Data.Random.Source.PureMT     as R++getRandomInts :: Member RandomFu r => Int -> Sem r [Int]+getRandomInts nDraws =+  sampleRVar $ M.replicateM nDraws (R.uniform 0 (100 :: Int))++getRandomIntsMR :: R.MonadRandom m => Int -> m [Int]+getRandomIntsMR nDraws =+  R.sample $ M.replicateM nDraws (R.uniform 0 (100 :: Int))++randomListsDifferent :: Member RandomFu r => Int -> Sem r Bool+randomListsDifferent nDraws = do+  a <- getRandomInts nDraws+  b <- getRandomInts nDraws+  return (a /= b)++randomListsDifferentMR :: R.MonadRandom m => Int -> m Bool+randomListsDifferentMR nDraws = do+  a <- getRandomIntsMR nDraws+  b <- getRandomIntsMR nDraws+  return (a /= b)++------------------------------------------------------------------------------++spec :: Spec+spec = describe "RandomFu" $ do+  it+      "Should produce [3, 78, 53, 41, 56], 5 psuedo-random Ints seeded from the same seed on each test."+    $ do+        result <- runM . runRandomIOPureMT (R.pureMT 1) $ getRandomInts 5+        result `shouldBe` [3, 78, 53, 41, 56]++  it+      "Should produce [3, 78, 53, 41, 56], 5 psuedo-random Ints seeded from the same seed on each test. Absorbing MonadRandom."+    $ do+        result <-+          runM+          . runRandomIOPureMT (R.pureMT 1)+          $ absorbMonadRandom+          $ getRandomIntsMR 5++        result `shouldBe` [3, 78, 53, 41, 56]+++  it "Should produce two distinct sets of psuedo-random Ints." $ do+    result <- runM . runRandomIO $ randomListsDifferent 5+    result `shouldBe` True++  it+      "Should produce two distinct sets of psuedo-random Ints (absorber version)."+    $ do+        result <-+          runM . runRandomIO $ absorbMonadRandom $ randomListsDifferentMR 5+        result `shouldBe` True+