build-0.0.1: src/Build/Rebuilder.hs
{-# LANGUAGE ConstraintKinds, RankNTypes, TupleSections #-}
-- | Rebuilders take care of deciding whether a key needs to be rebuild and
-- running the corresponding task if need be.
module Build.Rebuilder (
Rebuilder, perpetualRebuilder,
modTimeRebuilder, Time, MakeInfo,
approximationRebuilder, DependencyApproximation (..), ApproximationInfo,
vtRebuilder, stRebuilder, ctRebuilder, dctRebuilder
) where
import Control.Monad.State
import Data.Map (Map)
import qualified Data.Map as Map
import Build.Store
import Build.Task
import Build.Task.Applicative
import Build.Task.Monad
import Build.Trace
-- | Given a key-value pair and the corresponding task, a rebuilder returns a
-- new task that has access to the build information and can use it to skip
-- rebuilding a key if it is up to date.
type Rebuilder c i k v = k -> v -> Task c k v -> Task (MonadState i) k v
-- | Always rebuilds the key.
perpetualRebuilder :: Rebuilder Monad () k v
perpetualRebuilder _key _value task = task
------------------------------------- Make -------------------------------------
type Time = Integer
type MakeInfo k = (Map k Time, Time)
-- | This rebuilder uses modification time to decide whether a key is dirty and
-- needs to be rebuilt. Used by Make.
modTimeRebuilder :: Ord k => Rebuilder Applicative (MakeInfo k) k v
modTimeRebuilder key value task fetch = do
(modTime, now) <- get
let dirty = case Map.lookup key modTime of
Nothing -> True
time -> any (\d -> Map.lookup d modTime > time) (dependencies task)
if not dirty
then return value
else do
put (Map.insert key now modTime, now + 1)
task fetch
--------------------------- Dependency approximation ---------------------------
data DependencyApproximation k = SubsetOf [k] | Unknown
type ApproximationInfo k = (k -> Bool, k -> DependencyApproximation k)
-- | This rebuilders uses approximate dependencies to decide whether a key
-- needs to be rebuilt. Used by Excel.
approximationRebuilder :: Ord k => Rebuilder Monad (ApproximationInfo k) k v
approximationRebuilder key value task fetch = do
(isDirty, deps) <- get
let dirty = isDirty key || case deps key of SubsetOf ks -> any isDirty ks
Unknown -> True
if not dirty
then return value
else do
put (\k -> k == key || isDirty k, deps)
task fetch
------------------------------- Verifying traces -------------------------------
-- | This rebuilder relies on verifying traces.
vtRebuilder :: (Eq k, Hashable v) => Rebuilder Monad (VT k v) k v
vtRebuilder key value task fetch = do
vt <- get
dirty <- not <$> verifyVT key value (fmap hash . fetch) vt
if not dirty
then return value
else do
(newValue, deps) <- trackM task fetch
put =<< recordVT key newValue deps (fmap hash . fetch) =<< get
return newValue
------------------------------ Constructive traces -----------------------------
-- | This rebuilder relies on constructive traces.
ctRebuilder :: (Eq k, Hashable v) => Rebuilder Monad (CT k v) k v
ctRebuilder key value task fetch = do
ct <- get
maybeCachedValue <- constructCT key value (fmap hash . fetch) ct
case maybeCachedValue of
Just cachedValue -> return cachedValue
Nothing -> do
(newValue, deps) <- trackM task fetch
put =<< recordCT key newValue deps (fmap hash . fetch) =<< get
return newValue
----------------------- Deterministic constructive traces ----------------------
-- | This rebuilder relies on deterministic constructive traces.
dctRebuilder :: (Hashable k, Hashable v) => Rebuilder Monad (DCT k v) k v
dctRebuilder key _value task fetch = do
dct <- get
maybeCachedValue <- constructDCT key (fmap hash . fetch) dct
case maybeCachedValue of
Just cachedValue -> return cachedValue
Nothing -> do
(newValue, deps) <- trackM task fetch
put =<< recordDCT key newValue deps (fmap hash . fetch) =<< get
return newValue
------------------------------- Version traces -------------------------------
-- | This rebuilder relies on version/step traces.
stRebuilder :: (Eq k, Hashable v) => Rebuilder Monad (Step, ST k v) k v
stRebuilder key value task fetch = do
dirty <- not <$> verifyST key value (void . fetch) (gets snd)
if not dirty
then return value
else do
(newValue, deps) <- trackM task fetch
(step, st) <- get
put . (step,) =<< recordST step key newValue deps st
return newValue