creditmonad-1.0.0: src/Control/Monad/Credit/Base.hs
{-# LANGUAGE DerivingStrategies, TypeFamilies #-}
module Control.Monad.Credit.Base
( Cell(..), Credit(..), Ticks(..)
, MonadCount(..), MonadLazy(..), MonadCredit(..), HasStep(..), Lazy(..), MonadInherit(..)
, MTree(..), Memory(..), MemoryCell(..), MonadMemory(..), linearize, mkMCell, mkMList
, MemoryStructure(..), PrettyCell(..)
) where
import Prettyprinter
import Control.Monad
import Control.Monad.State
import Data.Char
import Data.Maybe
import Data.Map (Map)
import Data.Kind (Type)
import qualified Data.Map as Map
newtype Credit = Credit Int
deriving (Eq, Ord, Show)
deriving newtype (Num, Enum, Real, Integral, Pretty)
newtype Cell = Cell Int
deriving (Eq, Ord, Show)
instance Pretty Cell where
pretty (Cell 0) = pretty "main thread"
pretty (Cell i) = pretty "thunk" <+> pretty i
newtype Ticks = Ticks Int
deriving (Eq, Ord, Show)
deriving newtype (Num, Enum, Real, Integral, Pretty)
class Monad m => MonadCount m where
tick :: m ()
-- ^ tick consumes one credit of the current cell
class Monad m => MonadLazy m where
data Thunk m :: (Type -> Type) -> Type -> Type
delay :: t a -> m (Thunk m t a)
-- ^ delay creates a new cell with the given thunk
force :: HasStep t m => Thunk m t a -> m a
-- ^ force retrieves and evaluates the thunk of a cell
lazymatch :: Thunk m t a -> (a -> m b) -> (t a -> m b) -> m b
-- ^ lazymatch can inspect the unevaluated thunk and allows us to
-- perform an action like forcing or assigning credits.
-- | Thunks can take a step to yield a computation that evaluates to their result.
class HasStep t m where
step :: t a -> m a
-- | A basic thunk that contains the computation to be evaluated.
-- This type can be used to express any thunk but its disadvantage is that
-- it will be printed merely as "<lazy>".
newtype Lazy m a = Lazy (m a)
instance HasStep (Lazy m) m where
step (Lazy f) = f
-- | A computation in the credit monad has a given amounts of credits,
-- which it can spend on computation or transfer to other cells.
class (MonadCount m, MonadLazy m, MonadFail m) => MonadCredit m where
creditWith :: Thunk m t a -> Credit -> m ()
-- ^ creditWith transfers a given amount of credits to a cell
hasAtLeast :: Thunk m t a -> Credit -> m ()
-- ^ assert that a cell has at least a given amount of credits
class MonadCredit m => MonadInherit m where
creditAllTo :: Thunk m t a -> m ()
-- ^ creditAllTo transfers all credits to a cell and assigns it as heir
data MTree = MCell String [MTree] | MList [MTree] (Maybe MTree) | Indirection Cell
-- | A view of memory that can be pretty-printed.
data Memory = Memory
{ memoryTree :: MTree
, memoryStore :: Map Cell (MTree, Credit)
}
-- | Make memory cell with a given tag and a list of children.
mkMCell :: String -> [Memory] -> Memory
mkMCell d ms = Memory (MCell d (map memoryTree ms)) (Map.unions (map memoryStore ms))
-- | A special case for nicer printing of list-like datatypes.
-- ''mkMList [m1,...,mn] Nothing'' renders as ''[m1, .., mn]'', while
-- ''mkMList [m1,...,mn] (Just m)'' renders as ''[m1, .., mn] ++ m''.
mkMList :: [Memory] -> Maybe Memory -> Memory
mkMList ms mm =
let ms' = case mm of Nothing -> ms; Just m -> ms ++ [m] in
Memory (MList (map memoryTree ms) (fmap memoryTree mm)) (Map.unions (map memoryStore ms'))
-- | A class for pretty-printing memory cells.
class Monad m => MemoryCell m a where
prettyCell :: a -> m Memory
instance Monad m => MemoryCell m Int where
prettyCell i = pure $ mkMCell (show i) []
instance MemoryCell m a => MemoryCell m [a] where
prettyCell xs = flip mkMList Nothing <$> mapM prettyCell xs
instance (MemoryCell m a, MemoryCell m b) => MemoryCell m (a, b) where
prettyCell (a, b) = mkMCell "" <$> sequence [prettyCell a, prettyCell b]
instance (MemoryCell m a, MemoryCell m b, MemoryCell m c) => MemoryCell m (a, b, c) where
prettyCell (a, b, c) = mkMCell "" <$> sequence [prettyCell a, prettyCell b, prettyCell c]
instance Monad m => MemoryCell m (Lazy m a) where
prettyCell (Lazy _) = pure $ mkMCell "<lazy>" []
class Monad m => MonadMemory m where
prettyThunk :: (MemoryCell m a, MemoryCell m (t a)) => Thunk m t a -> m Memory
instance (MonadMemory m, MemoryCell m a, MemoryCell m (t a)) => MemoryCell m (Thunk m t a) where
prettyCell t = prettyThunk t
newtype PrettyCell a = PrettyCell a
deriving (Eq, Ord, Show)
instance (Monad m, Pretty a) => MemoryCell m (PrettyCell a) where
prettyCell (PrettyCell a) = pure $ mkMCell (show $ pretty a) []
class MemoryStructure t where
prettyStructure :: MonadMemory m => t m -> m Memory
showCredit :: Credit -> String
showCredit (Credit c) = map (chr . (\n -> n - 48 + 8320) . ord) $ show c
annCredit :: Credit -> MTree -> MTree
annCredit c (MCell d ms) = MCell (d ++ showCredit c) ms
annCredit c m = m
-- | Inline memory cells that are only used once and remove them from the store
linearize :: Memory -> Memory
linearize mem = linearize' mem $ countUsages mem
where
countUsage :: MTree -> Map Cell Int
countUsage (MCell _ ms) = Map.unionsWith (+) (map countUsage ms)
countUsage (MList ms mm) =
Map.unionsWith (+) (countUsage <$> ms ++ maybeToList mm)
countUsage (Indirection c) = Map.singleton c 1
countUsages :: Memory -> Map Cell Int
countUsages (Memory mtree mstore) = Map.unionsWith (+) (countUsage mtree : map (countUsage . fst) (Map.elems mstore))
lin :: Map Cell Int -> Map Cell (MTree, Credit) -> Cell -> State (Map Cell (MTree, Credit)) ()
lin usages mstore c = do
mstore' <- get
when (Map.notMember c mstore') $
case Map.lookup c mstore of
Just (mtree, credit) -> do
mtree' <- linearizeTree usages mstore mtree
case Map.lookup c usages of
Just 1 -> modify' $ Map.insert c (annCredit credit mtree', credit)
_ -> modify' $ Map.insert c (mtree', credit)
Nothing -> pure ()
linearizeTree :: Map Cell Int -> Map Cell (MTree, Credit) -> MTree -> State (Map Cell (MTree, Credit)) MTree
linearizeTree usages mstore (MCell d ms) = do
ms' <- mapM (linearizeTree usages mstore) ms
pure $ MCell d ms'
linearizeTree usages mstore (MList ms mm) = do
ms' <- mapM (linearizeTree usages mstore) ms
mm' <- mapM (linearizeTree usages mstore) mm
pure $ MList ms' mm'
linearizeTree usages mstore (Indirection c) =
case Map.lookup c usages of
Just 1 -> do
lin usages mstore c
mstore' <- get
pure $ case Map.lookup c mstore' of
Just (mtree, _) -> mtree
Nothing -> Indirection c
_ -> pure $ Indirection c
linearizeAll :: Map Cell Int -> Map Cell (MTree, Credit) -> Map Cell (MTree, Credit)
linearizeAll usages mstore = Map.foldrWithKey (\k _ -> execState (lin usages mstore k)) Map.empty mstore
removeUniques :: Map Cell Int -> Map Cell (MTree, Credit) -> Map Cell (MTree, Credit)
removeUniques usages mstore = Map.filterWithKey (\c _ -> Map.findWithDefault 0 c usages > 1) mstore
linearize' :: Memory -> Map Cell Int -> Memory
linearize' (Memory mtree mstore) usages =
let mstore' = linearizeAll usages mstore
mtree' = evalState (linearizeTree usages mstore' mtree) mstore'
in Memory mtree' (removeUniques usages mstore')
instance Pretty MTree where
pretty (MCell d []) = pretty d
pretty (MCell d ms) = pretty d <> tupled (map pretty ms)
pretty (MList [] Nothing) = pretty "[]"
pretty (MList [] (Just m)) = pretty m
pretty (MList ms Nothing) = list (map pretty ms)
pretty (MList ms (Just m)) = tupled [list (map pretty ms) <+> pretty "++" <+> pretty m]
pretty (Indirection (Cell c)) = pretty "<" <> pretty c <> pretty ">"
instance Pretty Memory where
pretty (Memory mtree mstore) =
let prettyStore = case Map.toList mstore of
[] -> mempty
_ -> pretty "where:" <+> align (vsep (map (\((Cell c), (m, cr)) -> pretty "<" <> pretty c <> pretty "> =>" <> pretty (showCredit cr) <+> pretty m) (Map.toList mstore)))
in pretty mtree <> line <> prettyStore