wizards-0.1: High level, generic library for interrogative user interfaces

Safe HaskellSafe-Infered




data WizardAction whereSource

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.

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))