{-# LANGUAGE KindSignatures, DataKinds, GADTs #-} -- {-# LANGUAGE TemplateHaskell #-} -- {-# OPTIONS_GHC -ddump-splices #-} -- for `makeFree` {-| -} module Vinyl.Effects.Language where import Vinyl.Effects.Extra import Vinyl.Effects.Types import Vinyl.CoRec import Data.Vinyl import Control.Monad.Trans.Free import Control.Monad import Data.Functor.Identity -------------------------------------------------------------------------------- {-| a domain-specific @language@ that supports the @effects@. e.g. @ Language [f,g] a @ -} newtype Language effects a = Language { getLanguage :: FreeF (LanguageF effects) a (Language effects a) } -- deriving (Functor) -- | 'fmap' instance Functor (Language effects) where fmap f (Language m) = Language $ case m of Pure a -> Pure $ f a Free fm -> Free $ fmap f `fmap` fm -- | 'pure' calls 'Pure' instance Applicative (Language effects) where pure = Pure >>> Language (<*>) = ap -- | '>>=' may call 'Free' instance Monad (Language effects) where return = pure (Language m) >>= k = case m of -- TODO right order? Pure a -> k a Free fm -> (Language . Free) $ (>>= k) `fmap` fm -------------------------------------------------------------------------------- {-| a (type-level) @sum@ of "language features". Generally, a (1) lifted, (2) n-ary, (3) associative sum. the `expression` may use any effect in `effects`: @ expression :: LanguageF effects @ generalizes 'Either': @ LanguageF '[f,g] a ~ Either (f a) (g a) @ -} newtype LanguageF (effects :: [* -> *]) (a :: *) = LanguageF { getLanguageF :: (CoRec (Apply a) effects) } -- deriving (Functor) -- TODO SumF -- | instance Functor (LanguageF effects) where fmap f (LanguageF (Col (Apply fa))) = (LanguageF . Col . Apply) (fmap f fa) {-| "inject" an @effect@ into a set of @effects@. generalizes 'Left' and 'Right': * @Left ~ (liftE :: f a -> LanguageF '[f,g] a)@ * @Right ~ (liftE :: g a -> LanguageF '[f,g] a)@ -} liftE :: ( effect ∈ effects , Functor effect ) => effect a -> LanguageF effects a liftE = Apply >>> Col >>> LanguageF -- the type at each step (including input and output): -- -- effect a -- (Apply a) effect -- CoRec (Apply a) effects -- LanguageF effects a -------------------------------------------------------------------------------- {-| the @m@onad supports each @effect@ a "final encoding" (TODO, is it?) for injecting functors into a sum. Analogous to 'liftF'. e.g. @ data ClipboardF k = GetClipboard (String -> k) | SetClipboard String k getClipboard :: (MonadClipboard m effects) => m String getClipboard = liftL $ GetClipboard id -- GetClipboard id :: ClipboardF String setClipboard :: (MonadClipboard m effects) => String -> m ()) setClipboard s = liftL $ SetClipboard s () -- SetClipboard s () :: ClipboardF () type MonadClipboard m effects = (MonadLanguage m effects, ClipboardF ∈ effects) @ the @FunctionalDependency@ (i.e. @m -> effects@) says: "a language-monad supports one set of effects". -} class (Monad m) => MonadLanguage m effects | m -> effects where liftL :: ( f ∈ effects , Functor f ) => f a -> m a --OLD effect (m a) -- | The simplest concrete implementation for the interface. -- -- Analogous to @(m ~ State s)@ for @(MonadState m s)@. -- -- @liftL = 'liftE' >>> 'liftF'@ -- instance MonadLanguage (Language effects) effects where -- :: (effect ∈ effects, Functor effect) => effect a -> Language effects a liftL = liftE >>> liftF -- the type at each step (including input and output): -- -- effect a -- LanguageF effects a -- (LanguageF effects) (Language effects a) -- FreeF (LanguageF effects) a (Language effects a) -- Language effects a {-old liftL = liftE >>> Free >>> Language -- the type at each step (including input and output): -- -- effect a -- effect (Language effects a) -- (LanguageF effects) (Language effects a) -- FreeF (LanguageF effects) a (Language effects a) -- Language effects a -} -- | instance MonadFree (LanguageF effects) (Language effects) where -- wrap :: f (m a) -> m a -- wrap :: (LanguageF effects) ((Language effects) a) -> (Language effects) a wrap = Free >>> Language -------------------------------------------------------------------------------- {-| -} fromUnitLanguageF :: forall f a. LanguageF '[f] a -> f a fromUnitLanguageF = getLanguageF >>> handle unitHandlers >>> getApply -------------------------------------------------------------------------------- -- | cast a (@newtype@'d) @Language@ to @Free@. TODO is cheap (can use 'coerce')? fromLanguage :: Language effects a -> Free (LanguageF effects) a fromLanguage (Language m) = (FreeT . Identity) $ fmap (fromLanguage) m -- | wraps 'iter'. iterL :: (LanguageF effects a -> a) -> (Language effects a -> a) -- CoAlgebra iterL u = fromLanguage >>> iter u -- | wraps 'iterM'. iterLM :: (Monad m) => (LanguageF effects (m a) -> m a) -> (Language effects a -> m a) -- CoAlgebra iterLM u = fromLanguage >>> iterM u --------------------------------------------------------------------------------