----------------------------------------------------------------------------- -- | -- Module : Data.Propagator.Cell -- Copyright : (c) Michael Szvetits, 2020 -- License : BSD3 (see the file LICENSE) -- Maintainer : typedbyte@qualified.name -- Stability : stable -- Portability : portable -- -- This module exports the types and functions needed to create cells, -- manipulate their data and wire them up for data propagation. ----------------------------------------------------------------------------- module Data.Propagator.Cell ( -- * Cell Creation and Inspection Cell , cell , readCell , writeCell -- * Cell Connection , connect , sync , syncWith -- * Value Propagation , Propagation , undo , succeeded , propagate , propagateMany , label ) where -- base import Control.Monad (forM) import Control.Monad.ST (ST) import Data.STRef (STRef, modifySTRef, newSTRef, readSTRef, writeSTRef) import Data.Propagator.Change (Change(..)) -- | The result of a propagation which allows to rollback changes and inspect -- its success. data Propagation s = Propagation { undo :: ST s () -- ^ An action that reverts all cell changes of a propagation (both direct -- and transitive ones). , succeeded :: Bool -- ^ 'True' if the propagation was successful (i.e., it did not lead to a -- cell change that is 'Incompatible'), otherwise 'False'. -- -- Note that unsuccessful propagations are not automatically reverted. Use -- 'undo' to do this. } addUndo :: ST s () -> Propagation s -> Propagation s addUndo action (Propagation us r) = Propagation (us >> action) r type Propagator s a = a -> ST s (Propagation s) -- | The type of a cell holding a value of type @a@. The type parameter @s@ -- serves to keep the internal states of different cell networks separate from -- each other (see 'ST' for details). data Cell s a = Cell { _join :: a -> a -> Change a , valueRef :: STRef s a , propRef :: STRef s (Propagator s a) } instance Eq (Cell s a) where Cell _ lr _ == Cell _ rr _ = lr == rr -- | Constructs a new cell with a given initial value and a function which -- defines how to react if a new value is about to be written to the cell. cell :: a -- ^ The initial value of the cell. -> (a -> a -> Change a) -- ^ A function that describes how to join -- an existing cell value with a new one that -- the cell has received via propagation. -> ST s (Cell s a) -- ^ The newly constructed cell. cell value f = do v <- newSTRef value n <- newSTRef emptyPropagator pure (Cell f v n) -- | Reads the value of a specific cell. readCell :: Cell s a -> ST s a readCell = readSTRef . valueRef -- | Writes a new value to a specific cell and starts to propagate potential -- changes through the network of connected cells. writeCell :: a -> Cell s a -> ST s (Propagation s) writeCell new (Cell f vRef pRef) = do old <- readSTRef vRef case f old new of Unchanged -> pure success Incompatible -> pure failure Changed n -> do writeSTRef vRef n propagator <- readSTRef pRef propagation <- propagator n pure (addUndo (writeSTRef vRef old) propagation) emptyPropagator :: Propagator s a emptyPropagator = const (pure success) failure :: Propagation s failure = Propagation (pure ()) False success :: Propagation s success = Propagation (pure ()) True attach :: Propagator s a -> STRef s (Propagator s a) -> ST s () attach newProp pRef = modifySTRef pRef $ \currentProp a -> chain (currentProp a) (newProp a) -- | Connects a source cell to a target cell in order to propagate changes -- from the source to the target. -- -- Note that newly connected cells do not start to propagate changes -- immediately after wiring up. Use 'propagate' or 'propagateMany' to do this. connect :: Cell s a -- ^ The source cell. -> Cell s b -- ^ The target cell. -> (a -> ST s b) -- ^ A function that describes how the value for the -- target cell is constructed, based on the value of -- the source cell. -> ST s () -- ^ Note that no propagation takes place (i.e., no -- 'Propagation' is returned). connect source target f = flip attach (propRef source) $ \a -> do newValue <- f a writeCell newValue target -- | Connects and synchronizes two cells, i.e. new values are propagated from -- the source to the target cell, and vice versa. Short form of 'syncWith' -- 'id' 'id'. -- -- Note that newly connected cells do not start to propagate changes -- immediately after wiring up. Use 'propagate' or 'propagateMany' to do this. sync :: Cell s a -> Cell s a -> ST s () sync = syncWith id id -- | Connects and synchronizes two cells using two translation functions @f@ -- and @g@, i.e. new values are propagated from the source to the target cell -- using @f@, and vice versa using @g@. -- -- Note that newly connected cells do not start to propagate changes -- immediately after wiring up. Use 'propagate' or 'propagateMany' to do this. syncWith :: (a -> b) -> (b -> a) -> Cell s a -> Cell s b -> ST s () syncWith f g left right = do connect left right (pure . f) connect right left (pure . g) -- | Propagates the value of a specific cell to its connected cells in a -- transitive manner. The propagation ends if no more cell changes occur or if -- an 'Incompatible' cell value change is encountered. propagate :: Cell s a -> ST s (Propagation s) propagate source = do value <- readSTRef (valueRef source) propagator <- readSTRef (propRef source) propagator value -- | Propagates the values of specific cells to their connected cells in a -- transitive manner. The propagation ends if no more cell changes occur or if -- an 'Incompatible' cell value change is encountered. propagateMany :: [Cell s a] -> ST s (Propagation s) propagateMany [] = pure success propagateMany (c:cs) = chain (propagate c) (propagateMany cs) chain :: ST s (Propagation s) -> ST s (Propagation s) -> ST s (Propagation s) chain prop continue = do propagation <- prop if succeeded propagation then do rest <- continue pure (addUndo (undo propagation) rest) else pure propagation -- | If the content of a @Cell s a@ is an accumulation of multiple values @[b]@, -- and every value @b@ itself can be used as content @a@ for the cell, then we -- can write every value @b@ one after another to the cell and check if the -- network converges to a successful state. -- -- As a result, we can enumerate all possible combinations of valid values for -- a given set of cells. This is often used in constraint solving algorithms. label :: (a -> [b]) -- ^ A function which extracts testable values from a cell -- content. -> (b -> a) -- ^ A function which translates a testable value into a -- value which can be written back to the cell. -> [Cell s a] -- ^ The set of cells for which the values are enumerated. -> ST s [[b]] -- ^ Returns all valid assignments for the given cells. label elems reify cells = solve [] (reverse cells) where solve current [] = pure [current] solve current (c:cs) = do cellValue <- readCell c solutions <- forM (elems cellValue) $ \v -> do propagation <- writeCell (reify v) c vSolutions <- if succeeded propagation then solve (v:current) cs else pure [] undo propagation pure vSolutions pure (concat solutions)