packages feed

creditmonad-1.0.0: src/Test/Credit/Sortable/Scheduled.hs

{-# LANGUAGE GADTs #-}

module Test.Credit.Sortable.Scheduled where

import Prettyprinter (Pretty)
import Control.Monad.Credit
import Test.Credit
import Test.Credit.Sortable.Base

rev :: MonadCredit m => [a] -> [a] -> m [a]
rev [] acc = pure acc
rev (x : xs) acc = tick >> rev xs (x : acc)

data Stream m a
  = SCons a (Stream m a)
  | SNil
  | SIndirect (Thunk m (Lazy m) (Stream m a))

indirect :: MonadCredit m => m (Stream m a) -> m (Stream m a)
indirect = fmap SIndirect . delay . Lazy

credit :: MonadCredit m => Credit -> Stream m a -> m ()
credit cr (SIndirect i) = creditWith i cr
credit _ _ = pure ()

-- | Smart destructor for streams, consuming one credit
smatch :: MonadCredit m => Stream m a -- ^ Scrutinee
       -> m b -- ^ Nil case
       -> (a -> Stream m a -> m b) -- ^ Cons case
       -> m b
smatch x nil cons = tick >> eval x
  where
    eval x = case x of
      SCons a as -> cons a as
      SNil -> nil
      SIndirect i -> force i >>= eval

streamToList :: MonadCredit m => Stream m a -> m [a]
streamToList xs = smatch xs
  (pure [])
  (\x xs' -> (x:) <$> streamToList xs')

type Schedule m a = [Stream m a]

data SMergeSort a m = SMergeSort Size [(Stream m a, Schedule m a)]

mrg :: MonadCredit m => Ord a => Stream m a -> Stream m a -> m (Stream m a)
mrg xs ys = indirect $ do
  smatch xs (pure ys) $ \x xs' ->
    smatch ys (pure xs) $ \y ys' -> do
      if x <= y
        then (SCons x) <$> mrg xs' ys
        else (SCons y) <$> mrg xs ys'

exec1 :: MonadCredit m => Schedule m a -> m (Schedule m a)
exec1 [] = pure []
exec1 (ds:sched) = credit 2 ds >> smatch ds
  (exec1 sched)
  (\_ xs -> pure $ xs : sched)

exec2 :: MonadCredit m => (Stream m a, Schedule m a) -> m (Stream m a, Schedule m a)
exec2 (xs, sched) = exec1 sched >>= exec1 >>= pure . (xs,)

execAll :: MonadCredit m => Schedule m a -> m ()
execAll [] = pure ()
execAll sched = exec1 sched >>= execAll

addSeg :: MonadCredit m => Ord a => Stream m a -> [(Stream m a, Schedule m a)] -> Size -> Schedule m a -> m [(Stream m a, Schedule m a)]
addSeg xs segs size rsched =
  if size `mod` 2 == 0
    then do
      sched <- rev rsched [] -- log2 size
      pure $ (xs, sched) : segs
    else do
      let ((xs', []) : segs') = segs
      xs'' <- mrg xs xs'
      addSeg xs'' segs' (size `div` 2) (xs'' : rsched)

mrgAll :: MonadCredit m => Ord a => Stream m a -> [(Stream m a, Schedule m a)] -> m (Stream m a)
mrgAll xs [] = pure xs
mrgAll xs ((xs', sched):segs) = do
  execAll sched -- total cost: 3 * linear size
  seg <- mrg xs xs'
  execAll [seg] -- total cost: 6 * linear size
  mrgAll seg segs

instance Sortable SMergeSort where
  empty = pure $ SMergeSort 0 []
  add x (SMergeSort size segs) = do
    segs' <- addSeg (SCons x SNil) segs size [] -- 1 * (log2 size + 1)
    segs'' <- mapM exec2 segs' -- 6 * (log2 size + 1)
    pure $ SMergeSort (size + 1) segs''
  sort (SMergeSort size segs) = do
    s <- mrgAll SNil segs -- 9 * (log2 size + 1)
    streamToList s -- linear size

instance BoundedSortable SMergeSort where
  scost n (Add _) = 7 * (log2 n + 1)
  scost n Sort = 10 * (linear n + 1)

instance (MonadMemory m, MemoryCell m a) => MemoryCell m (Stream m a) where
  prettyCell xs = mkMList <$> toList xs <*> toHole xs
    where
      toList SNil = pure $ []
      toList (SCons x xs) = (:) <$> prettyCell x <*> toList xs
      toList (SIndirect t) = pure $ []

      toHole SNil = pure $ Nothing
      toHole (SCons x xs) = toHole xs
      toHole (SIndirect t) = Just <$> prettyCell t

instance (MonadMemory m, MemoryCell m a) => MemoryCell m (SMergeSort a m) where
  prettyCell (SMergeSort size segs) = do
    size' <- prettyCell size
    segs' <- prettyCell segs
    pure $ mkMCell "SMergeSort" [size', segs']

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