HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Modulelogict-0.8.2.0Haskell2010

Control.Monad.Logic

Adapted from the paper Backtracking, Interleaving, and Terminating Monad Transformers by Oleg Kiselyov, Chung-chieh Shan, Daniel P. Friedman, Amr Sabry. Note that the paper uses MonadPlus vocabulary (mzero and mplus), while examples below prefer empty and <|> from Alternative.

  • 2 types
  • 13 values
  • Packagelogict-0.8.2.0
  • Exports15
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceLogic.hs

The Logic monad

6 declarations
typetype Logic = LogicT Identity
#

The basic Logic monad, for performing backtracking computations returning values (e.g. Logic a will return values of type a).

It's important to remember that Logic on its own is just a lawful list monad, behaving exactly as instance Monad []. One should explicitly use methods of MonadLogic such as (>>-) and interleave to get fair conjunction / disjunction. Note that usual lists have an instance of MonadLogic, so maybe you don't need Logic at all.

Technical perspective. Logic is a Boehm-Berarducci encoding of lists. Speaking plainly, its type is identical (up to Identity wrappers) to Data.List.foldr applied to a given list. And this list itself can be reconstructed by supplying (:) and [].

import Data.Functor.Identity

fromList :: [a] -> Logic a
fromList xs = LogicT $ \cons nil -> foldr cons nil xs

toList :: Logic a -> [a]
toList (LogicT fld) = runIdentity $ fld (\x (Identity xs) -> Identity (x : xs)) (Identity [])

Here is a systematic derivation of the isomorphism. We start with observing that [a] is isomorphic to a fix point of a non-recursive base algebra Fix (ListF a):

newtype Fix f = Fix (f (Fix f))
data ListF a r = ConsF a r | NilF deriving (Functor)

cata :: Functor f => (f r -> r) -> Fix f -> r
cata f = go where go (Fix x) = f (fmap go x)

from :: [a] -> Fix (ListF a)
from = foldr (\a acc -> Fix (ConsF a acc)) (Fix NilF)

to :: Fix (ListF a) -> [a]
to = cata (\case ConsF a r -> a : r; NilF -> [])

Further, Fix (ListF a) is isomorphic to Boehm-Berarducci encoding ListC a:

newtype ListC a = ListC (forall r. (ListF a r -> r) -> r)

from :: Fix (ListF a) -> ListC a
from xs = ListC (\f -> cata f xs)

to :: ListC a -> Fix (ListF a)
to (ListC f) = f Fix

Finally, ListF a r → r is isomorphic to a pair (a → r → r, r), so ListC is isomorphic to the Logic type modulo Identity wrappers:

newtype Logic a = Logic (forall r. (a -> r -> r) -> r -> r)

And wrapping every occurence of r into m gives us LogicT:

newtype LogicT m a = Logic (forall r. (a -> m r -> m r) -> m r -> m r)
valuelogic :: (forall r. (a -> r -> r) -> r -> r) -> Logic a
#

A smart constructor for Logic computations.

valuerunLogic :: Logic a -> (a -> r -> r) -> r -> r
#

Runs a Logic computation with the specified initial success and failure continuations.

Example1 expression
runLogic empty (+) 00
Example1 expression
runLogic (pure 5 <|> pure 3 <|> empty) (+) 08

When invoked with (:) and [] as arguments, reveals a half of the isomorphism between Logic and lists. See description of observeAll for the other half.

valueobserve :: Logic a -> a
#

Extracts the first result from a Logic computation, failing if there are no results.

Example1 expression
observe (pure 5 <|> pure 3 <|> empty)5
Example1 expression
observe empty*** Exception: No answer.

Since Logic is isomorphic to a list, observe is analogous to head.

valueobserveMany :: Int -> Logic a -> [a]
#

Extracts up to a given number of results from a Logic computation.

Example2 expressions
let nats = pure 0 <|> fmap (+ 1) natsobserveMany 5 nats[0,1,2,3,4]

Since Logic is isomorphic to a list, observeMany is analogous to take.

valueobserveAll :: Logic a -> [a]
#

Extracts all results from a Logic computation.

Example1 expression
observeAll (pure 5 <|> empty <|> empty <|> pure 3 <|> empty)[5,3]

observeAll reveals a half of the isomorphism between Logic and lists. See description of runLogic for the other half.

The LogicT monad transformer

9 declarations
newtypenewtype LogicT (m :: Type -> Type) a
#

A monad transformer for performing backtracking computations layered over another monad m.

When m is Identity, LogicT m becomes isomorphic to a list (see Logic). Thus LogicT m for non-trivial m can be imagined as a list, pattern matching on which causes monadic effects.

It's important to remember that LogicT on its own is just a lawful list monad transformer, adding a nondeterministic effect, and its Monad instance behaves just as instance Monad []:

Example3 expressions
:set -XOverloadedListsobserveMany 9 $ do {x <- [100,200] :: Logic Int; fmap (+x) [1..]}[101,102,103,104,105,106,107,108,109]observeMany 9 $ do {[100,200] >>= \x -> fmap (+x) [1..] :: Logic Int}[101,102,103,104,105,106,107,108,109]

One should explicitly use methods of MonadLogic such as (>>-) and interleave to get fair conjunction / disjunction:

Example1 expression
observeMany 9 $ do {[100,200] >>- \x -> fmap (+x) [1..] :: Logic Int}[101,201,102,202,103,203,104,204,105]

Constructors

Instances28MonadTrans, MonadError, MonadReader, MonadState, IsList, Eq, …
valuerunLogicT :: LogicT m a -> (a -> m r -> m r) -> m r -> m r
#

Runs a LogicT computation with the specified initial success and failure continuations.

The second argument ("success continuation") takes one result of the LogicT computation and the monad to run for any subsequent matches.

The third argument ("failure continuation") is called when the LogicT cannot produce any more results.

For example:

Example6 expressions
yieldWords = foldr ((<|>) . pure) emptyshowEach wrd nxt = putStrLn wrd >> nxtrunLogicT (yieldWords ["foo", "bar"]) showEach (putStrLn "none!")foobarnone!runLogicT (yieldWords []) showEach (putStrLn "none!")none!showFirst wrd _ = putStrLn wrdrunLogicT (yieldWords ["foo", "bar"]) showFirst (putStrLn "none!")foo
valueobserveAllT :: Applicative m => LogicT m a -> m [a]
#

Extracts all results from a LogicT computation, unless blocked by the underlying monad.

For example, given

Example1 expression
let nats = pure 0 <|> fmap (+ 1) nats

some monads (like Identity, Reader, Control.Monad.Writer.Writer, and Control.Monad.State.State) will be productive:

Example1 expression
take 5 $ runIdentity (observeAllT nats)[0,1,2,3,4]

but others (like ExceptT, and ContT) will not:

Example1 expression
take 20 <$> runExcept (observeAllT nats)

In general, if the underlying monad manages control flow then observeAllT may be unproductive under infinite branching, and observeManyT should be used instead.

valuehoistLogicT
  1. :: (Applicative m, Monad n)
  2. => forall x. m x -> n x
  3. -> LogicT m a
  4. -> LogicT n a
#

Convert a LogicT computation from one underlying monad to another. For example,

hoistLogicT lift :: LogicT m a -> LogicT (StateT m) a

The first argument should be a monad morphism. to produce sensible results.