packages feed

stable-tree-0.6.0: demo/Main.hs

-- |A small demo program to demonstrate how stable trees behave compared with
-- how one might naively implement versioned trees in a relational database.
-- More detailed explanation of the naive storage is in the stupidCount
-- function.
module Main
( main
) where

import Data.StableTree
import Data.StableTree.Persist

import qualified Data.Map as Map
import Control.Monad              ( foldM )
import Control.Monad.State.Strict ( State, runState, modify )
import Data.Map                   ( Map )
import Data.ObjectID              ( ObjectID )
import Data.Text                  ( Text )

type S = Map ObjectID (Fragment Int Int)

data DemoError = ApiError Text
instance Error DemoError where
  stableTreeError = ApiError

-- |Make a ton of related maps, storing all of them in a RAM store and printing
-- out the total number of unique entries in that store and how many database
-- entries would be required from a naive database implementation every
-- so-often.
main :: IO ()
main = do
  m0 <- foldM doRun Map.empty [0,100..1000::Int]
  let m1 = upd m0 [100..1000]
  prTrees m1
  let m2 = upd m1 [200..1000]
  prTrees m2
  let m3 = upd m2 $ [0..400] ++ [600..1000]
  prTrees m3

  where
  doRun :: S -> Int -> IO S
  doRun m0 i0 = do
    let m' = foldl (\m i -> upd m [0..i]) m0 [i0..i0+100]
    putStr $ "naive gives: " ++ stupidCount (i0+100) ++ " / stable gives: "
    prTrees m'
    return m'

  upd :: S -> [Int] -> S
  upd m is =
    let t = fromMap $ Map.fromList [(a,a+1) | a <- is]
        (_,m') = runState (save t) m
    in m'

  save :: StableTree Int Int -> State S (Either DemoError ObjectID)
  save = store' (\oid frag -> modify (Map.insert oid frag) >> return Nothing)

  prTrees m = do
    putStrLn $ (show $ Map.size m) ++ " unique entries"

-- |The typical way of storing key/value maps in SQL is to use a relational
-- table, like this:
--
-- @
-- CREATE TABLE Trees ( id serial primary key, name text );
-- CREATE TABLE Values ( id serial primary key, value bytea );
-- CREATE TABLE tree_entries ( tree_id integer references trees
--                           , value_id integer references values
--                           , name text
--                           , unique(tree_id, name)
--                           );
-- @
--
-- Using this strategy works poorly when trees are related, such as when doing
-- version control on a set of directories. In that case, supposing one were to
-- make a new version every time a file were added to a directory, the size of
-- tree_entries grows as the square of the size of trees. This function does
-- that calculation.
stupidCount :: Int -> String
stupidCount i = show $ i*(i-1) `div` 2