Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Control/Monad/Logic/Sequence.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module Control.Monad.Logic.Sequence
, observeMany
, observeT
, observe
, chooseSeqT
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better name for this? The only reason we provide it as a "primitive" is so we can write it in a way that take advantage of the internals, but maybe choose (without the SeqT) is a better name?

, module Control.Monad
, module Control.Monad.Trans
)
Expand Down
165 changes: 128 additions & 37 deletions src/Control/Monad/Logic/Sequence/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#endif

#if __GLASGOW_HASKELL__ >= 704
{-# LANGUAGE Safe #-}
{-# LANGUAGE Trustworthy #-}
#endif
{-# OPTIONS_HADDOCK not-home #-}

Expand Down Expand Up @@ -49,14 +49,16 @@ module Control.Monad.Logic.Sequence.Internal
, hoistPostUnexposed
, toLogicT
, fromLogicT
, chooseStreamM
, chooseSeqT
, cons
, consM
, choose
, chooseM
)
where

import Control.Applicative
import Control.Applicative as A
import Control.Monad
import qualified Control.Monad.Fail as Fail
import Control.Monad.Identity (Identity(..))
Expand Down Expand Up @@ -137,7 +139,7 @@ instance (Show1 m, Monad m) => Show1 (View m) where
-- it's really defined this way! However, the real implementation is different,
-- so as to be more efficient in the face of deeply left-associated `<|>` or
-- `mplus` applications.
newtype SeqT m a = SeqT (Queue (m (View m a)))
newtype SeqT m a = SeqT { runSeqT :: (Queue (m (View m a))) }
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I ended up using runSeqT in code, but I keep adding it because generally I feel like it's nice to have named projections like this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't unSeqT be more idiomatic these days? I'm not sure there's much point adding a record selector like that for an internal type, but it's your call of course.


#ifdef USE_PATTERN_SYNONYMS
pattern MkSeqT :: Monad m => m (View m a) -> SeqT m a
Expand Down Expand Up @@ -168,16 +170,44 @@ type Seq = SeqT Identity

fromView :: m (View m a) -> SeqT m a
fromView = SeqT . S.singleton
{-# INLINE fromView #-}
{-# INLINE[0] fromView #-}

toView :: Monad m => SeqT m a -> m (View m a)
toView (SeqT s) = case S.viewl s of
S.EmptyL -> return Empty
h S.:< t -> h >>= \x -> case x of
Empty -> toView (SeqT t)
hi :< SeqT ti -> return (hi :< SeqT (ti S.>< t))
{-# INLINEABLE toView #-}
{-# SPECIALIZE INLINE toView :: Seq a -> Identity (View Identity a) #-}
toView (SeqT s0) = go s0 where
go s = case S.viewl s of
S.EmptyL -> return Empty
h S.:< t -> h >>= \x -> case x of
Empty -> go t
hi :< SeqT ti -> return (hi :< SeqT (ti S.>< t))
{-# INLINE[0] toView #-}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's go for? Can you just use INLINABLE[0] with the original?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get a little fuzzy on which things I individually benchmarked, but I would have sworn writing it this way generates slightly faster code and it's just a difference of one line, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the least of my worries.

data Step s a = Done | Yield a s | Skip s
data StreamM m a = forall s. StreamM (s -> m (Step s a)) s

stream :: Monad m => SeqT m a -> StreamM m a
stream m = StreamM next m where
{-# INLINE next #-}
next s = do
x <- toView s
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My kingdom for an <$>....

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better this way. fmap would introduce laziness I'm guessing we don't want.

case x of
Empty -> return Done
h :< t -> return (Yield h t)
{-# INLINE[1] stream #-}

unstream :: Monad m => StreamM m a -> SeqT m a
unstream (StreamM next s0) = fromView (unfold s0)
where
unfold s = do
v <- next s
case v of
Done -> return Empty
Skip xs -> unfold xs
Yield x xs -> return (x :< fromView (unfold xs))
{-# INLINE[1] unstream #-}

{-# RULES
"stream-unstream" [2] forall s. stream (unstream s) = s;
#-}

{-
Theorem: toView . fromView = id
Expand All @@ -203,6 +233,16 @@ m >>= \x -> case x of
= m
-}

newtype Fix1 f a = In1 { out1 :: f (Fix1 f a) a }

chooseStreamM :: (F.Foldable f, Monad m) => f a -> StreamM m a
chooseStreamM = StreamM (return . out1) . F.foldr (\a b -> In1 (Yield a b)) (In1 Done)
{-# INLINE[1] chooseStreamM #-}

chooseSeqT :: (F.Foldable f, Monad m) => f a -> SeqT m a
chooseSeqT = unstream . chooseStreamM
{-# INLINE[3] chooseSeqT #-}

instance (Show (m (View m a)), Monad m) => Show (SeqT m a) where
showsPrec d s = showParen (d > app_prec) $
showString "MkSeqT " . showsPrec (app_prec + 1) (toView s)
Expand Down Expand Up @@ -238,13 +278,26 @@ instance (Show1 m, Monad m) => Show1 (SeqT m) where
single :: Monad m => a -> m (View m a)
single a = return (a :< mzero)
{-# INLINE single #-}
{-# SPECIALIZE INLINE single :: a -> Identity (View Identity a) #-}

instance Monad m => Functor (SeqT m) where
{-# INLINEABLE fmap #-}
fmap f (SeqT q) = SeqT $ fmap (liftM (fmap f)) q
{-# INLINABLE (<$) #-}
x <$ SeqT q = SeqT $ fmap (liftM (x <$)) q
fmap = fmapSeqT

fmapSeqT :: Monad m => (a -> b) -> SeqT m a -> SeqT m b
fmapSeqT f s = unstream (fmap_s f (stream s))
{-# INLINEABLE [3] fmapSeqT #-}

fmap_s :: Monad m => (a -> b) -> StreamM m a -> StreamM m b
fmap_s f (StreamM next_a a0) = StreamM next a0
where
{-# INLINE next #-}
next a = do
x <- next_a a
case x of
Done -> return Done
Skip s -> return (Skip s)
Yield y ys -> return (Yield (f y) ys)
{-# INLINEABLE [1] fmap_s #-}

instance Monad m => Applicative (SeqT m) where
{-# INLINE pure #-}
Expand All @@ -260,16 +313,35 @@ instance Monad m => Applicative (SeqT m) where
instance Monad m => Alternative (SeqT m) where
{-# INLINE empty #-}
{-# INLINEABLE (<|>) #-}
{-# SPECIALIZE INLINE (<|>) :: Seq a -> Seq a -> Seq a #-}
empty = SeqT S.empty
m <|> n = fromView (altView m n)
(<|>) = alt

altView :: Monad m => SeqT m a -> SeqT m a -> m (View m a)
altView (toView -> m) n = m >>= \x -> case x of
Empty -> toView n
h :< t -> return (h :< cat t n)
where cat (SeqT l) (SeqT r) = SeqT (l S.>< r)
{-# INLINE altView #-}
alt :: Monad m => SeqT m a -> SeqT m a -> SeqT m a
alt a b = unstream (alt_s (stream a) (stream b))
{-# INLINE[3] alt #-}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm quite concerned that this approach may cause asymptotic performance trouble in the face of lots of <|> (associated various ways, and especially recursive).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm curious about that case too. My simple benchmark does an iterative deepening search. And then I convert the generated values to a streamly stream. That conversion can either be done using something like observeAll or msplit with unfoldrM. It's the program where I originally discovered the quadratic slowdown in LogicT. So I've been using that to get a sense of whether our <|> implementation is dealing well with associativity.

For the version here there's no runtime difference between using msplit vs. observeAll for SeqT. Have you looked in bench? Do any of those cover the case you are imagining? It's a little bit hard to get a graph from those benchmarks so I haven't been running those between changes (also they take forever to run). But I would like to be able to answer question like this using benchmarks as a sort of ground truth on runtime.


alt_s :: Monad m => StreamM m a -> StreamM m a -> StreamM m a
alt_s (StreamM next_a a) (StreamM next_b b) = StreamM next (Just a, b)
where
{-# INLINE next #-}
next (Nothing, s_b) = do
x <- next_b s_b
return $ case x of
Done -> Done
Skip vs -> Skip (Nothing, vs)
Yield v vs -> Yield v (Nothing, vs)
next (Just s_a, s_b) = do
y <- next_a s_a
case y of
Done -> do
x <- next_b s_b
return $ case x of
Done -> Done
Skip vs -> Skip (Nothing, vs)
Yield v vs -> Yield v (Nothing, vs)
Skip vs -> return (Skip (Just vs, s_b))
Yield x xs -> return (Yield x (Just xs, s_b))
{-# INLINE[1] alt_s #-}

-- | @cons a s = pure a <|> s@
cons :: Monad m => a -> SeqT m a -> SeqT m a
Expand All @@ -284,30 +356,51 @@ consM m s = fromView (liftM (:< s) m)
instance Monad m => Monad (SeqT m) where
{-# INLINE return #-}
{-# INLINEABLE (>>=) #-}
{-# SPECIALIZE INLINE (>>=) :: Seq a -> (a -> Seq b) -> Seq b #-}
return = fromView . single
(toView -> m) >>= f = fromView $ m >>= \x -> case x of
Empty -> return Empty
h :< t -> f h `altView` (t >>= f)

{-# INLINEABLE (>>) #-}
(toView -> m) >> n = fromView $ m >>= \x -> case x of
Empty -> return Empty
_ :< t -> n `altView` (t >> n)

(>>=) = bind
#if !MIN_VERSION_base(4,13,0)
{-# INLINEABLE fail #-}
fail = Fail.fail
#endif

bind :: Monad m => SeqT m a -> (a -> SeqT m b) -> SeqT m b
bind m f = unstream (bind_s (stream m) (stream . f))
{-# INLINE[3] bind #-}

data BindSState a b m
= Boundary a
| forall s. InProgress a (s -> m (Step s b)) s

bind_s :: Monad m => StreamM m a -> (a -> StreamM m b) -> StreamM m b
bind_s (StreamM next_a a0) f = StreamM next (Boundary a0) where
next _ = return undefined
-- TODO: fixme: this is an infinite loop right now
{-
{-# INLINE next #-}
next (Boundary s_a) = do
x <- next_a s_a
return $ case x of
Yield a _ -> case f a of
StreamM next_fa fa0 -> Skip (InProgress s_a next_fa fa0)
Skip as -> Skip (Boundary as)
Done -> Done
next (InProgress s_a next_fa s_fa) = do
x <- next_fa s_fa
return $ case x of
Yield b bs -> Yield b (InProgress s_a next_fa bs)
Skip bs -> Skip (InProgress s_a next_fa bs)
Done -> Skip (Boundary s_a)
-}
{-# INLINE[1] bind_s #-}

instance Monad m => Fail.MonadFail (SeqT m) where
{-# INLINEABLE fail #-}
fail _ = SeqT S.empty

instance Monad m => MonadPlus (SeqT m) where
{-# INLINE mzero #-}
{-# INLINE mplus #-}
mzero = Control.Applicative.empty
mzero = A.empty
mplus = (<|>)

#if MIN_VERSION_base(4,9,0)
Expand All @@ -331,8 +424,7 @@ instance MonadTrans SeqT where
lift m = fromView (m >>= single)

instance Monad m => MonadLogic (SeqT m) where
{-# INLINE msplit #-}
{-# SPECIALIZE INLINE msplit :: Seq a -> Seq (Maybe (a, Seq a)) #-}
{-# INLINE[3] msplit #-}
msplit (toView -> m) = fromView $ do
r <- m
case r of
Expand Down Expand Up @@ -361,7 +453,6 @@ observeAllT (toView -> m) = m >>= go where
go (a :< t) = liftM (a:) (toView t >>= go)
go _ = return []
{-# INLINEABLE observeAllT #-}
{-# SPECIALIZE INLINE observeAllT :: Seq a -> Identity [a] #-}

observeT :: Monad m => SeqT m a -> m (Maybe a)
observeT (toView -> m) = m >>= go where
Expand Down