packages feed

logict-sequence-0.2: bench/logic-performance.hs

-------------------------------------------------------------------------
-- |
-- Copyright   : (c) 2016-2021 Koji Miyazato,
--               (c) 2021 Jason Dagit
-- License     : MIT
--
-- Port of a benchmark script by its author, originally written for
-- <https://gitlab.com/viercc/ListT>
-- 
-- Performance Tests on various MonadLogic implementations.
-- (1) []
-- (2) Data.Sequence.Seq
-- (3) ListT m
-- (4) LogicT m
-- (5) SeqT m
-------------------------------------------------------------------------

{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Main(main) where

import Control.Applicative
import Control.Monad.Trans
import Control.Monad.Identity
import Control.Monad.ST
import qualified Data.Foldable as F
import Data.Monoid (Alt (..))
import Data.Tree ( Tree(..) )

import Control.Monad.Logic (MonadLogic (..))
import qualified Control.Monad.Logic as Orig
import qualified Control.Monad.Logic.Sequence as L
import Data.Sequence (Seq, ViewL (..))
import qualified Data.Sequence as Seq
import ListT

import Gauge.Main
------------------------------------------------------------------------
-- Orphan instances

-- make Seq an instance of MonadLogic using viewl
instance MonadLogic Seq where
  msplit s = case Seq.viewl s of
    EmptyL -> return Nothing
    a :< as -> return (Just (a, as))

#if !MIN_VERSION_list_t(1,0,5)
instance Monad m => MonadLogic (ListT m) where
  msplit = lift . uncons
  interleave as bs = ListT $ uncons as >>= \case
    Nothing -> uncons bs
    Just (a,as') -> pure (Just (a, interleave bs as'))
#endif

------------------------------------------------------------------------
-- how to run MonadLogic instances

-- | [a].
runList :: [a] -> [a]
runList = id

-- | ListT. Most basic Backtracking monad.
runListT_I :: ListT Identity a -> [a]
runListT_I = runIdentity . toList

-- | ListT ST.
runListT_S :: (forall s. ListT (ST s) a) -> [a]
runListT_S ma = runST (toList ma)

-- | Seq. Asymptotically fast but constants are large. No transformer version.
runContainersSeq :: Seq a -> [a]
runContainersSeq = F.toList

-- | Logic. Very fast Monad/MonadPlus operation. Slow interleave.
runLogicT_I :: Orig.Logic a -> [a]
runLogicT_I = Orig.observeAll

runLogicT_S :: (forall s. Orig.LogicT (ST s) a) -> [a]
runLogicT_S ma = runST (Orig.observeAllT ma)

-- | SeqT from logict-sequence
runLSeqT_I :: L.Seq a -> [a]
runLSeqT_I = L.observeAll

runLSeqT_S :: (forall s. L.SeqT (ST s) a) -> [a]
runLSeqT_S ma = runST (L.observeAllT ma)

------------------------------------------------------------------------
-- Measured codes
heavy_right_assoc :: (MonadLogic m) => Int -> m ()
heavy_right_assoc n = heavy >>= guard
  where
    heavy = foldr (<|>) (return True) (replicate (n-1) (return False))
{-# INLINE heavy_right_assoc #-}

heavy_left_assoc :: (MonadLogic m) => Int -> m ()
heavy_left_assoc n = heavy >>= guard
  where
    falses = F.foldl (<|>) empty (replicate n (return False))
    heavy = falses <|> return True
{-# INLINE heavy_left_assoc #-}

heavy_treelike :: (MonadLogic m) => Int -> m ()
heavy_treelike n = go n True >>= guard
  where
    go k b
      | k <= 1 = return b
      | otherwise =
        let r = k `div` 2
            l = k - r
         in go l False <|> go r b
{-# INLINE heavy_treelike #-}

heavy_interleave :: (MonadLogic m) => Int -> m ()
heavy_interleave n = interleave heavy heavy >>= guard
  where
    m = n `div` 2
    heavy = foldr (<|>) (return True) (replicate (m-1) (return False))
{-# INLINE heavy_interleave #-}

heavy_fairbind :: (MonadLogic m) => Int -> m ()
heavy_fairbind n = heavy >>= guard
  where
    m = n `div` 5
    as = [1 .. 5] :: [Int]
    heavy =
      choose as >>- \k ->
        foldr (<|>) (return (k == 5)) (replicate m (return False))
{-# INLINE heavy_fairbind #-}

choose :: (Foldable t, Alternative f) => t a -> f a
choose = getAlt . foldMap (Alt . pure)
-- Copied from post by u/dagit on:
--   https://www.reddit.com/r/haskell/comments/onwfr2/logictsequence_logict_empowered_by_reflection/
makeTree :: Int -> Tree Int
makeTree n = go 0
  where
    go k = Node k (go <$> filter (< n) [k * 3 + 1, k * 3 + 2, k * 3 + 3])

bfs :: MonadLogic m => Tree a -> m a
bfs t = go (pure t)
  where
    go q = do
      mb <- msplit q
      case mb of
        Nothing -> empty
        Just (m, qs) -> pure (rootLabel m) <|> go (qs <|> choose (subForest m))
{-# INLINE bfs #-}

heavy_bfs :: (MonadLogic m) => Int -> m ()
heavy_bfs n = bfs (makeTree n) >>= \k -> guard (k == n)
{-# INLINE heavy_bfs #-}

------------------------------------------------------------------------
-- Benchmark definition
main :: IO ()
main =
  defaultMain
    [ bgroup "right_assoc" (forEachMonad heavy_right_assoc),
      bgroup "left_assoc" (forEachMonad heavy_left_assoc),
      bgroup "treelike" (forEachMonad heavy_treelike),
      bgroup "interleave" (forEachMonad heavy_interleave),
      bgroup "fairbind" (forEachMonad heavy_fairbind),
      bgroup "bfs" (forEachMonad heavy_bfs)
    ]

forEachMonad :: (forall m. (MonadLogic m) => Int -> m ()) -> [Benchmark]
forEachMonad targetLogic =
  [ bgroup "[]" (forEachSize $ nf (runList . targetLogic)),
    bgroup "Seq" (forEachSize $ nf (runContainersSeq . targetLogic)),
    bgroup "ListT_I" (forEachSize $ nf (runListT_I . targetLogic)),
    bgroup "ListT_S" (forEachSize $ nf (\n -> runListT_S (targetLogic n))),
    bgroup "LogicT_I" (forEachSize $ nf (runLogicT_I . targetLogic)),
    bgroup "LogicT_S" (forEachSize $ nf (\n -> runLogicT_S (targetLogic n))),
    bgroup "L.SeqT_I" (forEachSize $ nf (\n -> runLSeqT_I (targetLogic n))),
    bgroup "L.SeqT_S" (forEachSize $ nf (\n -> runLSeqT_S (targetLogic n)))
  ]
{-# INLINE forEachMonad #-}

forEachSize :: (Int -> Benchmarkable) -> [Benchmark]
forEachSize f =
  map (\n -> bench (show n) $ f n) [100, 300, 1000, 3000, 10000]