diff --git a/disco.cabal b/disco.cabal index 0cca2154..d8741473 100644 --- a/disco.cabal +++ b/disco.cabal @@ -460,11 +460,14 @@ library Disco.Interactive.CmdLine Disco.Interactive.Commands Disco.Doc + Polysemy.ConstraintAbsorber + Polysemy.ConstraintAbsorber.MonadCatch + Polysemy.Random other-modules: Paths_disco autogen-modules: Paths_disco - build-depends: base >=4.8 && <4.18, + build-depends: base >=4.8 && <4.20, filepath, directory, mtl >=2.2 && <2.4, @@ -480,7 +483,9 @@ library -- constraint, hence a breaking change polysemy >= 1.6.0.0 && < 1.10, polysemy-plugin >= 0.4 && < 0.5, - polysemy-zoo >= 0.7 && < 0.9, + constraints >= 0.13 && < 0.15, + reflection >= 2.1 && < 2.2, + random >= 1.2 && < 1.3, lens >= 4.14 && < 5.3, exact-combinatorics >= 0.2 && < 0.3, arithmoi >= 0.10 && < 0.14, diff --git a/src/Disco/Effects/ConstraintAbsorber.hs b/src/Disco/Effects/ConstraintAbsorber.hs new file mode 100644 index 00000000..afe9b646 --- /dev/null +++ b/src/Disco/Effects/ConstraintAbsorber.hs @@ -0,0 +1,85 @@ +-- Copied from the polysemy-zoo package. +{- +Copyright Sandy Maguire (c) 2019 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Sandy Maguire nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} + +module Disco.Effects.ConstraintAbsorber ( + -- * Absorb builder + absorbWithSem, + + -- * Re-exports + Reifies, + (:-) (Sub), + Dict (Dict), + reflect, + Proxy (Proxy), +) where + +import Data.Constraint (Dict (Dict), (:-) (Sub), (\\)) +import qualified Data.Constraint as C +import qualified Data.Constraint.Unsafe as C +import Data.Kind (Constraint, Type) +import Data.Proxy (Proxy (..)) +import Data.Reflection (Reifies, reflect) +import qualified Data.Reflection as R +import Polysemy (Sem) + +------------------------------------------------------------------------------ + +-- | This function can be used to locally introduce typeclass instances for +-- 'Sem'. See 'Polysemy.ConstraintAbsorber.MonadState' for an example of how to +-- use it. +-- +-- @since 0.3.0.0 +absorbWithSem :: + forall + -- Constraint to be absorbed + (p :: (Type -> Type) -> Constraint) + -- Wrapper to avoid orphan instances + (x :: (Type -> Type) -> Type -> Type -> Type) + d + r + a. + -- | Reified dictionary + d -> + -- | This parameter should always be @'Sub' 'Dict'@ + (forall s. R.Reifies s d :- p (x (Sem r) s)) -> + (p (Sem r) => Sem r a) -> + Sem r a +absorbWithSem d i m = R.reify d $ \(_ :: Proxy (s :: Type)) -> + m + \\ C.trans + (C.unsafeCoerceConstraint :: (p (x m s) :- p m)) + i +{-# INLINEABLE absorbWithSem #-} diff --git a/src/Disco/Effects/Fresh.hs b/src/Disco/Effects/Fresh.hs index f33b58ef..94ad03ee 100644 --- a/src/Disco/Effects/Fresh.hs +++ b/src/Disco/Effects/Fresh.hs @@ -2,10 +2,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ - -- | -- Module : Disco.Effects.Fresh -- Copyright : disco team and contributors diff --git a/src/Disco/Interactive/CmdLine.hs b/src/Disco/Interactive/CmdLine.hs index 18842482..7074b222 100644 --- a/src/Disco/Interactive/CmdLine.hs +++ b/src/Disco/Interactive/CmdLine.hs @@ -1,7 +1,3 @@ ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ - -- | -- Module : Disco.Interactive.CmdLine -- Copyright : disco team and contributors @@ -27,20 +23,14 @@ import Paths_disco (version) import Control.Lens hiding (use) import Control.Monad (unless, when) +import Control.Monad.Catch (SomeException) import qualified Control.Monad.Catch as CMC import Control.Monad.IO.Class (MonadIO (..)) import Data.Foldable (forM_) import Data.List (isPrefixOf) import Data.Maybe (isJust) -import System.Exit ( - exitFailure, - exitSuccess, - ) - -import qualified Options.Applicative as O -import System.Console.Haskeline as H - import Disco.AST.Surface (emptyModule) +import Disco.Effects.State import Disco.Error import Disco.Eval import Disco.Interactive.Commands @@ -51,11 +41,15 @@ import Disco.Module ( ) import Disco.Names (ModuleName (REPLModule)) import Disco.Pretty - -import Disco.Effects.State +import qualified Options.Applicative as O import Polysemy import Polysemy.ConstraintAbsorber.MonadCatch import Polysemy.Error +import System.Console.Haskeline as H +import System.Exit ( + exitFailure, + exitSuccess, + ) ------------------------------------------------------------ -- Command-line options parser diff --git a/src/Polysemy/ConstraintAbsorber.hs b/src/Polysemy/ConstraintAbsorber.hs new file mode 100644 index 00000000..8d6d4e75 --- /dev/null +++ b/src/Polysemy/ConstraintAbsorber.hs @@ -0,0 +1,85 @@ +-- Copied from the polysemy-zoo package. +{- +Copyright Sandy Maguire (c) 2019 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Sandy Maguire nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} + +module Polysemy.ConstraintAbsorber ( + -- * Absorb builder + absorbWithSem, + + -- * Re-exports + Reifies, + (:-) (Sub), + Dict (Dict), + reflect, + Proxy (Proxy), +) where + +import Data.Constraint (Dict (Dict), (:-) (Sub), (\\)) +import qualified Data.Constraint as C +import qualified Data.Constraint.Unsafe as C +import Data.Kind (Constraint, Type) +import Data.Proxy (Proxy (..)) +import Data.Reflection (Reifies, reflect) +import qualified Data.Reflection as R +import Polysemy (Sem) + +------------------------------------------------------------------------------ + +-- | This function can be used to locally introduce typeclass instances for +-- 'Sem'. See 'Polysemy.ConstraintAbsorber.MonadState' for an example of how to +-- use it. +-- +-- @since 0.3.0.0 +absorbWithSem :: + forall + -- Constraint to be absorbed + (p :: (Type -> Type) -> Constraint) + -- Wrapper to avoid orphan instances + (x :: (Type -> Type) -> Type -> Type -> Type) + d + r + a. + -- | Reified dictionary + d -> + -- | This parameter should always be @'Sub' 'Dict'@ + (forall s. R.Reifies s d :- p (x (Sem r) s)) -> + (p (Sem r) => Sem r a) -> + Sem r a +absorbWithSem d i m = R.reify d $ \(_ :: Proxy (s :: Type)) -> + m + \\ C.trans + (C.unsafeCoerceConstraint :: (p (x m s) :- p m)) + i +{-# INLINEABLE absorbWithSem #-} diff --git a/src/Polysemy/ConstraintAbsorber/MonadCatch.hs b/src/Polysemy/ConstraintAbsorber/MonadCatch.hs new file mode 100644 index 00000000..7785f9e3 --- /dev/null +++ b/src/Polysemy/ConstraintAbsorber/MonadCatch.hs @@ -0,0 +1,144 @@ +-- Copied from polysemy-zoo. +{- +Copyright Sandy Maguire (c) 2019 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Sandy Maguire nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} + +module Polysemy.ConstraintAbsorber.MonadCatch (absorbMonadCatch) where + +import qualified Control.Monad.Catch as C +import Polysemy +import Polysemy.ConstraintAbsorber +import qualified Polysemy.Error as E + +------------------------------------------------------------------------------ + +-- -- | Like 'E.runError' but applies a given function from 'SomeException' +-- -- to some other type, typically something less opaque. +-- -- e.g.: +-- -- @runMonadCatch C.displayException@ +-- -- +-- -- @since 0.7.0.0 +-- runMonadCatch :: +-- Exception e => +-- (Maybe e -> e') -> +-- Sem (E.Error C.SomeException : E.Error e' : r) a -> +-- Sem r (Either e' a) +-- runMonadCatch f = E.runError . E.mapError (f . C.fromException) + +-- runMonadCatchAsText :: +-- Sem (E.Error C.SomeException : E.Error T.Text : r) a -> +-- Sem r (Either T.Text a) +-- runMonadCatchAsText = E.runError . E.mapError (T.pack . C.displayException) + +-- | Introduce a local 'S.MonadCatch' constraint on 'Sem' --- allowing it to +-- interop nicely with exceptions +-- +-- @since 0.7.0.0 +absorbMonadCatch :: + Member (E.Error C.SomeException) r => + -- | A computation that requires an instance of 'C.MonadCatch' + -- or 'C.MonadThrow' for + -- 'Sem'. This might be something with type @'C.MonadCatch' e m => m a@. + (C.MonadCatch (Sem r) => Sem r a) -> + Sem r a +absorbMonadCatch = + absorbWithSem @C.MonadCatch @Action (CatchDict E.throw E.catch) (Sub Dict) +{-# INLINEABLE absorbMonadCatch #-} + +-- -- | Introduce a local 'S.MonadThrow' constraint on 'Sem' --- allowing it to +-- -- interop nicely with exceptions +-- -- +-- -- @since 0.7.0.0 +-- absorbMonadThrow :: +-- Member (E.Error C.SomeException) r => +-- -- | A computation that requires an instance of 'C.MonadCatch' +-- -- or 'C.MonadThrow' for +-- -- 'Sem'. This might be something with type @'C.MonadCatch' e m => m a@. +-- (C.MonadThrow (Sem r) => Sem r a) -> +-- Sem r a +-- absorbMonadThrow main = absorbMonadCatch main +-- {-# INLINEABLE absorbMonadThrow #-} + +------------------------------------------------------------------------------ + +-- | A dictionary of the functions we need to supply +-- to make an instance of Error +data CatchDict m = CatchDict + { throwM_ :: forall a. C.SomeException -> m a + , catch_ :: forall a. m a -> (C.SomeException -> m a) -> m a + } + +------------------------------------------------------------------------------ + +-- | Wrapper for a monadic action with phantom +-- type parameter for reflection. +-- Locally defined so that the instance we are going +-- to build with reflection must be coherent, that is +-- there cannot be orphans. +newtype Action m s' a = Action {action :: m a} + deriving (Functor, Applicative, Monad) + +------------------------------------------------------------------------------ + +-- | Given a reifiable mtl Error dictionary, +-- we can make an instance of @MonadError@ for the action +-- wrapped in @Action@. +instance + ( Monad m + , Reifies s' (CatchDict m) + ) => + C.MonadThrow (Action m s') + where + throwM e = Action $ throwM_ (reflect $ Proxy @s') (C.toException e) + {-# INLINEABLE throwM #-} + +instance + ( Monad m + , Reifies s' (CatchDict m) + ) => + C.MonadCatch (Action m s') + where + catch x f = + let catchF = catch_ (reflect $ Proxy @s') + in Action $ + (action x) `catchF` \e -> case C.fromException e of + Just e' -> action $ f e' + _ -> throwM_ (reflect $ Proxy @s') (C.toException e) + {-# INLINEABLE catch #-} diff --git a/src/Polysemy/Random.hs b/src/Polysemy/Random.hs new file mode 100644 index 00000000..8d529180 --- /dev/null +++ b/src/Polysemy/Random.hs @@ -0,0 +1,93 @@ +-- Copied from polysemy-zoo. +{- +Copyright Sandy Maguire (c) 2019 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Sandy Maguire nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-} +{-# 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 (Embed IO) r => Sem (Random ': r) a -> Sem r a +runRandomIO m = do + q <- embed @IO R.newStdGen + snd <$> runRandom q m +{-# INLINE runRandomIO #-}