The Crowd is a Monad

I had a great time this week at Computer Supported Cooperative Work and Social Computing. Met lots of interesting people, heard good talks, enjoyed Baltimore.

One of my favorite papers of the conference was AskSheet: Efficient Human Computation for Decision Making with Spreadsheets by Alex Quinn and Ben Bederson. The concept is brilliantly simple: embed crowdsourcing capabilities in a spreadsheet, allowing decision makers (and others) to use the tools they already know to solicit information via Mechanical Turk and process the responses. To make it all work, they did some very interesting work on batching queries, estimating the likelihood of actually needing a piece of data and using this to order the crowd data requests, and other things to improve the efficiency of an application’s use of the crowd.

It’s not hard to envision an extension of this work into functional programming more generally. Someone could create a Crowd monad with an API something like this:

--| Create a crowdsourced query.
ask :: String     --^ Descriptive text for this query.
    -> Schema a   --^ Schema for user responses.
    -> Crowd a
--| Run a crowdsourced query, returning the results (after processing).  Runs in
-- IO because it needs networking, etc.
crowdsource :: CrowdProvider -> Crowd a -> IO a

And then we could do a crude port of the shopping list example Alex used in his talk:

import Control.Monad.Crowd

groceries = ["apples", "bannanas", "cheerios", "wheat bread"]
stores = ["Trader Joe's", "Rainbow", "Cub Foods"]

bills :: Crowd [Currency]
bills = mapM totalBill stores
  where
    -- get prices for items at a store and sum them
    totalBill :: String -> Crowd Currency
    totalBill store = fmap sum $ mapM (priceQuery store) groceries
    -- make a query for the price of an individual item at a store
    priceQuery :: String -> String -> Crowd Currency
    priceQuery store item = ask text priceSchema
      where text = "What is the price of " ++ item ++ " at " ++ store ++ "?"

bestStore :: Crowd (String, Currency)
bestStore = fmap pickBest bills
  where
    pickBest prices = minimumBy snd $ zip stores bills

main = do
  (store, price) <- crowdsource mechanicalTurk bestStore
  putStrLn ("The best store is " ++ store
            ++ " with a price of " ++ show price)

To realize the optimization benefits of Quinn & Bederson’s work, you’d also need to write crowd-aware versions of various intersting functions such as medians, selections, etc. That should all be very doable, though, and then we’d have a rather interesting and powerful way to solicit and process crowdsourced data.

The code in this example just depends on the crowd being a functor, not a full monad. But representing it as a monad allows for queries that depend on the results of previous queries.

But as entertaining as musing about a Crowd monad may be, the spreadsheet is probably a far better embedding for making this kind of capability broadly usable.