cleff 0.3.0.1 → 0.3.1.0
raw patch · 23 files changed
+628/−182 lines, 23 filesdep −rec-smallarraydep ~primitivedep ~transformers-basePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies removed: rec-smallarray
Dependency ranges changed: primitive, transformers-base
API changes (from Hackage documentation)
- Cleff: infixr 0 ~>
- Cleff.Internal.Monad: infixr 0 ~>
+ Cleff.Internal.Rec: Rec :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !SmallArray Any -> Rec (f :: k -> Type) (es :: [k])
+ Cleff.Internal.Rec: class Elem (e :: k) (es :: [k])
+ Cleff.Internal.Rec: class KnownList (es :: [k])
+ Cleff.Internal.Rec: class KnownList es => Subset (es :: [k]) (es' :: [k])
+ Cleff.Internal.Rec: concat :: Rec f es -> Rec f es' -> Rec f (es ++ es')
+ Cleff.Internal.Rec: cons :: f e -> Rec f es -> Rec f (e : es)
+ Cleff.Internal.Rec: data Rec (f :: k -> Type) (es :: [k])
+ Cleff.Internal.Rec: drop :: forall es es' f. KnownList es => Rec f (es ++ es') -> Rec f es'
+ Cleff.Internal.Rec: empty :: Rec f '[]
+ Cleff.Internal.Rec: head :: Rec f (e : es) -> f e
+ Cleff.Internal.Rec: index :: forall e es f. Elem e es => Rec f es -> f e
+ Cleff.Internal.Rec: infixr 5 ++
+ Cleff.Internal.Rec: instance Cleff.Internal.Rec.KnownList '[]
+ Cleff.Internal.Rec: instance forall a (e :: a) (es :: [a]) (e' :: a). Cleff.Internal.Rec.Elem e es => Cleff.Internal.Rec.Elem e (e' : es)
+ Cleff.Internal.Rec: instance forall a (e :: a) (es :: [a]). Cleff.Internal.Rec.Elem e (e : es)
+ Cleff.Internal.Rec: instance forall k (e :: k). (TypeError ...) => Cleff.Internal.Rec.Elem e '[]
+ Cleff.Internal.Rec: instance forall k (es :: [k]) (e :: k). Cleff.Internal.Rec.KnownList es => Cleff.Internal.Rec.KnownList (e : es)
+ Cleff.Internal.Rec: instance forall k (es :: [k]) (es' :: [k]) (e :: k). (Cleff.Internal.Rec.Subset es es', Cleff.Internal.Rec.Elem e es') => Cleff.Internal.Rec.Subset (e : es) es'
+ Cleff.Internal.Rec: instance forall k (es :: [k]). Cleff.Internal.Rec.Subset '[] es
+ Cleff.Internal.Rec: newArr :: Int -> ST s (SmallMutableArray s Any)
+ Cleff.Internal.Rec: pick :: forall es es' f. Subset es es' => Rec f es' -> Rec f es
+ Cleff.Internal.Rec: tail :: Rec f (e : es) -> Rec f es
+ Cleff.Internal.Rec: take :: forall es es' f. KnownList es => Rec f (es ++ es') -> Rec f es
+ Cleff.Internal.Rec: type family xs ++ ys
+ Cleff.Internal.Rec: update :: forall e es f. Elem e es => f e -> Rec f es -> Rec f es
- Cleff: type (f :: k -> Type) ~> (g :: k -> Type) = forall (a :: k). () => f a -> g a
+ Cleff: type f ~> g = forall a. f a -> g a
- Cleff: type family (xs :: [a]) ++ (ys :: [a]) :: [a]
+ Cleff: type family xs ++ ys
- Cleff.Internal.Monad: type (f :: k -> Type) ~> (g :: k -> Type) = forall (a :: k). () => f a -> g a
+ Cleff.Internal.Monad: type f ~> g = forall a. f a -> g a
- Cleff.Internal.Monad: type family (xs :: [a]) ++ (ys :: [a]) :: [a]
+ Cleff.Internal.Monad: type family xs ++ ys
Files
- CHANGELOG.md +10/−0
- README.md +2/−2
- cleff.cabal +18/−16
- example/Filesystem.hs +2/−2
- example/Teletype.hs +2/−2
- src/Cleff.hs +44/−29
- src/Cleff/Error.hs +3/−3
- src/Cleff/Fail.hs +2/−2
- src/Cleff/Fresh.hs +4/−4
- src/Cleff/Input.hs +6/−6
- src/Cleff/Internal/Base.hs +41/−6
- src/Cleff/Internal/Interpret.hs +49/−31
- src/Cleff/Internal/Monad.hs +32/−11
- src/Cleff/Internal/Rec.hs +183/−0
- src/Cleff/Internal/TH.hs +68/−41
- src/Cleff/Mask.hs +1/−1
- src/Cleff/Output.hs +6/−6
- src/Cleff/Reader.hs +2/−2
- src/Cleff/State.hs +5/−5
- src/Cleff/Trace.hs +5/−5
- src/Cleff/Writer.hs +4/−4
- test/RecSpec.hs +130/−0
- test/ThSpec.hs +9/−4
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Changelog for `cleff` +## 0.3.0.1 (2022-02-28)++### Added++- `makeEffect` is now capable of generating sending functions for operations using concrete `Eff`s for the monad type++### Removed++- Dependency on `rec-smallarray`+ ## 0.3.0.1 (2022-02-21) Clarify changelog: new features that are listed "Unreleased" in 0.3.0.0 changelog are in fact *released*
README.md view
@@ -49,13 +49,13 @@ makeEffect ''Teletype -- Effect Interpretation via IO-runTeletypeIO :: IOE :> es => Eff (Teletype ': es) a -> Eff es a+runTeletypeIO :: IOE :> es => Eff (Teletype : es) a -> Eff es a runTeletypeIO = interpretIO \case ReadTTY -> getLine WriteTTY s -> putStrLn s -- Effect interpretation via other pure effects-runTeletypePure :: [String] -> Eff (Teletype ': es) w -> Eff es [String]+runTeletypePure :: [String] -> Eff (Teletype : es) w -> Eff es [String] runTeletypePure tty = fmap (reverse . snd) . runState [] . outputToListState . runState tty . inputToListState
cleff.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: cleff-version: 0.3.0.1+version: 0.3.1.0 synopsis: Fast and concise extensible effects description: Please see the README on GitHub at <https://github.com/re-xyr/cleff#readme> category: Control, Effect, Language@@ -48,6 +48,7 @@ Cleff.Internal.Instances Cleff.Internal.Interpret Cleff.Internal.Monad+ Cleff.Internal.Rec Cleff.Internal.TH Cleff.Mask Cleff.Output@@ -76,7 +77,6 @@ KindSignatures LambdaCase NoStarIsType- PatternSynonyms PolyKinds RankNTypes RoleAnnotations@@ -89,7 +89,7 @@ TypeOperators UndecidableInstances UnicodeSyntax- ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -Wmissing-export-lists+ ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wno-unticked-promoted-constructors -Wpartial-fields -Wunused-type-patterns -Wmissing-export-lists build-depends: atomic-primops ==0.8.* , base >=4.12 && <4.17@@ -97,14 +97,15 @@ , exceptions ==0.10.* , microlens >=0.1 && <0.5 , monad-control >=1 && <1.1- , primitive >=0.6 && <0.8- , rec-smallarray ==0.1.*+ , primitive >=0.6.4 && <0.8 , template-haskell >=2.14 && <2.19 , th-abstraction >=0.2 && <0.5- , transformers-base >=0.1 && <0.5+ , transformers-base ==0.4.* , unliftio >=0.2.4 && <0.3 if flag(dynamic-ioe) cpp-options: -DDYNAMIC_IOE+ if impl(ghc >= 8.8)+ ghc-options: -Wmissing-deriving-strategies default-language: Haskell2010 test-suite cleff-example@@ -134,7 +135,6 @@ KindSignatures LambdaCase NoStarIsType- PatternSynonyms PolyKinds RankNTypes RoleAnnotations@@ -147,7 +147,7 @@ TypeOperators UndecidableInstances UnicodeSyntax- ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N+ ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wno-unticked-promoted-constructors -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N build-depends: atomic-primops ==0.8.* , base >=4.12 && <4.17@@ -157,14 +157,15 @@ , extra , microlens >=0.1 && <0.5 , monad-control >=1 && <1.1- , primitive >=0.6 && <0.8- , rec-smallarray ==0.1.*+ , primitive >=0.6.4 && <0.8 , template-haskell >=2.14 && <2.19 , th-abstraction >=0.2 && <0.5- , transformers-base >=0.1 && <0.5+ , transformers-base ==0.4.* , unliftio >=0.2.4 && <0.3 if flag(dynamic-ioe) cpp-options: -DDYNAMIC_IOE+ if impl(ghc >= 8.8)+ ghc-options: -Wmissing-deriving-strategies default-language: Haskell2010 test-suite cleff-test@@ -176,6 +177,7 @@ HigherOrderSpec InterposeSpec MaskSpec+ RecSpec StateSpec ThSpec Paths_cleff@@ -198,7 +200,6 @@ KindSignatures LambdaCase NoStarIsType- PatternSynonyms PolyKinds RankNTypes RoleAnnotations@@ -211,7 +212,7 @@ TypeOperators UndecidableInstances UnicodeSyntax- ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N+ ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wno-unticked-promoted-constructors -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N build-depends: atomic-primops ==0.8.* , base >=4.12 && <4.17@@ -223,12 +224,13 @@ , lifted-base , microlens >=0.1 && <0.5 , monad-control >=1 && <1.1- , primitive >=0.6 && <0.8- , rec-smallarray ==0.1.*+ , primitive >=0.6.4 && <0.8 , template-haskell >=2.14 && <2.19 , th-abstraction >=0.2 && <0.5- , transformers-base >=0.1 && <0.5+ , transformers-base ==0.4.* , unliftio >=0.2.4 && <0.3 if flag(dynamic-ioe) cpp-options: -DDYNAMIC_IOE+ if impl(ghc >= 8.8)+ ghc-options: -Wmissing-deriving-strategies default-language: Haskell2010
example/Filesystem.hs view
@@ -30,7 +30,7 @@ deriving anyclass (Exception) -- | Run the 'Filesystem' effect with actual file IO.-runFilesystemIO :: '[IOE, Error FsError] :>> es => Eff (Filesystem ': es) a -> Eff es a+runFilesystemIO :: '[IOE, Error FsError] :>> es => Eff (Filesystem : es) a -> Eff es a runFilesystemIO = interpret \case ReadFile path -> adapt $ IO.readFile path WriteFile path contents -> adapt $ IO.writeFile path contents@@ -38,7 +38,7 @@ adapt m = liftIO m `catch` \(e :: IOException) -> throwError $ FsError $ show e -- | Run the 'Filesystem' effect with a faked filesystem.-runFilesystemPure :: Error FsError :> es => Map FilePath String -> Eff (Filesystem ': es) a -> Eff es a+runFilesystemPure :: Error FsError :> es => Map FilePath String -> Eff (Filesystem : es) a -> Eff es a runFilesystemPure fs = fmap fst . runState fs . reinterpret \case ReadFile path -> maybeM (throwError $ FsError $ "File not found: " ++ show path) pure $ gets (M.lookup path) WriteFile path contents -> modify $ M.insert path contents
example/Teletype.hs view
@@ -26,13 +26,13 @@ -- * Interpretations -- | Run 'Teletype' via stdio.-runTeletypeIO :: IOE :> es => Eff (Teletype ': es) a -> Eff es a+runTeletypeIO :: IOE :> es => Eff (Teletype : es) a -> Eff es a runTeletypeIO = interpretIO \case ReadTTY -> getLine WriteTTY s -> putStrLn s -- | Run 'Teletype' from a fixed input list.-runTeletypePure :: [String] -> Eff (Teletype ': es) w -> Eff es [String]+runTeletypePure :: [String] -> Eff (Teletype : es) w -> Eff es [String] runTeletypePure tty = fmap (reverse . snd) . runState [] . outputToListState . runState tty . inputToListState
src/Cleff.hs view
@@ -101,7 +101,7 @@ -- To run an effect @T@, we should use an /interpreter/ of @T@, which is a function that has a type like this: -- -- @--- runT :: 'Eff' (T ': es) a -> 'Eff' es a+-- runT :: 'Eff' (T : es) a -> 'Eff' es a -- @ -- -- Such an interpreter provides an implementation of @T@ and eliminates @T@ from the effect stack. All builtin effects@@ -116,7 +116,7 @@ -- $definingEffects -- An effect should be defined as a GADT and have the kind 'Effect'. Each operation in the effect is a constructor of--- the effect type. For example, an effect supporting reading/writing files can be as following:+-- the effect type. For example, an effect supporting reading and writing files can be like this: -- -- @ -- data Filesystem :: 'Effect' where@@ -124,32 +124,22 @@ -- WriteFile :: 'FilePath' -> 'String' -> Filesystem m () -- @ --+-- Here, @ReadFile@ is an operation that takes a 'FilePath' and returns a 'String', presumably the content of the file;+-- @WriteFile@ is an operation that takes a 'FilePath' and a 'String' and returns @()@, meaning it only performs+-- side effects - presumably writing the 'String' to the file specified.+-- -- Operations constructed with these constructors can be performed via the 'send' function. You can also use the -- Template Haskell function 'makeEffect' to automatically generate definitions of functions that perform the effects.--- For example,------ @--- 'makeEffect' ''Filesystem--- @------ generates the following definitions:------ @--- readFile :: Filesystem ':>' es => 'FilePath' -> 'Eff' es 'String'--- readFile x = 'send' (ReadFile x)--- writeFile :: Filesystem ':>' es => 'FilePath' -> 'String' -> 'Eff' es ()--- writeFile x y = 'send' (WriteFile x y)--- @ -- $interpretingEffects--- An effect can be understood as the "grammar" (or /syntax/) of a small language; however we also need to define the--- "meaning" (or /semantics/) of the language. In other words, we need to specify the implementation of effects.+-- An effect can be understood as the /syntax/ of a tiny language; however we also need to define the /meaning/ (or+-- /semantics/) of the language. In other words, we need to specify the implementations of effects. -- -- In an extensible effects system, this is achieved by writing /effect handlers/, which are functions that transforms -- operations of one effect into other "more primitive" effects. These handlers can then be used to make interpreters -- with library functions that we'll now see. ----- This is very easy to do. For example, for the @Filesystem@ effect+-- For example, for the @Filesystem@ effect: -- -- @ -- data Filesystem :: 'Effect' where@@ -161,26 +151,51 @@ -- constructors: -- -- @--- runFilesystemIO :: 'IOE' ':>' es => 'Eff' (Filesystem ': es) a -> 'Eff' es a+-- runFilesystemIO :: 'IOE' ':>' es => 'Eff' (Filesystem : es) a -> 'Eff' es a -- runFilesystemIO = 'interpretIO' \\case -- ReadFile path -> 'readFile' path -- WriteFile path contents -> 'writeFile' path contents -- @ ----- Alternatively, we can also construct an in-memory filesystem in terms of the 'Cleff.State.State' effect via--- the 'reinterpret' function.+-- Specifically, a @ReadFile@ operation is mapped to a real 'readFile' IO computation, and similarly a @WriteFile@+-- operation is mapped to a 'writeFile' computation. --+-- An effect is a set of abstract operations, and naturally, they can have more than one interpretations. Therefore,+-- here we can also construct an in-memory filesystem that reads from and writes into a 'Cleff.State.State' effect, via+-- the 'reinterpret' function that adds another effect to the stack for the effect handler to use:+-- -- @--- runFilesystemPure :: 'Cleff.Fail.Fail' ':>' es => 'Data.Map.Map' 'FilePath' 'String' -> 'Eff' (Filesystem ': es) a -> 'Eff' es a--- runFilesystemPure fs = 'fmap' 'fst' '.' 'Cleff.State.runState' fs '.' 'reinterpret' \\case+-- filesystemToState+-- :: 'Cleff.Fail.Fail' ':>' es+-- => 'Eff' (Filesystem : es) a+-- -> 'Eff' ('Cleff.State.State' ('Data.Map.Map' 'FilePath' 'String') : es) a+-- filesystemToState = 'reinterpret' \\case -- ReadFile path -> 'Cleff.State.gets' ('Data.Map.lookup' path) >>= \\case -- 'Nothing' -> 'fail' ("File not found: " ++ 'show' path) -- 'Just' contents -> 'pure' contents -- WriteFile path contents -> 'Cleff.State.modify' ('Data.Map.insert' path contents) -- @ ----- These interpreters can then be applied to computations with the @Filesystem@ effect to give different implementations--- to the effect.+-- Here, we used the 'reinterpret' function to introduce a @'Cleff.State.State' ('Data.Map.Map' 'FilePath' 'String')@ as+-- the in-memory filesystem, making 'filesystemToState' a /reinterpreter/ that "maps" an effect into another effect.+-- We also added a @'Cleff.Fail.Fail' ':>' es@ constraint to our reinterpreter so that we're able to report errors.+-- To make an /interpreter/ out of this is simple, as we just need to interpret the remaining 'Cleff.State.State'+-- effect:+--+-- @+-- runFilesystemPure+-- :: 'Cleff.Fail.Fail' ':>' es+-- => 'Data.Map.Map' 'FilePath' 'String'+-- -> 'Eff' (Filesystem : es) a+-- -> 'Eff' es a+-- runFilesystemPure fs+-- = 'fmap' 'fst' -- runState returns (Eff es (a, s)), so we need to extract the first component to get (Eff es a)+-- . 'Cleff.State.runState' fs -- (State (Map FilePath String) : es) ==> es+-- . 'filesystemToState' -- (Filesystem : es) ==> (State (Map FilePath String) : es)+-- @+--+-- Both of these interpreters can then be applied to computations with the @Filesystem@ effect to give different+-- implementations to the effect. -- $higherOrderEffects -- /Higher order effects/ are effects whose operations take other effect computations as arguments. For example, the@@ -196,9 +211,9 @@ -- More literally, an high order effect makes use of the monad type paramenter @m@, while a first order effect, like -- 'Cleff.State.State', does not. ----- It is harder to write interpreters for higher order effects, because we need to transform computations from--- arbitrary effect stacks into a specific stack that the effect is currently interpreted into. In other words, they--- need to thread other effects through themselves. This is why Cleff also provides convenient combinators for doing so.+-- It is harder to write interpreters for higher order effects, because the operations of these effects carry+-- computations from arbitrary effect stacks, and we'll need to convert the to the current effect stack that the effect+-- is being interpreted into. Fortunately, Cleff provides convenient combinators for doing so. -- -- In a 'Handler', you can temporarily "unlift" a computation from an arbitrary effect stack into the current stack via -- 'toEff', explicitly change the current effect interpretation in the computation via 'toEffWith', or directly express
src/Cleff/Error.hs view
@@ -150,7 +150,7 @@ newExcUid = incrCounter 1 excUidSource {-# INLINE newExcUid #-} -errorHandler :: ExcUid -> Handler (Error e) (IOE ': es)+errorHandler :: ExcUid -> Handler (Error e) (IOE : es) errorHandler eid = \case ThrowError e -> Exc.throwIO $ ErrorExc eid (toAny e) CatchError m' h' -> withToIO \toIO -> liftIO $ catch' eid (toIO m') (toIO . h')@@ -169,14 +169,14 @@ -- the same 'runError' handler), the error /will/ be caught in the parent thread even if you don't deal with it in the -- forked thread. But if you passed the @Async@ value out of the effect scope and @wait@ed for it elsewhere, the error -- will again not be caught. The best choice is /not to pass @Async@ values around randomly/.-runError :: ∀ e es a. Eff (Error e ': es) a -> Eff es (Either e a)+runError :: ∀ e es a. Eff (Error e : es) a -> Eff es (Either e a) runError m = thisIsPureTrustMe do eid <- liftIO newExcUid try' eid $ reinterpret (errorHandler eid) m {-# INLINE runError #-} -- | Transform an 'Error' into another. This is useful for aggregating multiple errors into one type.-mapError :: ∀ e e' es. Error e' :> es => (e -> e') -> Eff (Error e ': es) ~> Eff es+mapError :: ∀ e e' es. Error e' :> es => (e -> e') -> Eff (Error e : es) ~> Eff es mapError f = thisIsPureTrustMe . reinterpret \case ThrowError e -> throwError $ f e CatchError m h -> do
src/Cleff/Fail.hs view
@@ -30,13 +30,13 @@ -- * Interpretations -- | Run a 'Fail' effect in terms of 'Error'.-runFail :: Eff (Fail ': es) a -> Eff es (Either String a)+runFail :: Eff (Fail : es) a -> Eff es (Either String a) runFail = runError . reinterpret \case Fail msg -> throwError msg {-# INLINE runFail #-} -- | Run a 'Fail' effect in terms of throwing exceptions in 'IO'.-runFailIO :: IOE :> es => Eff (Fail ': es) ~> Eff es+runFailIO :: IOE :> es => Eff (Fail : es) ~> Eff es runFailIO = interpret \case Fail msg -> liftIO $ Fail.fail msg {-# INLINE runFailIO #-}
src/Cleff/Fresh.hs view
@@ -42,14 +42,14 @@ -- value. -- -- @since 0.2.1.0-freshEnumToState :: Enum a => Eff (Fresh a ': es) ~> Eff (State a ': es)+freshEnumToState :: Enum a => Eff (Fresh a : es) ~> Eff (State a : es) freshEnumToState = reinterpret \case Fresh -> state \s -> (s, succ s) {-# INLINE freshEnumToState #-} -- | Interpret a @'Fresh' 'Int'@ effect in terms of @'State' 'Int'@. This is a specialized version of -- 'freshEnumToState'.-freshIntToState :: Eff (Fresh Int ': es) ~> Eff (State Int ': es)+freshIntToState :: Eff (Fresh Int : es) ~> Eff (State Int : es) freshIntToState = freshEnumToState {-# INLINE freshIntToState #-} @@ -57,7 +57,7 @@ -- than 'runFreshUnique'. -- -- @since 0.2.1.0-runFreshAtomicCounter :: Eff (Fresh Int ': es) ~> Eff es+runFreshAtomicCounter :: Eff (Fresh Int : es) ~> Eff es runFreshAtomicCounter m = thisIsPureTrustMe do counter <- liftIO $ newCounter minBound reinterpret (\case@@ -66,7 +66,7 @@ -- | Interpret a @'Fresh' 'Unique'@ effect in terms of IO actions. This is slower than 'runFreshAtomicCounter', but it -- won't overflow on @'maxBound' :: 'Int'@.-runFreshUnique :: IOE :> es => Eff (Fresh Unique ': es) ~> Eff es+runFreshUnique :: IOE :> es => Eff (Fresh Unique : es) ~> Eff es runFreshUnique = interpret \case Fresh -> liftIO newUnique {-# INLINE runFreshUnique #-}
src/Cleff/Input.hs view
@@ -44,13 +44,13 @@ -- * Interpretations -- | Run an 'Input' effect by giving a constant input value.-runInputConst :: i -> Eff (Input i ': es) ~> Eff es+runInputConst :: i -> Eff (Input i : es) ~> Eff es runInputConst x = interpret \case Input -> pure x {-# INLINE runInputConst #-} -- | Run an 'Input' effect by going through a list of values.-inputToListState :: Eff (Input (Maybe i) ': es) ~> Eff (State [i] ': es)+inputToListState :: Eff (Input (Maybe i) : es) ~> Eff (State [i] : es) inputToListState = reinterpret \case Input -> state \case [] -> (Nothing, [])@@ -60,13 +60,13 @@ -- | Run an 'Input' in terms of a 'Reader'. -- -- @since 0.2.1.0-inputToReader :: Eff (Input i ': es) ~> Eff (Reader i ': es)+inputToReader :: Eff (Input i : es) ~> Eff (Reader i : es) inputToReader = reinterpret \case Input -> ask {-# INLINE inputToReader #-} -- | Run an 'Input' effect by performing a computation for each input request.-runInputEff :: Eff es i -> Eff (Input i ': es) ~> Eff es+runInputEff :: Eff es i -> Eff (Input i : es) ~> Eff es runInputEff m = interpret \case Input -> m {-# INLINE runInputEff #-}@@ -74,7 +74,7 @@ -- | Transform an 'Input' effect into another one already in the effect stack, by a pure function. -- -- @since 0.2.1.0-mapInput :: Input i' :> es => (i' -> i) -> Eff (Input i ': es) ~> Eff es+mapInput :: Input i' :> es => (i' -> i) -> Eff (Input i : es) ~> Eff es mapInput f = interpret \case Input -> f <$> input {-# INLINE mapInput #-}@@ -82,7 +82,7 @@ -- | Transform an 'Input' effect into another one already in the effect stack, by an effectful computation. -- -- @since 0.2.1.0-bindInput :: Input i' :> es => (i' -> Eff es i) -> Eff (Input i ': es) ~> Eff es+bindInput :: Input i' :> es => (i' -> Eff es i) -> Eff (Input i : es) ~> Eff es bindInput f = interpret \case Input -> f =<< input {-# INLINE bindInput #-}
src/Cleff/Internal/Base.hs view
@@ -141,7 +141,7 @@ -- | Unsafely eliminate an 'IOE' effect from the top of the effect stack. This is mainly for implementing effects that -- uses 'IO' but does not do anything really /impure/ (i.e. can be safely used 'unsafeDupablePerformIO' on), such as a -- State effect.-thisIsPureTrustMe :: Eff (IOE ': es) ~> Eff es+thisIsPureTrustMe :: Eff (IOE : es) ~> Eff es thisIsPureTrustMe = interpret \case #ifdef DYNAMIC_IOE Lift m -> primLiftIO m@@ -171,18 +171,53 @@ -- @ -- 'interpretIO' f = 'interpret' ('liftIO' '.' f) -- @-interpretIO :: IOE :> es => HandlerIO e es -> Eff (e ': es) ~> Eff es+interpretIO :: IOE :> es => HandlerIO e es -> Eff (e : es) ~> Eff es interpretIO f = interpret (liftIO . f) {-# INLINE interpretIO #-} -- * Combinators for interpreting higher-order effects --- | Temporarily gain the ability to unlift an @'Eff' esSend@ computation into 'IO'. This is useful for dealing with--- higher-order effects that involves 'IO'.+-- | Temporarily gain the ability to unlift an @'Eff' esSend@ computation into 'IO'. This is analogous to+-- 'withRunInIO', and is useful in dealing with higher-order effects that involves 'IO'. For example, the @Resource@+-- effect that supports bracketing:+--+-- @+-- data Resource m a where+-- Bracket :: m a -> (a -> m ()) -> (a -> m b) -> Resource m b+-- @+--+-- can be interpreted into 'Control.Exception.bracket' actions in 'IO', by converting all effect computations into+-- 'IO' computations via 'withToIO':+--+-- @+-- runResource :: 'IOE' ':>' es => 'Eff' (Resource : es) a -> 'Eff' es a+-- runResource = 'interpret' \\case+-- Bracket alloc dealloc use -> 'withToIO' $ \\toIO ->+-- 'Control.Exception.bracket' (toIO alloc) (toIO . dealloc) (toIO . use)+-- @ withToIO :: (Handling esSend e es, IOE :> es) => ((Eff esSend ~> IO) -> IO a) -> Eff es a withToIO f = Eff \es -> f \m -> unEff m (updateEnv es esSend) --- | Lift an 'IO' computation into @'Eff' esSend@. This is useful for dealing with effect operations with the monad type in--- the negative position within 'Cleff.IOE', like 'UnliftIO.mask'ing.+-- | Lift an 'IO' computation into @'Eff' esSend@. This is analogous to 'liftIO', and is only useful in dealing with+-- effect operations with the monad type in the negative position, for example 'Control.Exception.mask'ing:+--+-- @+-- data Mask :: 'Effect' where+-- Mask :: ((m '~>' m) -> m a) -> Mask m a+-- ^ this "m" is in negative position+-- @+--+-- See how the @restore :: IO a -> IO a@ from 'Control.Exception.mask' is "wrapped" into+-- @'Eff' esSend a -> 'Eff' esSend a@:+--+-- @+-- runMask :: 'IOE' ':>' es => 'Eff' (Mask : es) a -> 'Eff' es a+-- runMask = 'interpret' \\case+-- Mask f -> 'withToIO' $ \\toIO -> 'Control.Exception.mask' $+-- \\restore -> f ('fromIO' . restore . toIO)+-- @+--+-- Here, @toIO@ from 'withToIO' takes an @'Eff' esSend@ to 'IO', where it can be passed into the @restore@ function,+-- and the returned 'IO' computation is recovered into 'Eff' with 'fromIO'. fromIO :: (Handling esSend e es, IOE :> es) => IO ~> Eff esSend fromIO = Eff . const
src/Cleff/Internal/Interpret.hs view
@@ -49,8 +49,8 @@ ) where import Cleff.Internal.Monad-import Data.Rec.SmallArray (Rec, pattern (:++:))-import qualified Data.Rec.SmallArray as Rec+import Cleff.Internal.Rec (Rec)+import qualified Cleff.Internal.Rec as Rec import Unsafe.Coerce (unsafeCoerce) -- * Trivial handling@@ -62,7 +62,7 @@ adjust f m = Eff (unEff m . adjustEnv f) -- | Lift a computation into a bigger effect stack with one more effect. For a more general version see 'raiseN'.-raise :: ∀ e es. Eff es ~> Eff (e ': es)+raise :: ∀ e es. Eff es ~> Eff (e : es) raise = raiseN @'[e] -- | Lift a computation into a bigger effect stack with arbitrarily more effects. This function requires@@ -71,13 +71,13 @@ raiseN = adjust (Rec.drop @es') -- | Like 'raise', but adds the new effect under the top effect. This is useful for transforming an interpreter--- @e' ':>' es => 'Eff' (e ': es) '~>` 'Eff' es@ into a reinterpreter @'Eff' (e ': es) '~>' 'Eff' (e' ': es)@:+-- @e' ':>' es => 'Eff' (e : es) '~>` 'Eff' es@ into a reinterpreter @'Eff' (e : es) '~>' 'Eff' (e' : es)@: -- -- @--- myInterpreter :: Bar ':>' es => 'Eff' (Foo ': es) '~>' 'Eff' es+-- myInterpreter :: Bar ':>' es => 'Eff' (Foo : es) '~>' 'Eff' es -- myInterpreter = ... ----- myReinterpreter :: 'Eff' (Foo ': es) '~>' 'Eff' (Bar ': es)+-- myReinterpreter :: 'Eff' (Foo : es) '~>' 'Eff' (Bar : es) -- myReinterpreter = myInterpreter '.' 'raiseUnder' -- @ --@@ -92,20 +92,20 @@ -- more efficient. -- -- @since 0.2.0.0-raiseUnder :: ∀ e' e es. Eff (e ': es) ~> Eff (e ': e' ': es)+raiseUnder :: ∀ e' e es. Eff (e : es) ~> Eff (e : e' : es) raiseUnder = raiseNUnder @'[e'] -- | Like 'raiseUnder', but allows introducing multiple effects. This function requires @TypeApplications@. -- -- @since 0.2.0.0-raiseNUnder :: ∀ es' e es. KnownList es' => Eff (e ': es) ~> Eff (e ': es' ++ es)+raiseNUnder :: ∀ es' e es. KnownList es' => Eff (e : es) ~> Eff (e : es' ++ es) raiseNUnder = raiseNUnderN @es' @'[e] -- | Like 'raiseUnder', but allows introducing the effect under multiple effects. This function requires -- @TypeApplications@. -- -- @since 0.2.0.0-raiseUnderN :: ∀ e es' es. KnownList es' => Eff (es' ++ es) ~> Eff (es' ++ e ': es)+raiseUnderN :: ∀ e es' es. KnownList es' => Eff (es' ++ es) ~> Eff (es' ++ e : es) raiseUnderN = raiseNUnderN @'[e] @es' @es -- | A generalization of both 'raiseUnderN' and 'raiseNUnder', allowing introducing multiple effects under multiple@@ -122,12 +122,12 @@ inject = adjust (Rec.pick @es') -- | Eliminate a duplicate effect from the top of the effect stack. For a more general version see 'subsumeN'.-subsume :: ∀ e es. e :> es => Eff (e ': es) ~> Eff es+subsume :: ∀ e es. e :> es => Eff (e : es) ~> Eff es subsume = subsumeN @'[e] -- | Eliminate several duplicate effects from the top of the effect stack. This function requires @TypeApplications@. subsumeN :: ∀ es' es. Subset es' es => Eff (es' ++ es) ~> Eff es-subsumeN = adjust \re -> Rec.pick @es' re :++: re+subsumeN = adjust \re -> Rec.concat (Rec.pick @es' re) re -- * Handler types @@ -187,34 +187,36 @@ unEff (instHandling handle (SendSite ess ptr) e) (updateEnv ess es) -- | Interpret an effect @e@ in terms of effects in the effect stack @es@ with an effect handler.-interpret :: ∀ e es. Handler e es -> Eff (e ': es) ~> Eff es+interpret :: ∀ e es. Handler e es -> Eff (e : es) ~> Eff es interpret = reinterpretN @'[] --- | Like 'interpret', but adds a new effect @e'@ that can be used in the handler.-reinterpret :: ∀ e' e es. Handler e (e' ': es) -> Eff (e ': es) ~> Eff (e' ': es)+-- | Like 'interpret', but adds a new effect @e'@ to the stack that can be used in the handler.+reinterpret :: ∀ e' e es. Handler e (e' : es) -> Eff (e : es) ~> Eff (e' : es) reinterpret = reinterpretN @'[e'] -- | Like 'reinterpret', but adds two new effects.-reinterpret2 :: ∀ e' e'' e es. Handler e (e' ': e'' ': es) -> Eff (e ': es) ~> Eff (e' ': e'' ': es)+reinterpret2 :: ∀ e' e'' e es. Handler e (e' : e'' : es) -> Eff (e : es) ~> Eff (e' : e'' : es) reinterpret2 = reinterpretN @'[e', e''] -- | Like 'reinterpret', but adds three new effects.-reinterpret3 :: ∀ e' e'' e''' e es. Handler e (e' ': e'' ': e''' ': es) -> Eff (e ': es) ~> Eff (e' ': e'' ': e''' ': es)+reinterpret3 :: ∀ e' e'' e''' e es. Handler e (e' : e'' : e''' : es) -> Eff (e : es) ~> Eff (e' : e'' : e''' : es) reinterpret3 = reinterpretN @'[e', e'', e'''] -- | Like 'reinterpret', but adds arbitrarily many new effects. This function requires @TypeApplications@.-reinterpretN :: ∀ es' e es. KnownList es' => Handler e (es' ++ es) -> Eff (e ': es) ~> Eff (es' ++ es)+reinterpretN :: ∀ es' e es. KnownList es' => Handler e (es' ++ es) -> Eff (e : es) ~> Eff (es' ++ es) reinterpretN handle m = Eff \es -> let (# ptr, es' #) = allocaEnv es in unEff m $ appendEnv ptr (mkInternalHandler ptr es' handle) $ adjustEnv (Rec.drop @es') es' {-# INLINE reinterpretN #-} --- | Respond to an effect while being able to leave it unhandled (i.e. you can resend the effects in the handler).+-- | Respond to an effect, but does not eliminate it from the stack. This means you can re-send the operations in the+-- effect handler; it is often useful when you need to "intercept" operations so you can add extra behaviors like+-- logging. interpose :: ∀ e es. e :> es => Handler e es -> Eff es ~> Eff es interpose = imposeN @'[] -- | Like 'interpose', but allows to introduce one new effect to use in the handler.-impose :: ∀ e' e es. e :> es => Handler e (e' ': es) -> Eff es ~> Eff (e' ': es)+impose :: ∀ e' e es. e :> es => Handler e (e' : es) -> Eff es ~> Eff (e' : es) impose = imposeN @'[e'] -- | Like 'impose', but allows introducing arbitrarily many effects. This requires @TypeApplications@.@@ -231,7 +233,7 @@ -- @ -- 'transform' trans = 'interpret' ('sendVia' 'toEff' '.' trans) -- @-transform :: ∀ e e' es. e' :> es => Translator e e' -> Eff (e ': es) ~> Eff es+transform :: ∀ e e' es. e' :> es => Translator e e' -> Eff (e : es) ~> Eff es transform trans = interpret (sendVia toEff . trans) -- | Like 'transform', but instead of using an effect in stack, add a new one to the top of it.@@ -239,22 +241,37 @@ -- @ -- 'translate' trans = 'reinterpret' ('sendVia' 'toEff' '.' trans) -- @-translate :: ∀ e e' es. Translator e e' -> Eff (e ': es) ~> Eff (e' ': es)+translate :: ∀ e e' es. Translator e e' -> Eff (e : es) ~> Eff (e' : es) translate trans = reinterpret (sendVia toEff . trans) -- * Combinators for interpreting higher effects --- | Run a computation in the current effect stack. This is useful for interpreting higher-order effects, like a--- bracketing effect:+-- | Run a computation in the current effect stack; this is useful for interpreting higher-order effects. For example,+-- if you want to interpret a bracketing effects in terms of 'IO': -- -- @ -- data Resource m a where -- Bracket :: m a -> (a -> m ()) -> (a -> m b) -> Resource m b -- @ --+-- You will not be able to simply write this for the effect:+-- -- @--- Bracket alloc dealloc use ->--- 'UnliftIO.bracket'+-- runBracket :: IOE ':>' es => 'Eff' (Resource : es) a -> 'Eff' es a+-- runBracket = 'interpret' \\case+-- Bracket alloc dealloc use -> UnliftIO.'UnliftIO.bracket' alloc dealloc use+-- @+--+-- This is because effects are sended from all kinds of stacks that has @Resource@ in it, so effect handlers received+-- the effect as @Resource esSend a@, where @esSend@ is an arbitrary stack with @Resource@, instead of+-- @Resource es a@. This means @alloc@, @dealloc@ and @use@ are of type @'Eff' esSend a@, while 'UnliftIO.bracket' can+-- only take and return @'Eff' es a@. So we need to use 'toEff', which converts an @'Eff' esSend a@ into+-- an @'Eff' es a@:+--+-- @+-- runBracket :: IOE ':>' es => 'Eff' (Resource : es) a -> 'Eff' es a+-- runBracket = 'interpret' \\case+-- Bracket alloc dealloc use -> UnliftIO.'UnliftIO.bracket' -- ('toEff' alloc) -- ('toEff' . dealloc) -- ('toEff' . use)@@ -262,11 +279,12 @@ toEff :: Handling esSend e es => Eff esSend ~> Eff es toEff m = Eff \es -> unEff m (updateEnv es esSend) --- | Run a computation in the current effect stack, but handles the current effect inside the computation differently--- by providing a new 'Handler'. This is useful for interpreting effects with local contexts, like 'Cleff.Reader.Local':+-- | Run a computation in the current effect stack, just like 'toEff', but takes a 'Handler' of the current effect+-- being interpreted, so that inside the computation being ran, the effect is interpreted differently. This is useful+-- for interpreting effects with local contexts, like 'Cleff.Reader.Local': -- -- @--- runReader :: r -> 'Eff' ('Cleff.Reader.Reader' r ': es) '~>' 'Eff' es+-- runReader :: r -> 'Eff' ('Cleff.Reader.Reader' r : es) '~>' 'Eff' es -- runReader x = 'interpret' (handle x) -- where -- handle :: r -> 'Handler' ('Cleff.Reader.Reader' r) es@@ -280,8 +298,8 @@ -- monomorphic. Therefore this usage is safe. writeEnv (hdlPtr @esSend) (mkInternalHandler (hdlPtr @esSend) es handle) $ updateEnv es esSend --- | Temporarily gain the ability to lift some @'Eff' es@ actions into @'Eff' esSend@. This is useful for dealing with--- effect operations with the monad type in the negative position, which means it's unlikely that you need to use this--- function in implementing your effects.+-- | Temporarily gain the ability to lift some @'Eff' es@ actions into @'Eff' esSend@. This is only useful for dealing+-- with effect operations with the monad type in the negative position, which means it's unlikely that you need to use+-- this function in implementing your effects. withFromEff :: Handling esSend e es => ((Eff es ~> Eff esSend) -> Eff esSend a) -> Eff es a withFromEff f = Eff \es -> unEff (f \m -> Eff \ess -> unEff m (updateEnv ess es)) (updateEnv es esSend)
src/Cleff/Internal/Monad.hs view
@@ -40,14 +40,14 @@ , sendVia ) where -import Cleff.Internal.Any+import Cleff.Internal.Any (Any, fromAny, toAny)+import Cleff.Internal.Rec (Elem, KnownList, Rec, Subset, type (++))+import qualified Cleff.Internal.Rec as Rec import Control.Applicative (Applicative (liftA2)) import Control.Monad.Fix (MonadFix (mfix)) import Data.IntMap.Strict (IntMap) import qualified Data.IntMap.Strict as Map import Data.Kind (Constraint, Type)-import Data.Rec.SmallArray (Elem, KnownList, Rec, Subset, pattern (:~:), type (++), type (~>))-import qualified Data.Rec.SmallArray as Rec -- * Basic types @@ -64,9 +64,22 @@ -- alias for @(e1 ':>' es, ..., en ':>' es)@. type family xs :>> es :: Constraint where '[] :>> _ = ()- (x ': xs) :>> es = (x :> es, xs :>> es)+ (x : xs) :>> es = (x :> es, xs :>> es) infix 0 :>> +-- | A natural transformation from @f@ to @g@. With this, instead of writing+--+-- @+-- runSomeEffect :: 'Eff' (SomeEffect : es) a -> 'Eff' es a+-- @+--+-- you can write:+--+-- @+-- runSomeEffect :: 'Eff' (SomeEffect : es) ~> 'Eff' es+-- @+type f ~> g = ∀ a. f a -> g a+ -- * The 'Eff' monad -- | The internal representation of effect handlers. This is just a natural transformation from the effect type@@ -77,16 +90,21 @@ newtype InternalHandler e = InternalHandler { runHandler :: ∀ es. e (Eff es) ~> Eff es } -- | The extensible effects monad. The monad @'Eff' es@ is capable of performing any effect in the /effect stack/ @es@,--- which is a type-level list that holds all effects available. However, most of the times, for flexibility, @es@--- should be a polymorphic type variable, and you should use the '(:>)' and '(:>>)' operators in constraints to--- indicate what effects are in the stack. For example,+-- which is a type-level list that holds all effects available. --+-- The best practice is to always use a polymorphic type variable for the effect stack @es@, and then use the type+-- operators '(:>)' and '(:>>)' in constraints to indicate what effects are available in the stack. For example,+-- -- @ -- ('Cleff.Reader.Reader' 'String' ':>' es, 'Cleff.State.State' 'Bool' ':>' es) => 'Eff' es 'Integer' -- @ -- -- means you can perform operations of the @'Cleff.Reader.Reader' 'String'@ effect and the @'Cleff.State.State' 'Bool'@--- effect in a computation returning an 'Integer'.+-- effect in a computation returning an 'Integer'. The reason why you should always use a polymorphic effect stack as+-- opposed to a concrete list of effects are that+--+-- * it can contain other effects that are used by computations other than the current one, and+-- * it does not require you to run the effects in any particular order. type role Eff nominal representational newtype Eff es a = Eff { unEff :: Env es -> IO a } -- ^ The effect monad receives an effect environment 'Env' that contains all effect handlers and produces an 'IO'@@ -129,7 +147,10 @@ -- effect interpretation ('Cleff.reinterpretN') and the latter for local interpretation ('Cleff.toEffWith') in order to -- retain correct HO semantics. For more details on this see https://github.com/re-xyr/cleff/issues/5. type role Env nominal-data Env (es :: [Effect]) = Env {-# UNPACK #-} !Int {-# UNPACK #-} !(Rec HandlerPtr es) !(IntMap Any)+data Env (es :: [Effect]) = Env+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !(Rec HandlerPtr es)+ !(IntMap Any) -- | A pointer to 'InternalHandler' in an 'Env'. type role HandlerPtr nominal@@ -166,8 +187,8 @@ {-# INLINE replaceEnv #-} -- | Add a new effect to the stack with its corresponding handler pointer. \( O(n) \).-appendEnv :: ∀ e es. HandlerPtr e -> InternalHandler e -> Env es -> Env (e ': es)-appendEnv (HandlerPtr m) x (Env n re mem) = Env n (HandlerPtr m :~: re) (Map.insert m (toAny x) mem)+appendEnv :: ∀ e es. HandlerPtr e -> InternalHandler e -> Env es -> Env (e : es)+appendEnv (HandlerPtr m) x (Env n re mem) = Env n (Rec.cons (HandlerPtr m) re) (Map.insert m (toAny x) mem) {-# INLINE appendEnv #-} -- | Use the state of LHS as a newer version for RHS. \( O(1) \).
+ src/Cleff/Internal/Rec.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# OPTIONS_HADDOCK not-home #-}+-- |+-- Copyright: (c) 2021 Xy Ren+-- License: BSD3+-- Maintainer: xy.r@outlook.com+-- Stability: unstable+-- Portability: non-portable (GHC only)+--+-- This module defines an immutable extensible record type, similar to @vinyl@ and @data-diverse@. However this+-- implementation focuses on fast reads, hence has very different performance characteristics from other libraries:+--+-- * Lookup: Amortized \( O(1) \).+-- * Update: \( O(n) \).+-- * Shrink: \( O(1) \).+-- * Append: \( O(n) \).+--+-- __This is an /internal/ module and its API may change even between minor versions.__ Therefore you should be+-- extra careful if you're to depend on this module.+module Cleff.Internal.Rec+ ( Rec (Rec)+ , type (++)+ -- * Construction+ , empty+ , cons+ , concat+ -- * Deconstruction+ , KnownList+ , head+ , take+ , tail+ , drop+ -- * Retrieval and updating+ , Elem+ , Subset+ , index+ , pick+ , update+ -- * Helpers+ , newArr+ ) where++import Cleff.Internal.Any (Any, fromAny, toAny)+import Control.Monad.ST (ST)+import Data.Kind (Type)+import Data.Primitive.SmallArray (SmallArray, SmallMutableArray, cloneSmallArray, copySmallArray,+ indexSmallArray, newSmallArray, runSmallArray, thawSmallArray,+ writeSmallArray)+import GHC.TypeLits (ErrorMessage (ShowType, Text, (:<>:)), TypeError)+import Prelude hiding (all, any, concat, drop, head, length, tail, take, zipWith)++-- | Extensible record type supporting efficient \( O(1) \) reads. The underlying implementation is 'SmallArray'+-- slices, therefore suits small numbers of entries (/i.e./ less than 128).+type role Rec representational nominal+data Rec (f :: k -> Type) (es :: [k]) = Rec+ {-# UNPACK #-} !Int -- ^ The offset.+ {-# UNPACK #-} !Int -- ^ The length.+ {-# UNPACK #-} !(SmallArray Any) -- ^ The array content.++-- | Create a new 'SmallMutableArray' with no contents.+newArr :: Int -> ST s (SmallMutableArray s Any)+newArr len = newSmallArray len $ error+ "Cleff.Internal.Rec.newArr: Attempting to read an element of the underlying array of a 'Rec'. Please report this \+ \as a bug."++unreifiable :: String -> String -> String -> a+unreifiable clsName funName comp = error $+ funName <> ": Attempting to access " <> comp <> " without a reflected value. This is perhaps because you are trying \+ \to define an instance for the '" <> clsName <> "' typeclass, which you should not be doing whatsoever. If that or \+ \other shenanigans seem unlikely, please report this as a bug."++-- | Create an empty record. \( O(1) \).+empty :: Rec f '[]+empty = Rec 0 0 $ runSmallArray $ newArr 0++-- | Prepend one entry to the record. \( O(n) \).+cons :: f e -> Rec f es -> Rec f (e : es)+cons x (Rec off len arr) = Rec 0 (len + 1) $ runSmallArray do+ marr <- newArr (len + 1)+ writeSmallArray marr 0 (toAny x)+ copySmallArray marr 1 arr off len+ pure marr++-- | Type level list concatenation.+type family xs ++ ys where+ '[] ++ ys = ys+ (x : xs) ++ ys = x : (xs ++ ys)+infixr 5 ++++-- | Concatenate two records. \( O(m+n) \).+concat :: Rec f es -> Rec f es' -> Rec f (es ++ es')+concat (Rec off len arr) (Rec off' len' arr') = Rec 0 (len + len') $ runSmallArray do+ marr <- newArr (len + len')+ copySmallArray marr 0 arr off len+ copySmallArray marr len arr' off' len'+ pure marr++-- | Slice off one entry from the top of the record. \( O(1) \).+tail :: Rec f (e : es) -> Rec f es+tail (Rec off len arr) = Rec (off + 1) (len - 1) arr++-- | @'KnownList' es@ means the list @es@ is concrete, i.e. is of the form @'[a1, a2, ..., an]@ instead of a type+-- variable.+class KnownList (es :: [k]) where+ -- | Get the length of the list.+ reifyLen :: Int+ reifyLen = unreifiable "KnownList" "Cleff.Internal.Rec.reifyLen" "the length of a type-level list"++instance KnownList '[] where+ reifyLen = 0++instance KnownList es => KnownList (e : es) where+ reifyLen = 1 + reifyLen @_ @es++-- | Slice off several entries from the top of the record. \( O(1) \).+drop :: ∀ es es' f. KnownList es => Rec f (es ++ es') -> Rec f es'+drop (Rec off len arr) = Rec (off + len') (len - len') arr+ where len' = reifyLen @_ @es++-- | Get the head of the record. \( O(1) \).+head :: Rec f (e : es) -> f e+head (Rec off _ arr) = fromAny $ indexSmallArray arr off++-- | Take elements from the top of the record. \( O(m) \).+take :: ∀ es es' f. KnownList es => Rec f (es ++ es') -> Rec f es+take (Rec off _ arr) = Rec 0 len $ cloneSmallArray arr off len+ where len = reifyLen @_ @es++-- | The element @e@ is present in the list @es@.+class Elem (e :: k) (es :: [k]) where+ -- | Get the index of the element.+ reifyIndex :: Int+ reifyIndex = unreifiable "Elem" "Cleff.Internal.Rec.reifyIndex" "the index of an element of a type-level list"++-- | The element closer to the head takes priority.+instance {-# OVERLAPPING #-} Elem e (e : es) where+ reifyIndex = 0++instance Elem e es => Elem e (e' : es) where+ reifyIndex = 1 + reifyIndex @_ @e @es++type ElemNotFound e = Text "The element '" :<>: ShowType e :<>: Text "' is not present in the constraint"++instance TypeError (ElemNotFound e) => Elem e '[] where+ reifyIndex = error+ "Cleff.Internal.Rec.reifyIndex: Attempting to refer to a nonexistent member. Please report this as a bug."++-- | Get an element in the record. Amortized \( O(1) \).+index :: ∀ e es f. Elem e es => Rec f es -> f e+index (Rec off _ arr) = fromAny $ indexSmallArray arr (off + reifyIndex @_ @e @es)++-- | @es@ is a subset of @es'@, i.e. all elements of @es@ are in @es'@.+class KnownList es => Subset (es :: [k]) (es' :: [k]) where+ -- | Get a list of indices of the elements.+ reifyIndices :: [Int]+ reifyIndices = unreifiable+ "Subset" "Cleff.Internal.Rec.reifyIndices" "the index of multiple elements of a type-level list"++instance Subset '[] es where+ reifyIndices = []++instance (Subset es es', Elem e es') => Subset (e : es) es' where+ reifyIndices = reifyIndex @_ @e @es' : reifyIndices @_ @es @es'++-- | Get a subset of the record. Amortized \( O(m) \).+pick :: ∀ es es' f. Subset es es' => Rec f es' -> Rec f es+pick (Rec off _ arr) = Rec 0 (reifyLen @_ @es) $ runSmallArray do+ marr <- newArr (reifyLen @_ @es)+ go marr 0 (reifyIndices @_ @es @es')+ pure marr+ where+ go :: SmallMutableArray s Any -> Int -> [Int] -> ST s ()+ go _ _ [] = pure ()+ go marr newIx (ix : ixs) = do+ writeSmallArray marr newIx $ indexSmallArray arr (off + ix)+ go marr (newIx + 1) ixs++-- | Update an entry in the record. \( O(n) \).+update :: ∀ e es f. Elem e es => f e -> Rec f es -> Rec f es+update x (Rec off len arr) = Rec 0 len $ runSmallArray do+ marr <- thawSmallArray arr off len+ writeSmallArray marr (reifyIndex @_ @e @es) (toAny x)+ pure marr
src/Cleff/Internal/TH.hs view
@@ -28,22 +28,29 @@ import Prelude hiding ((<>)) -- | For a datatype @T@ representing an effect, @'makeEffect' T@ generates functions defintions for performing the--- operations of @T@ via 'send'. The naming rule is changing the first uppercase letter in the constructor name to--- lowercase or removing the @:@ symbol in the case of operator constructors. Also, this function will preserve any--- fixity declarations defined on the constructors.+-- operations of @T@ via 'send'. For example, ----- Because of the limitations of Template Haskell, all constructors of @T@ should be /polymorphic in the monad type/,--- if they are to be used by 'makeEffect'. For example, this is not OK:+-- @+-- 'makeEffect' ''Filesystem+-- @ --+-- generates the following definitions:+-- -- @--- data Limited :: 'Effect' where--- Noop :: Limited ('Eff' es) ()+-- readFile :: Filesystem ':>' es => 'FilePath' -> 'Eff' es 'String'+-- readFile x = 'send' (ReadFile x)+-- writeFile :: Filesystem ':>' es => 'FilePath' -> 'String' -> 'Eff' es ()+-- writeFile x y = 'send' (WriteFile x y) -- @ ----- because the monad type @'Eff' es@ is not a fully polymorphic type variable.+-- The naming rule is changing the first uppercase letter in the constructor name to lowercase or removing the @:@+-- symbol in the case of operator constructors. Also, this function will preserve any fixity declarations defined on+-- the constructors. --+-- === Technical details+-- -- This function is also "weaker" than @polysemy@'s @makeSem@, because this function cannot properly handle some--- cases involving complex higher order effects. Those cases are rare, though. See the tests for more details.+-- cases involving ambiguous types. Those cases are rare, though. See the @ThSpec@ test spec for more details. makeEffect :: Name -> Q [Dec] makeEffect = makeSmartCons True @@ -66,67 +73,87 @@ -- | This is the function underlying 'makeEffect' and 'makeEffect_'. You can switch between the behavior of two by -- changing the 'Bool' parameter to 'True' (generating signatures) or 'False' (not generating signatures). makeSmartCons :: Bool -> Name -> Q [Dec]-makeSmartCons makeSig effName = do+makeSmartCons shouldMakeSig effName = do info <- reifyDatatype effName- join <$> traverse (makeCon makeSig) (constructorName <$> reverse (datatypeCons info))+ join <$> traverse (makeCon shouldMakeSig) (constructorName <$> reverse (datatypeCons info)) -- | Make a single function definition of a certain effect operation. makeCon :: Bool -> Name -> Q [Dec]-makeCon makeSig name = do+makeCon shouldMakeSig name = do fixity <- reifyFixity name- typ <- reify name >>= \case- DataConI _ typ _ -> pure typ- _ -> fail $ show- $ text "'" <> ppr name <> text "' is not a constructor"+ ctorTy <- reify name >>= \case+ DataConI _ ty _ -> pure ty+ _ -> fail $ show $ text "'" <> ppr name <> text "' is not a constructor" - effVar <- VarT <$> newName "es"+ operationCtx' <- extractCtx ctorTy+ (operationParams', (effTy, effMonad, resTy')) <- extractParams ctorTy - let actionCtx = extractCtx typ- (actionPar, (effTy, monadVar, resTy)) <- extractPar typ+ (esVar, maybeMndVar) <- case effMonad of+ Right m -> do+ fresh <- VarT <$> newName "es"+ pure (fresh, Just m)+ Left v -> pure (VarT v, Nothing) + let operationCtx = substMnd maybeMndVar esVar <$> operationCtx'+ let operationParams = substMnd maybeMndVar esVar <$> operationParams'+ let resTy = substMnd maybeMndVar esVar resTy'+ let fnName = mkName $ toSmartConName $ nameBase name- fnArgs <- traverse (const $ newName "x") actionPar+ fnArgs <- traverse (const $ newName "x") operationParams let fnBody = VarE 'send `AppE` foldl' (\f -> AppE f . VarE) (ConE name) fnArgs- fnSig = ForallT [] (UInfixT effTy ''(:>) effVar : actionCtx)- (makeTyp actionPar effVar effTy monadVar resTy)+ fnSig = ForallT [] (UInfixT effTy ''(:>) esVar : operationCtx)+ (makeTyp operationParams esVar effTy resTy) pure $ maybeToList ((`InfixD` name) <$> fixity) ++- [ SigD fnName fnSig | makeSig ] +++ [ SigD fnName fnSig | shouldMakeSig ] ++ [ FunD fnName [Clause (VarP <$> fnArgs) (NormalB fnBody) []] ] where+ -- Uncapitalize the first letter / remove the ':' in operator constructors+ toSmartConName :: String -> String toSmartConName (':' : xs) = xs toSmartConName (x : xs) = toLower x : xs toSmartConName _ = error "Cleff.makeEffect: Empty constructor name. Please report this as a bug." - extractCtx (ForallT _ ctx t) = ctx ++ extractCtx t- extractCtx _ = []+ -- Extract constraints for the constructor (the type is normalized so we don't need to extract recursively)+ extractCtx :: Type -> Q Cxt+ extractCtx (ForallT _ ctx _) = pure ctx+ extractCtx ty = fail $ show $ text "The constructor with type'" <> ppr ty <> text "' does not construct an effect" - extractPar (ForallT _ _ t) = extractPar t- extractPar (SigT t _) = extractPar t- extractPar (ParensT t) = extractPar t- extractPar (ArrowT `AppT` a `AppT` t) = do- (args, ret) <- extractPar t+ -- Extract (parameter types, (effect type, Eff es / m variable, return type))+ extractParams :: Type -> Q ([Type], (Type, Either Name Name, Type))+ extractParams (ForallT _ _ t) = extractParams t+ extractParams (SigT t _) = extractParams t+ extractParams (ParensT t) = extractParams t+ extractParams (ArrowT `AppT` a `AppT` t) = do+ (args, ret) <- extractParams t pure (a : args, ret) #if MIN_VERSION_template_haskell(2,17,0)- extractPar (MulArrowT `AppT` _ `AppT` a `AppT` t) = do- (args, ret) <- extractPar t+ extractParams (MulArrowT `AppT` _ `AppT` a `AppT` t) = do+ (args, ret) <- extractParams t pure (a : args, ret) #endif-- extractPar (effTy `AppT` VarT monadVar `AppT` resTy) = pure ([], (effTy, monadVar, resTy))- extractPar ty@(_ `AppT` m `AppT` _) = fail $ show+ extractParams (effTy `AppT` VarT mndVar `AppT` resTy) = pure ([], (effTy, Right mndVar, resTy))+ extractParams (effTy `AppT` (ConT eff `AppT` VarT esVar) `AppT` resTy)+ | eff == ''Eff = pure ([], (effTy, Left esVar, resTy))+ extractParams ty@(_ `AppT` m `AppT` _) = fail $ show $ text "The effect monad argument '" <> ppr m- <> text "' in the effect '" <> ppr ty <> text "' is not a type variable"- extractPar t = fail $ show+ <> text "' in the effect '" <> ppr ty <> text "' is not a type variable nor in shape 'Eff es'"+ extractParams t = fail $ show $ text "The type '" <> ppr t <> text "' does not have the shape of an effect (i.e. has a polymorphic monad type and a result type)" - makeTyp [] effVar _ _ resTy = ConT ''Eff `AppT` effVar `AppT` resTy- makeTyp (parTy : pars) effVar effTy monadVar resTy =- ArrowT `AppT` substMnd monadVar effVar parTy `AppT` makeTyp pars effVar effTy monadVar resTy+ -- Make the type of the smart constructor from params, effect row variable, effect type and result type+ -- Example: a -> m b -> c -> MyEffect m d ==> a -> Eff es b -> c -> Eff es d+ makeTyp :: [Type] -> Type -> Type -> Type -> Type+ makeTyp [] esVar _ resTy = ConT ''Eff `AppT` esVar `AppT` resTy+ makeTyp (parTy : pars) esVar effTy resTy =+ ArrowT `AppT` parTy `AppT` makeTyp pars esVar effTy resTy - substMnd monadVar effVar = applySubstitution (Map.singleton monadVar $ ConT ''Eff `AppT` effVar)+ -- Substitute in 'Eff es' for the 'm' variable+ substMnd :: Maybe Name -> Type -> Type -> Type+ substMnd Nothing _ = id+ substMnd (Just mndVar) esVar = applySubstitution (Map.singleton mndVar $ ConT ''Eff `AppT` esVar)
src/Cleff/Mask.hs view
@@ -135,7 +135,7 @@ -- * Interpretations -- | Interpret the 'Mask' effect in terms of primitive 'IO' actions.-runMask :: Eff (Mask ': es) ~> Eff es+runMask :: Eff (Mask : es) ~> Eff es runMask = thisIsPureTrustMe . reinterpret \case Mask f -> withToIO \toIO -> Exc.mask \restore -> toIO $ f (fromIO . restore . toIO) UninterruptibleMask f -> withToIO \toIO -> Exc.uninterruptibleMask \restore -> toIO $ f (fromIO . restore . toIO)
src/Cleff/Output.hs view
@@ -40,25 +40,25 @@ -- | Run an 'Output' effect by accumulating a list. Note that outputs are being prepended to the head of the list, so -- in many cases you would want to 'reverse' the result.-outputToListState :: Eff (Output o ': es) ~> Eff (State [o] ': es)+outputToListState :: Eff (Output o : es) ~> Eff (State [o] : es) outputToListState = reinterpret \case Output x -> modify (x :) {-# INLINE outputToListState #-} -- | Run an 'Output' effect by translating it into a 'Writer'.-outputToWriter :: (o -> o') -> Eff (Output o ': es) ~> Eff (Writer o' ': es)+outputToWriter :: (o -> o') -> Eff (Output o : es) ~> Eff (Writer o' : es) outputToWriter f = reinterpret \case Output x -> tell $ f x {-# INLINE outputToWriter #-} -- | Ignore outputs of an 'Output' effect altogether.-ignoreOutput :: Eff (Output o ': es) ~> Eff es+ignoreOutput :: Eff (Output o : es) ~> Eff es ignoreOutput = interpret \case Output _ -> pure () {-# INLINE ignoreOutput #-} -- | Run an 'Output' effect by performing a computation for each output.-runOutputEff :: (o -> Eff es ()) -> Eff (Output o ': es) ~> Eff es+runOutputEff :: (o -> Eff es ()) -> Eff (Output o : es) ~> Eff es runOutputEff m = interpret \case Output x -> m x {-# INLINE runOutputEff #-}@@ -66,7 +66,7 @@ -- | Transform an 'Output' effect into another one already in the effect stack, by a pure function. -- -- @since 0.2.1.0-mapOutput :: Output o' :> es => (o -> o') -> Eff (Output o ': es) ~> Eff es+mapOutput :: Output o' :> es => (o -> o') -> Eff (Output o : es) ~> Eff es mapOutput f = interpret \case Output x -> output $ f x {-# INLINE mapOutput #-}@@ -74,7 +74,7 @@ -- | Transform an 'Output' effect into another one already in the effect stack, by an effectful computation. -- -- @since 0.2.1.0-bindOutput :: Output o' :> es => (o -> Eff es o') -> Eff (Output o ': es) ~> Eff es+bindOutput :: Output o' :> es => (o -> Eff es o') -> Eff (Output o : es) ~> Eff es bindOutput f = interpret \case Output x -> output =<< f x {-# INLINE bindOutput #-}
src/Cleff/Reader.hs view
@@ -48,7 +48,7 @@ -- * Interpretations -- | Run a 'Reader' effect with a given environment value.-runReader :: r -> Eff (Reader r ': es) ~> Eff es+runReader :: r -> Eff (Reader r : es) ~> Eff es runReader x = interpret (h x) where h :: r -> Handler (Reader r) es@@ -58,7 +58,7 @@ {-# INLINE runReader #-} -- | Run a 'Reader' effect in terms of a larger 'Reader' via a 'Lens''.-magnify :: Reader t :> es => Lens' t r -> Eff (Reader r ': es) ~> Eff es+magnify :: Reader t :> es => Lens' t r -> Eff (Reader r : es) ~> Eff es magnify field = interpret \case Ask -> asks (^. field) Local f m -> local (& field %~ f) $ toEff m
src/Cleff/State.hs view
@@ -86,7 +86,7 @@ -- -- 'runState' will stop taking care of state operations done on forked threads as soon as the main thread finishes its -- computation. Any state operation done /before main thread finishes/ is still taken into account.-runState :: s -> Eff (State s ': es) a -> Eff es (a, s)+runState :: s -> Eff (State s : es) a -> Eff es (a, s) runState s m = thisIsPureTrustMe do rs <- newIORef s x <- reinterpret (handleIORef rs) m@@ -97,14 +97,14 @@ -- | Run the 'State' effect in terms of operations on a supplied 'IORef'. The 'state' operation is atomic. -- -- @since 0.2.1.0-runStateIORef :: IOE :> es => IORef s -> Eff (State s ': es) a -> Eff es a+runStateIORef :: IOE :> es => IORef s -> Eff (State s : es) a -> Eff es a runStateIORef rs = interpret $ handleIORef rs {-# INLINE runStateIORef #-} -- | Run the 'State' effect in terms of operations on a supplied 'MVar'. -- -- @since 0.2.1.0-runStateMVar :: IOE :> es => MVar s -> Eff (State s ': es) a -> Eff es a+runStateMVar :: IOE :> es => MVar s -> Eff (State s : es) a -> Eff es a runStateMVar rs = interpret \case Get -> readMVar rs Put s' -> void $ swapMVar rs s'@@ -114,7 +114,7 @@ -- | Run the 'State' effect in terms of operations on a supplied 'TVar'. -- -- @since 0.2.1.0-runStateTVar :: IOE :> es => TVar s -> Eff (State s ': es) a -> Eff es a+runStateTVar :: IOE :> es => TVar s -> Eff (State s : es) a -> Eff es a runStateTVar rs = interpret \case Get -> readTVarIO rs Put s' -> atomically $ writeTVar rs s'@@ -126,7 +126,7 @@ {-# INLINE runStateTVar #-} -- | Run a 'State' effect in terms of a larger 'State' via a 'Lens''.-zoom :: State t :> es => Lens' t s -> Eff (State s ': es) ~> Eff es+zoom :: State t :> es => Lens' t s -> Eff (State s : es) ~> Eff es zoom field = interpret \case Get -> gets (^. field) Put s -> modify (& field .~ s)
src/Cleff/Trace.hs view
@@ -38,29 +38,29 @@ -- * Interpretations -- | Run the 'Trace' effect by writing to a 'Handle'.-runTraceHandle :: IOE :> es => Handle -> Eff (Trace ': es) a -> Eff es a+runTraceHandle :: IOE :> es => Handle -> Eff (Trace : es) a -> Eff es a runTraceHandle h = interpretIO \case Trace s -> hPutStrLn h s {-# INLINE runTraceHandle #-} -- | Run the 'Trace' effect by writing to 'stdout'.-runTraceStdout :: IOE :> es => Eff (Trace ': es) ~> Eff es+runTraceStdout :: IOE :> es => Eff (Trace : es) ~> Eff es runTraceStdout = runTraceHandle stdout {-# INLINE runTraceStdout #-} -- | Run the 'Trace' effect by writing to 'stderr'.-runTraceStderr :: IOE :> es => Eff (Trace ': es) ~> Eff es+runTraceStderr :: IOE :> es => Eff (Trace : es) ~> Eff es runTraceStderr = runTraceHandle stderr {-# INLINE runTraceStderr #-} -- | Run the 'Trace' effect by ignoring all outputs altogether.-ignoreTrace :: Eff (Trace ': es) ~> Eff es+ignoreTrace :: Eff (Trace : es) ~> Eff es ignoreTrace = interpret \case Trace _ -> pure () {-# INLINE ignoreTrace #-} -- | Transform the 'Trace' effect into an @'Output' 'String'@ effect.-traceToOutput :: Eff (Trace ': es) ~> Eff (Output String ': es)+traceToOutput :: Eff (Trace : es) ~> Eff (Output String : es) traceToOutput = reinterpret \case Trace s -> output s {-# INLINE traceToOutput #-}
src/Cleff/Writer.hs view
@@ -60,14 +60,14 @@ -- Both 'runWriter' and 'listen's under 'runWriter' will stop taking care of writer operations done on -- forked threads as soon as the main thread finishes its computation. Any writer operation done -- /before main thread finishes/ is still taken into account.-runWriter :: ∀ w es a. Monoid w => Eff (Writer w ': es) a -> Eff es (a, w)+runWriter :: ∀ w es a. Monoid w => Eff (Writer w : es) a -> Eff es (a, w) runWriter m = thisIsPureTrustMe do rw <- newIORef mempty x <- reinterpret (h [rw]) m w' <- readIORef rw pure (x, w') where- h :: [IORef w] -> Handler (Writer w) (IOE ': es)+ h :: [IORef w] -> Handler (Writer w) (IOE : es) h rws = \case Tell w' -> traverse_ (\rw -> liftIO $ atomicModifyIORefCAS_ rw (<> w')) rws Listen m' -> do@@ -94,14 +94,14 @@ -- of 'runWriter'. -- -- @since 0.2.0.0-runWriterBatch :: ∀ w es a. Monoid w => Eff (Writer w ': es) a -> Eff es (a, w)+runWriterBatch :: ∀ w es a. Monoid w => Eff (Writer w : es) a -> Eff es (a, w) runWriterBatch m = thisIsPureTrustMe do rw <- newIORef mempty x <- reinterpret (h rw) m w' <- readIORef rw pure (x, w') where- h :: IORef w -> Handler (Writer w) (IOE ': es)+ h :: IORef w -> Handler (Writer w) (IOE : es) h rw = \case Tell w' -> liftIO $ atomicModifyIORefCAS_ rw (<> w') Listen m' -> do
+ test/RecSpec.hs view
@@ -0,0 +1,130 @@+{-# OPTIONS_GHC -Wno-orphans #-}+module RecSpec where++import Cleff.Internal.Rec (Elem, Rec (Rec), type (++))+import qualified Cleff.Internal.Rec as Rec+import Data.Functor.Identity (Identity (Identity))+import Data.Primitive.SmallArray (indexSmallArray, sizeofSmallArray)+import Data.Typeable (cast)+import Test.Hspec++type I = Identity+i :: a -> Identity a+i = Identity++-- | Test the size invariant of 'Rec'.+sizeInvariant :: Rec f es -> Rec f es+sizeInvariant xs@(Rec off len arr)+ | tracked == actual = xs+ | otherwise = error $+ "Cleff.Internal.Rec.sizeInvariant: tracked size " <> show tracked <> ", actual size " <> show actual+ where+ tracked = len + off+ actual = sizeofSmallArray arr++-- | Test whether all fields of 'Rec' are really set.+allAccessible :: Rec f es -> Rec f es+allAccessible xs@(Rec off len arr) = go 0+ where+ go n+ | n == len = xs+ | otherwise = indexSmallArray arr (off + n) `seq` go (n + 1)++-- | Test all invariants.+invariant :: Rec f es -> Rec f es+invariant = allAccessible . sizeInvariant++singleton :: f e -> Rec f '[e]+singleton x = x <:> Rec.empty++(<:>) :: f e -> Rec f es -> Rec f (e : es)+(<:>) = Rec.cons+infixr 5 <:>++(<++>) :: Rec f es -> Rec f es' -> Rec f (es ++ es')+(<++>) = Rec.concat+infixr 5 <++>++(<//>) :: Elem e es => f e -> Rec f es -> Rec f es+(<//>) = Rec.update+infixr 9 <//>++instance Eq (Rec f '[]) where+ _ == _ = True++instance (Eq (Rec f xs), Eq (f x)) => Eq (Rec f (x : xs)) where+ xs == ys = Rec.head xs == Rec.head ys && Rec.tail xs == Rec.tail ys++instance Show (Rec f '[]) where+ show _ = "empty"++instance (Show (f x), Show (Rec f xs)) => Show (Rec f (x : xs)) where+ showsPrec p xs = showParen (p > consPrec) $+ showsPrec (consPrec + 1) (Rec.head xs) . showString " <:> " . showsPrec consPrec (Rec.tail xs)+ where consPrec = 5++spec :: Spec+spec = describe "Rec (SmallArray)" $ parallel do+ it "is Typeable" do+ let+ x = i (5 :: Int) <:> i False <:> Rec.empty+ y = cast x :: Maybe (Rec I '[Int, String])+ z = cast x :: Maybe (Rec I '[Int, Bool])+ y `shouldBe` Nothing+ z `shouldBe` Just x++ it "can be constructed with 'empty', 'cons', 'concat'" do+ let+ x = invariant $ i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ y = invariant $ singleton (i (5 :: Int)) <++> singleton (i False)+ <++> singleton (i 'X') <++> singleton (i (Just 'O'))+ a = invariant $ i (5 :: Int) <:> singleton (i False)+ b = invariant $ singleton (i 'X') <++> singleton (i (Just 'O'))+ x `shouldBe` y+ invariant (a <++> b) `shouldBe` x++ it "can contain multiple fields of the same type" do+ let+ x = i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ y = i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> i (6 :: Int) <:> i (Just 'A') <:> Rec.empty+ invariant (x <++> 6 <:> i (Just 'A') <:> Rec.empty) `shouldBe` y++ it "can be destructed via 'head', 'tail', 'take', 'drop'" do+ let+ a = (x <:> y) <++> singleton z+ x = i (5 :: Int)+ y = i (singleton $ i False) <:> i 'X' <:> Rec.empty+ z = i (Just 'O')+ Rec.head a `shouldBe` x+ invariant (Rec.drop @'[Int, Rec I '[Bool], Char] a) `shouldBe` singleton z+ invariant (Rec.tail a) `shouldBe` invariant (y <++> singleton z)+ invariant (Rec.take @'[Int, Rec I '[Bool], Char] a) `shouldBe` (x <:> y)++ it "can get elements via 'index'" do+ let x = i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ Rec.index @Int x `shouldBe` 5+ Rec.index @Bool x `shouldBe` i False+ Rec.index @Char x `shouldBe` i 'X'+ Rec.index @(Maybe Char) x `shouldBe` i (Just 'O')++ it "can get the topmost element among the duplicate ones" do+ let y = i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> i (6 :: Int) <:> i (Just 'A') <:> Rec.empty+ Rec.index @Int y `shouldBe` 5+ Rec.index @Bool y `shouldBe` i False+ Rec.index @Char y `shouldBe` i 'X'+ Rec.index @(Maybe Char) y `shouldBe` i (Just 'O')++ it "can set elements via 'update'" do+ let x = i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ invariant (Rec.update @Int 6 x) `shouldBe` 6 <:> i False <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ invariant (i True <//> x) `shouldBe` 5 <:> i True <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ invariant (i 'O' <//> x) `shouldBe` 5 <:> i False <:> i 'O' <:> i (Just 'O') <:> Rec.empty+ invariant (i (Just 'P') <//> x) `shouldBe` 5 <:> i False <:> i 'X' <:> i (Just 'P') <:> Rec.empty++ it "can get multiple elements via 'pick'" do+ let x = i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ invariant (Rec.pick @'[Int, Maybe Char] x) `shouldBe` 5 <:> i (Just 'O') <:> Rec.empty++ it "can reorder elements via 'pick'" do+ let x = i (5 :: Int) <:> i False <:> i 'X' <:> i (Just 'O') <:> Rec.empty+ invariant (Rec.pick @'[Bool, Int, Maybe Char] x) `shouldBe` i False <:> 5 <:> i (Just 'O') <:> Rec.empty
test/ThSpec.hs view
@@ -1,11 +1,10 @@- -- | This module is adapted from https://github.com/arybczak/effectful/blob/master/effectful/tests/ThEffectSpec.hs, -- originally BSD3 license, authors Andrzej Rybczak et al. module ThSpec where import Cleff import Data.Kind (Type)-import GHC.TypeLits+import GHC.TypeLits (KnownNat, Nat, type (+)) import Test.Hspec spec :: Spec@@ -70,8 +69,8 @@ MMIC1 :: f m -> Family 'MMI m (f m) MMIC2 :: (∀ x. m x -> m (f m)) -> Family 'MMI m (f m) --- TODO(daylily): This cannot produce desired result.--- makeEffect 'MMIC1+-- Generates correctly since 0.3.1.0+makeEffect 'MMIC1 data Complex m a where Mono :: Int -> Complex m Bool@@ -127,3 +126,9 @@ ByFieldC :: { byFieldCF :: Int } -> ByField m Int makeEffect 'byFieldCF++data PartialMonad m a where+ ImpartialMonad :: Int -> PartialMonad m ()+ PartialMonad :: IOE :> es => Int -> Eff es () -> PartialMonad (Eff es) ()++makeEffect ''PartialMonad