porcupine-core-0.1.0.0: src/System/TaskPipeline/Caching.hs
{-# LANGUAGE Arrows #-}
{-# LANGUAGE TupleSections #-}
-- | Functions in that module are necessary only if you want a fine control over
-- the caching of some actions. When you want to perform several reads and
-- writes from and to VirtualFiles as part of a /single/ cached task, the recommended way is
-- to use:
--
-- - 'getDataReader'/'getDataWriter' to obtain the accessors
-- - 'toTask'' to create the cached task, to which you give the accessors
--
-- Given the accessors are hashable, the files that are bound to them are
-- incorporated to the hash, so binding them to new files will re-trigger the
-- task.
module System.TaskPipeline.Caching
( toTaskAndWrite
, toTaskAndWrite_
-- * Re-exports
, module Data.Locations.LogAndErrors
, Properties(..)
, defaultCacherWithIdent
, Default(..)
) where
import qualified Control.Exception.Safe as SE
import Control.Funflow
import Data.Default (Default (..))
import Data.Locations.LogAndErrors
import Data.Locations.VirtualFile
import System.TaskPipeline.PorcupineTree
import System.TaskPipeline.PTask
import System.TaskPipeline.VirtualFileAccess
import Prelude hiding (id, (.))
-- | For when the result of the lifted function just needs to be written, not
-- returned.
toTaskAndWrite_
:: (LogCatch m, Typeable b, Typeable ignored)
=> Properties (a, DataWriter m b) () -- ^ Location types aren't ContentHashable, but
-- all are convertible to JSON. We need that to
-- hash on locations so the task is repeated if
-- we bind to new locations.
-> VirtualFile b ignored -- ^ The VirtualFile to write
-> (a -> m b) -- ^ The function to lift. Won't be executed if
-- the file isn't mapped
-> PTask m a ()
toTaskAndWrite_ props vf f =
toTaskAndWrite props id vf (fmap (,()) . f) (const $ return ())
{-# INLINE toTaskAndWrite_ #-}
-- | Similar to 'toTask'', but caches a write action of the result too. In this
-- case we use the filepath bound to the VirtualFile to compute the hash. That
-- means that if the VirtualFile is bound to something else, the step will be
-- re-executed.
toTaskAndWrite
:: (LogCatch m, Typeable b, Typeable ignored)
=> Properties (a', DataWriter m b) c -- ^ Location types aren't ContentHashable, but
-- all are convertible to JSON. We need that to
-- hash on locations so the task is repeated if
-- we bind to new locations.
-> (a -> a') -- ^ If the input mustn't or cannot be fully
-- hashed, you can select a subset of it or
-- transform it into a hashable intermediate
-- representation (like aeson Value). Else just
-- use 'id'
-> VirtualFile b ignored -- ^ The VirtualFile to write. If the file
-- isn't mapped, the action won't be performed,
-- and the task will return the default result.
-> (a -> m (b,c)) -- ^ The function to lift. First item of the
-- returned tuple will be written to the
-- VirtualFile. The second will be returned by
-- the task, so it must be loadable from the
-- store.
-> (a -> m c) -- ^ Called when the VirtualFile isn't mapped,
-- and therefore no @b@ needs to be computed
-> PTask m a c
toTaskAndWrite props inputHashablePart vf action actionWhenNotMapped = proc input -> do
writer <- getDataWriter vf -< ()
throwTask <<< toTask' props' cached -< (input,writer)
where
cached (input,writer) | null (dwLocsAccessed writer)
= Right <$> actionWhenNotMapped input
| otherwise
= do
res <- SE.try $ action input
case res of
Right (outputForVFile, outputForStore) -> do
dwPerformWrite writer outputForVFile
return $ Right outputForStore
Left err -> return $ Left (err::SomeException)
props' = props { cache = cache'
, mdpolicy = updMdw <$> mdpolicy props }
getH (input,writer) = (inputHashablePart input,writer)
cache' = case cache props of
NoCache -> NoCache
Cache key sv rv ->
let key' salt = key salt . getH
sv' (Left e) = error $
"toTaskAndWrite: An exception occured during the cached function: "
++ displayException e
sv' (Right x) = sv x
rv' = Right . rv
in Cache key' sv' rv'
updMdw mdWriter i (Right o) = mdWriter (getH i) o
updMdw _ _ (Left _) = []