wizards-0.1: System/Console/Wizard/Internal.hs
{-# LANGUAGE GADTs, KindSignatures #-}
module System.Console.Wizard.Internal ( WizardAction (..)
, PromptString (..)
-- $backend
) where
type PromptString = String
-- | Internally, a 'Wizard' is essentially a prompt monad with a 'WizardAction'. A constructor exists for each primitive action, as well
-- as a special \"escape hatch\" constructor ('Backend') used for writing backend-specific primitives and modifiers.
-- Each back-end has a corresponding data type, used as a type parameter for 'Wizard'. This data type is usually opaque, but internally
-- specifies additional primitive actions that are specific to the back-end.
-- 'WizardAction' is parameterised by this data type (for use in the 'Backend' constructor), the prompt monad itself (so that modifiers
-- can be made as well as primitives) and the return type of the action.
data WizardAction :: ((* -> *) -> * -> *) -> (* -> *) -> * -> * where
Line :: PromptString -> WizardAction b m String
LinePreset :: PromptString -> String -> String -> WizardAction b m String
Password :: PromptString -> Maybe Char -> WizardAction b m String
Character :: PromptString -> WizardAction b m Char
Output :: String -> WizardAction b m ()
OutputLn :: String -> WizardAction b m ()
Backend :: b m a -> WizardAction b m a
-- $backend
-- A short tutorial on writing backends.
--
-- Backends consist of two main components:
--
-- 1. A back-end data type (the type parameter to 'Wizard'), which includes constructors
-- for any primitive actions or modifiers that are specific to the back-end.
--
-- 2. An interpreter function, of type @Wizard DataType a -> B (Maybe a)@ for some type @B@ (depending on the backend).
-- Typically this function will provide semantics for each 'WizardAction' using 'runRecPromptM' or similar.
--
-- The 'Backend' constructor can be used to add back-end specific primitives and modifiers.
--
-- As an example, suppose I am writing a back-end to @IO@, like "System.Console.Wizard.BasicIO".
-- One additional primitive action that I might want to include is the ability to run arbitrary @IO@ actions while a wizard is running.
-- So, my backend data type will be:
--
-- @
-- data MyBackend (m :: * -> *) r = ArbitraryIO (IO r) -- kind signature to avoid defaulting to *
-- @
--
-- And my interpreter function will be:
--
-- @
-- runWizardMyBackend :: Wizard MyBackend a -> IO a
-- runWizardMyBackend (Wizard (MaybeT c)) = runRecPromptM f c
-- where f :: WizardAction MyBackend (RecPrompt (WizardAction MyBackend)) a -> IO a
-- f (Output s) = putStr s
-- f (... ) = ...
-- f (Backend (ArbitraryIO io)) = io
-- @
--
-- And then the action can be easily defined:
--
-- @
-- runIO :: IO a -> Wizard MyBackend a
-- runIO = prompt . Backend . ArbitraryIO
-- @
--
-- I might also want to include a /modifier/, which say, colours any output text green. Assuming I have a function
-- @
-- withGreenText :: IO a -> IO a
-- @
-- which causes any output produced by the input action to be coloured green, we can use the 'Backend' constructor to transform
-- this into a wizard modifier.
--
-- @
--data MyBackend m r = ArbitraryIO (IO r)
-- | GreenText (m r)
--
--runWizardMyBackend :: Wizard MyBackend
--runWizardMyBackend (Wizard (MaybeT c)) = runRecPromptM f c
-- where f :: WizardAction MyBackend (RecPrompt (WizardAction MyBackend)) a -> IO a
-- f (Output s) = putStr s
-- f (... ) = ...
-- f (Backend (ArbitraryIO io)) = io
-- f (Backend (GreenText a)) = withGreenText $ runRecPromptM f a
--
--greenText :: Wizard MyBackend a -> Wizard MyBackend a
--greenText (Wizard (MaybeT a)) = prompt (Backend (GreenText a))
-- @
--
--