Workflow examples

From Clean
Revision as of 10:30, 19 March 2009 by ErikZuurbier (talk | contribs) (Lazy workflow)
Jump to: navigation, search

To determine the direction of the iTasks research we need examples of workflow scenarios to see if they can be implemented in the system.

Basic workflow

Lazy workflow

A lazy workflow is one where a user is not asked to provide any data until it is sure the data are needed for constructing the end result. Well, you can do this in any programming language, so we only consider a workflow to be lazy if not only the computer does a minimum job; also the programmer is allowed to be lazy: a minimum of special language constructs or manual program transformations needed to turn a program from strict into lazy.

We know Clean is lazy in this respect for ordinary program fragments that do not involve I/O. We also know that such laziness in workflows is impossible without compromising referential transparency. But all is not lost.

The following assignment is a simple teaser to demonstrate that lazy workflows are even impossible in today's iTasks: Say we have a tree structure with a form of some type at each Node. The form could be for a mortgage application, an insurance claim, or anything else. For simplicity’s sake, we assume the form is a simple Integer number.

The assignment is to use your favorite workflow platform to display a task for each form and ask the user to enter a Boolean verdict for each form. For the mortgage application, the answer could be ‘granted’ or ‘not granted’, for the insurance claim it could be ‘covered’ or ‘not covered’. For the Integer number it could be ‘even’ or ‘odd’. The assignment includes delivering the result in a tree structure. The input tree and the result tree should have the same shape.

The result tree will be consumed by a black box process. We do not know in which order this process will consume the Leaves and Nodes of the tree, nor whether it will consume all Leaves and Nodes, nor how much time it will take. Parts of the tree could even be consumed in a parallel or interleaved way. We know nothing of the tree’s size. We must avoid submitting forms for a verdict if that particular verdict will never be consumed. We should also take into account that there may be multiple independent black box consuming processes: each form should be submitted for a verdict to a workflow user at most once.

Dynamic workflow

  • A user is given the task of producing some data (e.g. fill in a large form). Since he does not have all information to complete the task he decides to: fill in part of the form himself, divide the rest of the form and assigns the pieces to a number of experts. Depending on whether the user trusts his experts enough he may setup the task such that: A. the information of the expert is automatically merged with his own and the task is closed and data delivered, or B. he first wants to validate the information from the experts before passing the data along.

Store for anything workflow

  • In this example we construct a store for anything, i.e. the items that you want to sell are determined by their type. The store specification itself is fully overloaded and generic. Stock and orders are stored in a database. The complete source code can be downloaded at
  • We define two workflow situations in this case study.
  • A catalogue management workflow in which a worker can edit, remove, and add items in the store. This is an example of a regular workflow, because each item in stock can be manipulated in the same way. This can be expressed concisely by a map of the stock items.
manageCatalog          :: a [HtmlTag] -> Task a | iData, DB a
manageCatalog _ prompt = stopTask (prompt ?>> foreverTask (dbReadAll =>> browseCatalog))
  browseCatalog        :: [a] -> Task a | iData, DB a
  browseCatalog items  = orTasksVert (map itemActions items ++ [chooseTask_btn [] [("Append",new)]])
     new               = dbCreateItem =>> \first -> editTask "Store" first =>> dbUpdateItem

     itemActions       :: a -> Task a | iData, DB a
     itemActions item  = chooseTask_btn [toHtml item] 
                           [("Edit",   editTask "Store" item =>> dbUpdateItem)
                           ,("Delete", dbDeleteItem (getItemId item) #>> return item)
  • A customer workflow. A customer can browse the shop catalogue, add and remove items in a shopping cart, buy the selected items of the cart, or leave the shop. This is an example of an irregular recursive workflow.
doShopping                 :: (Cart a) [a] -> Task (ShopAction,Cart a) | iData, Product a
doShopping cart []         = (shopPrompt ++ [normalText "Currently no items in catalogue, sorry."])
                             ?>> OK #>> return (LeaveShop,cart)
doShopping cart items      = orTasksVert [ navigateShop shopPrompt cart : map (itemActions cart) items ] 
   itemActions             :: (Cart a) a -> Task (ShopAction,Cart a) | iData, Product a
   itemActions cart item   = chooseTask_btn [toHtml item] [("Add to Cart", return (ToCart, add (toCartItem item) cart))]
      add                  :: (CartItem a) [CartItem a] -> [CartItem a]
      add new []           = [new]
      add new [item:items] = if (eqItemNr new item) 
                                [amountOrderedUpd item (amountOrderedOf item+1):items]
                                [item:add new items]
  • Shop navigation is a matter of selecting the right button:
navigateShop               :: [HtmlTag] (Cart a) -> Task (ShopAction, Cart a) | iData a
navigateShop prompt cart   = chooseTask [] 
                                [ ("Do Shopping",       return (ToShop,   cart))
                                , ("Check Out And Pay", return (ToPay,    cart))
                                , ("Show Cart",         return (ToCart,   cart))
                                , ("Leave Shop",        return (LeaveShop,cart))
                                ] <<? [ BrTag  [], boldText "Total cost of ordered items = ", toHtml (totalCost cart)
                                      , DivTag [] prompt ]
  • The irregular recursive structure is connected together with the following two, mutually recursive, functions:
browseShop                 :: (Cart a) [HtmlTag] [HtmlTag] -> Task Void | iData, DB, Product a
browseShop initCart shopPrompt cartPrompt
                           = dbReadAll      =>> \items ->
                             doShopping initCart items =>> 
                             doAction   initCart items
doAction                   :: (Cart a) [a] (ShopAction, Cart a) -> Task Void | iData, DB, Product a
doAction initCart items (action,cart)
                           = case action of
                               LeaveShop = return Void
                               ToCart    = showCart   cart       =>> doAction initCart items
                               ToShop    = doShopping cart items =>> doAction initCart items
                               ToPay     = checkOutAndPay cart   #>> browseShop initCart shopPrompt cartPrompt
  • Do you want to sell books? Let's make a book type:
:: Book                    = { id_     :: DBRef Book
                             , title   :: String
                             , author  :: String
                             , price   :: HtmlCurrency
                             , inStock :: Int
  • It has to connect to a database:
instance DB Book where databaseId         = mkDBid "books" LSTxtFile
                       getItemId item     = id_Of  item
                       setItemId id item  = id_Upd item id
  • It has to connect to the store, that wants to know what kind of product it is:
class Product a | nameOf, priceOf, id_Of, inStockOf a
instance nameOf    Book where nameOf    r = r.Book.title
instance priceOf   Book where priceOf   r = r.Book.price
instance id_Of     Book where id_Of     r = r.Book.id_
instance inStockOf Book where inStockOf r = r.Book.inStock
  • It has to be the default product and cart content:
defaultProduct             :: Book
defaultProduct             = createDefault
defaultCart                :: Cart Book
defaultCart                = createDefault
  • You're done!