creditmonad-1.0.0: src/Test/Credit/Heap/LazyPairing.hs
{-# LANGUAGE GADTs #-}
module Test.Credit.Heap.LazyPairing where
import Prettyprinter (Pretty)
import Control.Monad.Credit
import Test.Credit
import Test.Credit.Heap.Base
-- Okasaki does not present an amortized analysis and instead merely conjectures
-- that they have O(log n) amortized cost for insert and splitMin (Section 6.5).
-- An amortized analysis in a sequential setting is given by Nipkow and Brinkop
-- in 'Amortized Complexity Verified' (2019), Section 8.
-- Below we generalize it to the persistent setting.
data LazyPairing a s
= Empty
| Heap Size a (LazyPairing a s) (PThunk s (LazyPairing a s))
-- ^ Changed from Okasaki is that we annotate the size of the thunk.
-- Invariant: For 'Heap sm x a m', we have either:
-- - 'a' is Empty and 'm' has (log2 sm) credits
-- - 'a' is not Empty and 'm' has (2 * log2 sm) credits
-- - Right before forcing, 'm' has (3 * log2 sm) credits
size :: LazyPairing a s -> Size
size Empty = 0
size (Heap sm _ a _) = 1 + sm + size a
data PLazyCon m a where
Em :: PLazyCon m (LazyPairing a m)
Link :: Ord a => Size -> LazyPairing a m -> LazyPairing a m -> Thunk m (PLazyCon m) (LazyPairing a m) -> PLazyCon m (LazyPairing a m)
-- ^ Merging 'h = Link(a, b, m)' costs one tick and performs two links, and assigns some credits to 'm'.
-- Because 'link a b' costs 'log2 (sa + sb)' credits, we have total costs of:
-- 2 + 2*log2 (sa + sb + sm) + 2*log2 (sa + sb) + 2*log2 sm
-- <= 6 * log2 sh (since sa + sb + sm <= sh)
instance MonadCredit m => HasStep (PLazyCon m) m where
step Em = pure Empty
step (Link sm a b m) = tick >> do -- 1
creditWith m (log2 sm) -- log2 sm
m <- force m -- free
ab <- link a b -- log2 (sa + sb)
link ab m -- log2 (sa + sb + sm)
type PThunk s = Thunk s (PLazyCon s)
data NEHeap s a = NEHeap Size a (LazyPairing a s) (PThunk s (LazyPairing a s))
-- | 'mergePairs' costs up to 'log2 (sz + sa)' credits
mergePairs :: MonadCredit m => Ord a => NEHeap m a -> LazyPairing a m -> m (LazyPairing a m)
mergePairs (NEHeap sm x Empty m) a = do
creditWith m (log2 sm)
pure $ Heap sm x a m
mergePairs (NEHeap sm x b m) a = do
t <- delay $ Link sm a b m
let sz = size a + size b + sm
creditWith t (log2 sz)
pure $ Heap sz x Empty t
-- | 'link' costs up to 'log2 (sz + sa) + 1' credits
link :: MonadCredit m => Ord a => LazyPairing a m -> LazyPairing a m -> m (LazyPairing a m)
link a Empty = pure a
link Empty b = pure b
link a@(Heap sa x a1 a2) b@(Heap sb y b1 b2)
| x <= y = mergePairs (NEHeap sa x a1 a2) b
| otherwise = mergePairs (NEHeap sb y b1 b2) a
instance Heap LazyPairing where
empty = pure Empty
insert x a = do
t <- delay $ Em
merge (Heap 0 x Empty t) a
-- | 'merge' costs '1 + log2 (sa + sb)' credits
merge a b = tick >> link a b
splitMin Empty = pure Nothing
splitMin (Heap sm x a m) = do
creditWith m (2 * log2 sm) -- in case 'a' is Empty
m <- force m
am <- merge a m
pure $ Just (x, am)
instance BoundedHeap LazyPairing where
hcost n (Insert _) = 1 + log2 (n + 1)
hcost n Merge = 1 + log2 (n + 1)
hcost n SplitMin = 1 + 3 * log2 (n + 1)
instance (MonadMemory m, MemoryCell m a) => MemoryCell m (PLazyCon m a) where
prettyCell Em = pure $ mkMCell "Empty" []
prettyCell (Link _ a b m) = do
a' <- prettyCell a
b' <- prettyCell b
m' <- prettyCell m
pure $ mkMCell "Link" [a', b', m']
instance (MonadMemory m, MemoryCell m a) => MemoryCell m (LazyPairing a m) where
prettyCell Empty = pure $ mkMCell "Empty" []
prettyCell (Heap sz x a m) = do
sz' <- prettyCell sz
x' <- prettyCell x
a' <- prettyCell a
m' <- prettyCell m
pure $ mkMCell "Heap" [sz', x', a', m']
instance Pretty a => MemoryStructure (LazyPairing (PrettyCell a)) where
prettyStructure = prettyCell