{-# LANGUAGE ConstraintKinds, KindSignatures, DataKinds, RankNTypes #-} {-| Making interpreters: * 'singletonInterpreter', from a handler Growing interpreters: * 'appendInterpreters' Shrinking an interpreter: * 'downcastInterpreter', when it handles more than you need If you want to do more with them, just unwrap the @newtype@ and use "Data.Vinyl". -} module Vinyl.Effects.Interpreter.Simple where import Vinyl.Effects.Types import Vinyl.Effects.Language -- import Vinyl.Effects.Extra import Vinyl.CoRec import Data.Vinyl import Data.Vinyl.TypeLevel -- import Data.Coerce -------------------------------------------------------------------------------- {-| a product of handlers. each field holds a co-algebra: @ f (m a) -> m a @ e.g. @ runFG :: Interpreter IO '[f,g] runFG = Interpreter $ 'HandlerM' runF :& 'HandlerM' runG :& RNil runF :: f (m a) -> m a runG :: g (m a) -> m a @ specialization: @ Interpreter IO [f,g] ~ Rec ('HandlerM' IO) [f,g] ~ (HandlerM IO f, HandlerM IO g) ~ ( (forall x. f (IO x) -> IO x) , (forall y. g (IO y) -> IO y) ) @ -} newtype Interpreter (m :: * -> *) (effects :: [* -> *]) = Interpreter { getInterpreter :: Rec (HandlerM m) effects } -------------------------------------------------------------------------------- {-| monadically handle a functor. -} newtype HandlerM m f = HandlerM { getHandlerM :: forall x. f (m x) -> m x } -- {-| -- -- -} -- newtype Handler f = Handler { getHandler :: -- forall x. f x -> x -- } -- TODO Conflicts with co-records --old type HandlerM m a f = Handler f (m a) (m a) {-old each field holds a natural transformation: f (m a) -> -} -------------------------------------------------------------------------------- {-| A (1) lifted, (2) n-ary, (3) associative product. In particular, a @product@ of "handlers". @fs@ must all be @Functor@s. e.g. @ ProductF [f,g] a @ generalizes '(,)': @ ProductF '[f,g] a ~ (f a, g a) @ -} data ProductF fs a = ProductF { getProductF :: Rec (Apply a) fs } -------------------------------------------------------------------------------- {-| interpret a language into some monad. e.g. @ interpretLanguage anInterpreter aLanguage :: m () @ calls 'iterLM'. -} interpretLanguage :: (Monad m) => Interpreter m effects -> (Language effects :~> m) interpretLanguage interpreter = iterLM (interpretLanguageF interpreter) -- {-| -- -- -} -- interpretLanguage -- :: () -- => Interpreter m effects -- -> (Language effects a -> m a) -- interpretLanguage (Interpreter handlers) (Language features) = m -- where -- m = undefined "TODO" {-| you consume a coproduct with a product of consumers i.e. you must handle every case. like 'match'. -} interpretLanguageF :: Interpreter m effects -> AnAlgebra (LanguageF effects) (m a) interpretLanguageF (Interpreter handlers) (LanguageF (Col variant)) = h (getApply variant) where HandlerM h = rget variant handlers -- pattern matching on `Col` refines the type, -- which we index into the handler array, -- (@Apply a f@ unifies with @proxy f@), -- to access the correct handler. {-old interpretLanguageF :: Rec (OpCoAlgebra a) fs -> CoRec (Apply a) fs -> a interpretLanguageF handlers (Col variant) = h (getApply variant) where OpCoAlgebra h = rget variant handlers -} -------------------------------------------------------------------------------- {-| make a interpreter from a single handler. e.g. @ data ClipboardF k = GetClipboard (String -> k) | SetClipboard String k deriving Functor clipboardInterpreter :: Interpreter IO '[ClipboardF] a clipboardInterpreter = singletonInterpreter $ \\case GetClipboard f -> ... >>= f SetClipboard s k -> ... s >> k @ -} singletonInterpreter :: (forall x. AnAlgebra f (m x)) -> Interpreter m '[f] singletonInterpreter h = Interpreter hs where hs = HandlerM h :& RNil {- | compose two interpreters. ordered. -} appendInterpreters :: Interpreter m fs -> Interpreter m gs -> Interpreter m (fs ++ gs) appendInterpreters = asInterpreter2 (<+>) --TODO Can coerce? {-| Discard any number of handlers from the interpreter. -} downcastInterpreter :: (gs ⊆ fs) => Interpreter m fs -> Interpreter m gs downcastInterpreter = asInterpreter1 rcast --TODO Can coerce? -------------------------------------------------------------------------------- {-TODO unwrap all rec ops with classes? -} {- | Lift a (unary) function on records to interpreters (a newtype thereof). e.g. @ 'downcastInterpreter' = asInterpreter1 'rcast' @ -} asInterpreter1 :: (Rec (HandlerM m) fs -> Rec (HandlerM m) gs) -> (Interpreter m fs -> Interpreter m gs) asInterpreter1 u = \fs -> Interpreter $ u (getInterpreter fs) {- | Lift an operation on records to interpreters (a newtype thereof). e.g. @ 'appendInterpreters' = asInterpreter2 ('<+>') @ -} asInterpreter2 :: (Rec (HandlerM m) fs -> Rec (HandlerM m) gs -> Rec (HandlerM m) hs) -> (Interpreter m fs -> Interpreter m gs -> Interpreter m hs) asInterpreter2 op = \fs gs -> Interpreter $ (getInterpreter fs) `op` (getInterpreter gs) {-old :: (Rec (HandlerM m) fs -> Rec (HandlerM m) gs -> Rec (HandlerM m) hs) -- TODO weaker, but clearer: {forall g xs. Rec g xs}) :: (forall g as bs cs. Rec g as -> Rec g bs -> Rec g cs) -} {-err appendInterpreters = coerce (<+>) Couldn't match representation of type ‘Rec f0 (as0 ++ bs0)’ with that of ‘Interpreter m (fs ++ gs) a’ arising from trying to show that the representations of ‘Rec f0 as0 -> Rec f0 bs0 -> Rec f0 (as0 ++ bs0)’ and ‘Interpreter m fs a -> Interpreter m gs a -> Interpreter m (fs ++ gs) a’ are the same Relevant role signatures: type role Rec nominal representational nominal type role ++ nominal nominal nominal type role Interpreter nominal nominal nominal -} {-| inverts 'singletonInterpreter', for convenient access. -} fromSingletonInterpreter :: Interpreter m '[f] -> AnAlgebra f (m a) fromSingletonInterpreter (Interpreter (HandlerM h :& RNil)) = h --TODO spurious non-exhaustive --------------------------------------------------------------------------------