packages feed

creditmonad-1.0.0: src/Test/Credit/Queue/Implicit.hs

{-# LANGUAGE GADTs, LambdaCase #-}

module Test.Credit.Queue.Implicit where

import Prelude hiding (head, tail)
import Control.Monad (when, unless)
import Control.Monad.Credit
import Prettyprinter (Pretty)
import Test.Credit.Queue.Base

-- Main idea:
--  - snoc and tail both cost two credits
--  - the first credit is used to tick
--  - We maintain the invariant: In each queue Deep(f, m, r), m has 2 - (|f| - |r|) credits.
--  - The m thunk represents either a snoc or a tail, and thus requires two credits to force.
--  - snoc and tail spend their second credit on either the old m to be able to force it,
--    or on the new m to maintain the invariant.

data Digit a = Zero | One a | Two a a
  deriving (Eq, Ord)

data Implicit a m
  = Shallow (Digit a)
  | Deep (Digit a) (Thunk m (ILazyCon m) (Implicit (a, a) m)) (Digit a)

data ILazyCon m a where
  IPure :: a -> ILazyCon m a
  ISnoc :: Thunk m (ILazyCon m) (Implicit a m) -> a -> ILazyCon m (Implicit a m)
  ITail :: Implicit a m -> ILazyCon m (Implicit a m)

instance MonadCredit m => HasStep (ILazyCon m) m where
  step (IPure x) = pure x
  step (ISnoc t p) = do
    q <- force t
    snoc' q p
  step (ITail q) = tail q

isEmpty :: Implicit a m -> Bool
isEmpty (Shallow Zero) = True
isEmpty _ = False

head :: MonadCredit m => Implicit a m -> m a
head q = case q of
  Shallow (One x) -> pure x
  Deep (Two x _) _ _ -> pure x
  Deep (One x) _ _ -> pure x
  _ -> fail "head: empty queue"

size :: Digit a -> Credit
size = \case { Zero -> 0; One _ -> 1; Two _ _ -> 2 }

deep :: MonadCredit m => Digit a -> Thunk m (ILazyCon m) (Implicit (a, a) m) -> Digit a -> m (Implicit a m)
deep f m r = do
  m `hasAtLeast` (2 - size f + size r)
  pure $ Deep f m r

snoc' :: MonadCredit m => Implicit a m -> a -> m (Implicit a m)
snoc' q y = do
  tick
  case q of
    Shallow Zero -> pure $ Shallow (One y)
    Shallow (One x) -> do
      middle <- delay $ IPure $ Shallow Zero
      deep (Two x y) middle Zero
    Deep front middle Zero -> do
      middle `creditWith` 1
      deep front middle (One y)
    Deep front middle (One x) -> do
      middle `hasAtLeast` (2 - size front + 1)
      t <- delay $ ISnoc middle (x, y)
      if size front == 1
        then t      `creditWith` 1
        else middle `creditWith` 1
      deep front t Zero
    _ -> fail "snoc: malformed queue"

tail :: MonadCredit m => Implicit a m -> m (Implicit a m)
tail q = tick >> case q of
  Shallow (One _) -> pure $ Shallow Zero
  Deep (Two _ y) m rear -> do
    creditWith m 1
    deep (One y) m rear
  Deep (One _) m rear -> do
    unless (size rear == 1) $ creditWith m 1
    m' <- force m
    if isEmpty m'
      then pure $ Shallow rear
      else do
        (y, z) <- head m'
        t <- delay $ ITail m'
        when (size rear == 1) $ creditWith t 1
        deep (Two y z) t rear
  _ -> fail "tail: empty queue"

instance Show a => Show (Digit a) where
  show Zero = "Zero"
  show (One a) = "(One " ++ show a ++ ")"
  show (Two a b) = "(Two " ++ show a ++ " " ++ show b ++ ")"

showThunk :: (MonadLazy m, Show a)
          => Thunk m (ILazyCon m) (Implicit a m) -> m String
showThunk t = lazymatch t showImplicit $ \case
    IPure a -> showImplicit a
    ISnoc middle xy -> do
      m <- showThunk middle
      pure $ "(snoc " ++ m ++ " " ++ show xy ++ ")"
    ITail q -> do
      m <- showImplicit q
      pure $ "(tail " ++ m ++ ")"

showImplicit :: (MonadLazy m, Show a)
             => Implicit a m -> m String
showImplicit (Shallow d) = do
  pure $ "(Shallow " ++ show d ++ ")"
showImplicit (Deep f m r) = do
  m' <- showThunk m
  pure $ "(Deep " ++ show f ++ " " ++ m' ++ " "
                  ++ show r ++ ")"

instance Queue Implicit where
  empty = pure $ Shallow Zero
  snoc q y = snoc' q y
  uncons q =
    if isEmpty q
      then pure Nothing
      else do
        h <- head q
        t <- tail q
        pure $ Just (h, t)

instance BoundedQueue Implicit where
  qcost _ (Snoc _) = 2
  qcost _ Uncons = 2

instance MemoryCell m a => MemoryCell m (Digit a) where
  prettyCell Zero = pure $ mkMCell "Zero" []
  prettyCell (One a) = do
    a' <- prettyCell a
    pure $ mkMCell "One" [a']
  prettyCell (Two a b) = do
    a' <- prettyCell a
    b' <- prettyCell b
    pure $ mkMCell "Two" [a', b']

instance (MonadMemory m, MemoryCell m a) => MemoryCell m (ILazyCon m a) where
  prettyCell (IPure x) = do
    x' <- prettyCell x
    pure $ mkMCell "IPure" [x']
  prettyCell (ISnoc t _) = do
    t' <- prettyCell t
    pure $ mkMCell "ISnoc" [t']
  prettyCell (ITail q) = do
    q' <- prettyCell q
    pure $ mkMCell "ITail" [q']

instance (MonadMemory m, MemoryCell m a) => MemoryCell m (Implicit a m) where
  prettyCell (Shallow d) = do
    d' <- prettyCell d
    pure $ mkMCell "Shallow" [d']
  prettyCell (Deep f m r) = do
    f' <- prettyCell f
    m' <- prettyCell m
    r' <- prettyCell r
    pure $ mkMCell "Deep" [f', m', r']

instance Pretty a => MemoryStructure (Implicit (PrettyCell a)) where
  prettyStructure = prettyCell