`zoom`, `magnify`, and `wrapError` forget other capabilities
aherrmann opened this issue · 0 comments
aherrmann commented
The functions zoom
, magnify
, and wrapError
as introduced by #31 allow to modify a HasState
, HasReader
, or HasCatch/Throw
capability according to a deriving strategy. However, they forget any other capabilities that were in scope before. E.g. in the following code the writer capability is forgotten:
verboseStates
:: (HasState "both" (Int, Int) m, HasWriter "log" (Sum Int) m)
=> m ()
verboseStates = do
let incAndTell
:: (HasState "count" Int m, HasWriter "log" (Sum Int) m)
=> m ()
incAndTell = do
msg <- state @"count" (\n -> (n, succ n))
tell @"log" (Sum msg)
zoom @"both" @"count" @(Rename 1 :.: Pos 1 "both") $
-- XXX: Cannot use incAndTell here, because the HasWriter has been forgotten.
-- incAndTell
pure ()
A more general combinator, call it using
, would allow the above. E.g.
using
@'[ HasState "count" Int `Via` Rename 1 :.: Pos 1 "both"
, HasWriter "log" (Sum Int) `Via` Self ] $
-- Can use incAndTell here
incAndTell
It could be implemented as follows:
using :: forall vias m a. AllCapabilities vias (Combine vias m)
=> (forall m'. AllCapabilities vias m' => m' a)
-> m a
using m = coerce @(Combine vias m a) m
newtype Combine (vias :: [*]) m (a :: *) = Combine (m a)
deriving (Functor, Applicative, Monad, MonadIO, PrimMonad)
-- Add appropriate capability instances for `Combine`. E.g.
deriving via ((t :: (* -> *) -> * -> *) m)
instance
( NoDuplicateStrategy (HasState tag s) vias
, forall x. Coercible (m x) (t m x)
, HasState tag s (t m)
, Monad m )
=> HasState tag s (Combine (HasState tag s `Via` t ': vias) m)
deriving via (Combine vias m)
instance {-# OVERLAPPABLE #-}
( HasState tag s (Combine vias m), Monad m )
=> HasState tag s (Combine (via ': vias) m)
data Via
(capability :: (* -> *) -> Constraint)
(strategy :: (* -> *) -> * -> *)
infix 8 `Via`
newtype Self m (a :: *) = Self (m a)
deriving (Functor, Applicative, Monad, MonadIO, PrimMonad)