------------------------------------------------------------------------------
--- Library for dynamic predicates.
---
--- This paper contains a description of the basic ideas
--- behind this library.
---
--- Currently, it is still experimental so that its interface might
--- be slightly changed in the future.
---
--- A dynamic predicate p
with arguments of type
--- t1,...,tn
must be declared by:
---
--- p :: t1 -> ... -> tn -> Dynamic
--- p = dynamic
---
---
--- A dynamic predicate where all facts should be persistently stored
--- in the directory DIR
must be declared by:
---
--- p :: t1 -> ... -> tn -> Dynamic
--- p = persistent "file:DIR"
---
--- Remark:
--- This library has been revised to the library Database
.
--- Thus, it might not be further supported in the future.
---
--- @author Michael Hanus
--- @version August 2011
--- @category database
------------------------------------------------------------------------------
module Dynamic(Dynamic,(<>),(|>),(|&>),dynamic,persistent,
assert,retract,getKnowledge,
getDynamicSolutions,getDynamicSolution,isKnown,
transaction,transactionWithErrorCatch,abortTransaction) where
import AllSolutions
infixr 2 <>
infixl 1 |>, |&>
----------------------------------------------------------------------
--- The general type of dynamic predicates.
data Dynamic = Dynamic DynSpec -- a single predicate
| Prod Dynamic Dynamic -- cartesian product of two dynamics
| Cond Dynamic Bool -- conditional dynamic
-- The behavior specification of a dynamic predicate (only internally used).
data DynSpec = Temporary -- a dynamic predicate that exists only during
-- a single execution of a program
| Persistent -- a persistent dynamic predicate
--- dynamic
is only used for the declaration of
--- a dynamic predicate and should not be used elsewhere.
dynamic :: _
dynamic = failed
--- persistent
is only used for the declaration of
--- a persistent dynamic predicate and should not be used elsewhere.
persistent :: String -> _
persistent _ = failed
----------------------------------------------------------------------
--- Combine two dynamics.
(<>) :: Dynamic -> Dynamic -> Dynamic
d1 <> d2 = Prod d1 d2
--- Restrict a dynamic with a condition.
(|>) :: Dynamic -> Bool -> Dynamic
d |> b = Cond d b
--- Restrict a dynamic with a constraint.
(|&>) :: Dynamic -> Bool -> Dynamic
d |&> c = d |> (c &> True)
--- Asserts new facts (without free variables!) about dynamic predicates.
--- Conditional dynamics are asserted only if the condition holds.
assert :: Dynamic -> IO ()
assert (Dynamic spec) = assertFact (Dynamic spec)
assert (Prod d1 d2) = assert d1 >> assert d2
assert (Cond d b) = if b then assert d else done
--- Deletes facts (without free variables!) about dynamic predicates.
--- Conditional dynamics are retracted only if the condition holds.
--- Returns True if all facts to be retracted exist,
--- otherwise False is returned.
retract :: Dynamic -> IO Bool
retract (Dynamic spec) = retractFact (Dynamic spec)
retract (Prod d1 d2) = do
b1 <- retract d1
b2 <- retract d2
return (b1&&b2)
retract (Cond d b) = if b then retract d else return True
--- Returns the knowledge at a particular point of time about dynamic
--- predicates. If other processes made changes to persistent predicates,
--- these changes are read and made visible to the currently running program.
getKnowledge :: IO (Dynamic -> Bool)
getKnowledge = do
known <- getDynamicKnowledge
return (knownAll known)
where
knownAll k (Dynamic spec) = k (Dynamic spec)
knownAll k (Prod d1 d2) = knownAll k d1 & knownAll k d2
knownAll k (Cond d b) = knownAll k d & b=:=True
--- Returns all answers to an abstraction on a dynamic expression.
--- If other processes made changes to persistent predicates,
--- these changes are read and made visible to the currently running program.
getDynamicSolutions :: (a -> Dynamic) -> IO [a]
getDynamicSolutions query = do
known <- getKnowledge
getAllSolutions (\x -> known (query x))
--- Returns an answer to an abstraction on a dynamic expression.
--- Returns Nothing if no answer exists.
--- If other processes made changes to persistent predicates,
--- these changes are read and made visible to the currently running program.
getDynamicSolution :: (a -> Dynamic) -> IO (Maybe a)
getDynamicSolution query = do
known <- getKnowledge
getOneSolution (\x -> known (query x))
--- Returns True if there exists the argument facts (without free variables!)
--- and False, otherwise.
isKnown :: Dynamic -> IO Bool
isKnown (Dynamic spec) = do
known <- getDynamicKnowledge
first <- getOneSolution (\_ -> known (Dynamic spec))
return (first /= Nothing)
isKnown (Prod d1 d2) = do
b1 <- isKnown d1
b2 <- isKnown d2
return (b1&&b2)
isKnown (Cond d c) = do
b <- isKnown d
return (b&&c)
--- Perform an action (usually containing updates of various
--- dynamic predicates) as a single transaction.
--- This is the preferred way to execute any changes to persistent
--- dynamic predicates if there might be more than one process
--- that may modify the definition of such predicates in parallel.
---
--- Before the transaction is executed, the access to all persistent
--- predicates is locked (i.e., no other process can perform a
--- transaction in parallel).
--- After the successful transaction, the access is unlocked so that
--- the updates performed in this transaction become persistent and
--- visible to other processes.
--- Otherwise (i.e., in case of a failure or abort of the transaction),
--- the changes of the transaction to persistent predicates are
--- ignored and Nothing is returned.
---
--- In general, a transaction should terminate and all failures inside
--- a transaction should be handled (execept for abortTransaction).
--- If a transaction is externally interrupted (e.g., by killing the process),
--- some locks might never be removed. However, they
--- can be explicitly removed by deleting the corresponding lock files
--- reported at startup time.
---
--- Nested transactions are not supported and lead to a failure.
transaction :: IO a -> IO (Maybe a)
transaction action = do
tnr <- startTransaction
catch (performTrans tnr)
(\e -> catch abortTransaction -- we perform an explicit abort in
-- case of run time errors in action
(\_ -> putStrLn (showError e) >> return Nothing))
where
performTrans tnr = do
result <- action
commitTransaction tnr
return (Just result)
--- Perform an action (usually containing updates of various
--- dynamic predicates) as a single transaction.
--- This is similar to transaction
but an execution
--- error is caught and returned instead of printing it.
transactionWithErrorCatch :: IO a -> IO (Either a IOError)
transactionWithErrorCatch action = do
tnr <- startTransaction
catch (performTrans tnr)
(\e -> catch abortTransaction -- we perform an explicit abort in
-- case of run time errors in action
(\_ -> return (Right e)))
where
performTrans tnr = do
result <- action
commitTransaction tnr
return (Left result)
--- Aborts the current transaction. If a transaction is aborted,
--- the remaining actions of the transaction are not executed
--- and all changes to persistent dynamic predicates
--- made in this transaction are ignored.
---
--- abortTransaction should only be used in a transaction.
--- Although the execution of abortTransaction always fails
--- (basically, it writes an abort record in log files, unlock them
--- and then fails), the failure is handled inside transaction
.
abortTransaction :: IO _
abortTransaction external
------------------------------------------------------------------------
-- Internals...
-- Asserts a new fact (without free variables!) about a dynamic predicate.
assertFact :: Dynamic -> IO ()
assertFact pred = prim_assertFact $## pred
prim_assertFact :: Dynamic -> IO ()
prim_assertFact external
-- Deletes a fact (without free variables!) about a dynamic predicate.
-- Returns True if there was such a fact and False, otherwise.
retractFact :: Dynamic -> IO Bool
retractFact pred = prim_retractFact $## pred
prim_retractFact :: Dynamic -> IO Bool
prim_retractFact external
-- Returns the knowledge at a particular point of time about all dynamic
-- predicates. If other processes made changes to persistent predicates,
-- these changes are read and made visible to the currently running program.
getDynamicKnowledge :: IO (Dynamic -> Bool)
getDynamicKnowledge external
-- To implement transactions: lock persistent files, load newest version,
-- and mark begin of transaction in log files
startTransaction :: IO Int
startTransaction external
-- To implement transactions: mark end of transaction in log files and
-- unlock persistent files
commitTransaction :: Int -> IO ()
commitTransaction t = prim_commitTransaction $# t
prim_commitTransaction :: Int -> IO ()
prim_commitTransaction external
-- ...to implement getKnowledge:
isKnownAtTime :: Int -> Dynamic -> Bool
isKnownAtTime t dyn = (prim_isKnownAtTime $# t) $!! dyn
prim_isKnownAtTime :: Int -> Dynamic -> Bool
prim_isKnownAtTime external