From 6e284015a93e0686fcf8dc9b4fd7a89fe942e0b1 Mon Sep 17 00:00:00 2001
From: Christian Gram Kalhauge <chrg@dtu.dk>
Date: Tue, 5 Mar 2024 10:29:13 +0100
Subject: [PATCH] Add addaptive algorithm

---
 rtree/src/Control/Monad/IRTree.hs             | 183 ++++++++----------
 rtree/src/Control/Monad/RTree.hs              |  30 ++-
 rtree/src/Data/RPath.hs                       |  53 ++++-
 rtree/test/expected/double-let-expr-ired      |   6 +
 rtree/test/expected/double-let-expr-ireda     |   4 +
 .../expected/double-overloading-let-expr-ired |   6 +
 .../double-overloading-let-expr-ireda         |   4 +
 rtree/test/expected/small-let-expr-ired       |   5 +
 rtree/test/expected/small-let-expr-ireda      |   3 +
 rtree/test/expected/small-opr-expr-ired       |   4 +
 rtree/test/expected/small-opr-expr-ireda      |   2 +
 rtree/test/src/Control/Monad/IRTreeSpec.hs    | 137 +++++++++----
 rtree/test/src/Control/Monad/RTreeSpec.hs     |   2 +-
 13 files changed, 274 insertions(+), 165 deletions(-)
 create mode 100644 rtree/test/expected/double-let-expr-ired
 create mode 100644 rtree/test/expected/double-let-expr-ireda
 create mode 100644 rtree/test/expected/double-overloading-let-expr-ired
 create mode 100644 rtree/test/expected/double-overloading-let-expr-ireda
 create mode 100644 rtree/test/expected/small-let-expr-ired
 create mode 100644 rtree/test/expected/small-let-expr-ireda
 create mode 100644 rtree/test/expected/small-opr-expr-ired
 create mode 100644 rtree/test/expected/small-opr-expr-ireda

diff --git a/rtree/src/Control/Monad/IRTree.hs b/rtree/src/Control/Monad/IRTree.hs
index 517b8c2..706c9db 100644
--- a/rtree/src/Control/Monad/IRTree.hs
+++ b/rtree/src/Control/Monad/IRTree.hs
@@ -1,9 +1,12 @@
+{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE BlockArguments #-}
 {-# LANGUAGE DerivingVia #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeOperators #-}
 
 module Control.Monad.IRTree (
   -- * IRTree
@@ -11,16 +14,18 @@ module Control.Monad.IRTree (
   extract,
   probe,
   reduce,
-  reduceExp,
-  reduceFib,
+  reduceAdaptive,
 
   -- * IRTreeT
   IRTreeT,
   probeT,
   extractT,
   reduceT,
-  reduceFibT,
-  reduceExpT,
+  reduceAdaptiveT,
+
+  -- * helpers
+  binsearch,
+  expsearch,
 
   -- * Re-exports
   module Control.Monad.Reader,
@@ -32,6 +37,7 @@ import Control.Monad.Reader
 import Control.Monad.Reduce
 import Data.Bits
 import Data.Foldable
+import Data.Function
 import Data.Functor
 import Data.Functor.Identity
 import Data.RPath
@@ -56,123 +62,104 @@ extractT :: (Functor m) => IRTreeT l m i -> m i
 extractT (IRTreeT m) = fmap (\(i, _, _) -> i) (runRWST m "" 0)
 {-# INLINE extractT #-}
 
-probe :: IRTree l i -> RPath -> (i, [(Bool, l)])
+probe :: IRTree l i -> RPath -> (i, [AnnotatedChoice l], Int)
 probe t pth = runIdentity $ probeT t pth
 {-# INLINE probe #-}
 
-probeT :: (Functor m) => IRTreeT l m i -> RPath -> m (i, [(Bool, l)])
+{- | Probe the IRTree at a path RPath, and return the input, a list of annotated choices,
+and the number of guesses made (can be negative to indicate that not all choices in
+the path was used.
+-}
+probeT :: (Functor m) => IRTreeT l m i -> RPath -> m (i, [AnnotatedChoice l], Int)
 probeT (IRTreeT m) pth =
-  runRWST m pth 0 <&> \(i, _, l) ->
-    (i, zip (toChoiceList pth <> repeat False) (appEndo l []))
+  runRWST m pth 0 <&> \(i, x, l) ->
+    (i, annotateChoices pth (appEndo l []), x - numberOfChoices pth)
 {-# INLINE probeT #-}
 
 reduce
-  :: (Monad m)
-  => ([(Bool, l)] -> i -> m Bool)
+  :: (Monad m, m ~ IO, Show l)
+  => ([AnnotatedChoice l] -> i -> m Bool)
   -> IRTree l i
-  -> m (Maybe i)
+  -> m i
 reduce = reduceT (pure . runIdentity)
 {-# INLINE reduce #-}
 
 -- | Interpreted reduction with an m base monad
 reduceT
-  :: (Monad m, Functor t)
+  :: (Monad m, Functor t, m ~ IO, Show l)
   => (forall a. t a -> m a)
-  -> ([(Bool, l)] -> i -> m Bool)
+  -> ([AnnotatedChoice l] -> i -> m Bool)
   -> IRTreeT l t i
-  -> m (Maybe i)
+  -> m i
 reduceT lift_ p rt = do
-  (i, l) <- lift_ (probeT rt "")
-  t <- p l i
-  if t
-    then Just <$> go Seq.empty
-    else pure Nothing
- where
-  go pth = do
+  Seq.empty & fix \rec sq -> do
     -- Try to run the true branch.
-    let pth' = fromChoiceList (toList (pth Seq.|> True))
-    (i, l) <- lift_ $ probeT rt pth'
-    if length l >= numberOfChoices pth'
-      then do
-        t <- p l i
-        go (pth Seq.|> t)
-      else pure i
+    (i, l, left) <- _probe (sq Seq.|> True)
+    p l i >>= \case
+      -- If predicate is true, and there is choices left
+      True | left > 0 -> rec (sq Seq.|> True)
+      -- If predicate is false (and stable)
+      False | left >= 0 -> rec (sq Seq.|> False)
+      _ow -> pure i
+ where
+  _probe sq = lift_ . probeT rt . fromChoiceList $ toList sq
 {-# INLINE reduceT #-}
 
-reduceExp
-  :: (Monad m)
-  => ([(Bool, l)] -> i -> m Bool)
-  -> IRTree l i
-  -> m (Maybe i)
-reduceExp = reduceExpT (pure . runIdentity)
-{-# INLINE reduceExp #-}
-
-reduceFib
-  :: (Monad m)
-  => ([(Bool, l)] -> i -> m Bool)
+reduceAdaptive
+  :: (Monad m, m ~ IO, Show i, Show l)
+  => ([AnnotatedChoice l] -> i -> m Bool)
   -> IRTree l i
-  -> m (Maybe i)
-reduceFib = reduceFibT (pure . runIdentity)
-{-# INLINE reduceFib #-}
+  -> m i
+reduceAdaptive = reduceAdaptiveT (pure . runIdentity)
+{-# INLINE reduceAdaptive #-}
 
--- | Interpreted reduction with an m base monad, and running in expoential mode.
-reduceExpT
-  :: (Monad m, Functor t)
+-- | Interpreted reduction with an m base monad, but using exponential search.
+reduceAdaptiveT
+  :: (Monad m, Functor t, IO ~ m, Show i, Show l)
   => (forall a. t a -> m a)
   -- ^ a lift of monad m into t (normally @id@ or @lift@)
-  -> ([(Bool, l)] -> i -> m Bool)
+  -> ([AnnotatedChoice l] -> i -> m Bool)
   -> IRTreeT l t i
-  -> m (Maybe i)
-reduceExpT lift_ p rt = do
-  (i, l) <- lift_ (probeT rt "")
-  t <- p l i
-  if t
-    then Just <$> go 0 Seq.empty
-    else pure Nothing
+  -> m i
+reduceAdaptiveT lift_ p rt = do
+  Seq.empty & fix \rec !sq -> do
+    (d', c) <- expsearch \d -> do
+      let sq' = sq <> Seq.replicate d True
+      (i, l, left) <- _probe sq'
+      t <- p l i
+      pure (t, left)
+    if c
+      then rec (sq <> Seq.replicate d' True <> Seq.singleton False)
+      else do
+        (i, _, _) <- _probe (sq <> Seq.replicate d' True)
+        pure i
  where
-  go n sq = do
-    let depth = shiftL 1 n
-    let sq' = sq <> Seq.replicate depth True
-    let pth' = fromChoiceList (toList sq')
-    (i, l) <- lift_ (probeT rt pth')
-    if length l >= numberOfChoices pth' - depth + 1
-      then do
-        t <- p l i
-        if t
-          then go (n + 1) sq'
-          else case n of
-            0 -> go 0 (sq Seq.|> False)
-            n' -> go (n' - 1) sq
-      else pure i
-{-# INLINE reduceExpT #-}
-
--- | Interpreted reduction with an m base monad, and running in fibonacci mode.
-reduceFibT
-  :: (Monad m, Functor t)
-  => (forall a. t a -> m a)
-  -- ^ a lift of monad m into t (normally @id@ or @lift@)
-  -> ([(Bool, l)] -> i -> m Bool)
-  -> IRTreeT l t i
-  -> m (Maybe i)
-reduceFibT lift_ p rt = do
-  (i, l) <- lift_ (probeT rt "")
-  t <- p l i
-  if t
-    then Just <$> go 1 1 Seq.empty
-    else pure Nothing
+  _probe sq = lift_ . probeT rt . fromChoiceList $ toList sq
+
+expsearch :: (Monad m) => (Int -> m (Bool, Int)) -> m (Int, Bool)
+expsearch p =
+  1 & fix \rec !d -> do
+    p d >>= \case
+      (True, left)
+        | left < 0 -> pure (d + left, False)
+        | otherwise -> rec (d `shiftL` 1)
+      (False, left) -> do
+        d' <-
+          binsearch
+            (fmap (fmap fst) p)
+            (d `shiftR` 1)
+            (min d (d + left))
+        pure (d', d' < d + left - 1)
+
+binsearch :: (Monad m) => (Int -> m Bool) -> Int -> Int -> m Int
+binsearch p = go
  where
-  go n m sq = do
-    let depth = m
-    let sq' = sq <> Seq.replicate depth True
-    let pth' = fromChoiceList (toList sq')
-    (i, l) <- lift_ (probeT rt pth')
-    if length l >= numberOfChoices pth' - depth + 1
-      then do
-        t <- p l i
-        if t
-          then go m (n + m) sq'
-          else case m of
-            1 -> go 1 1 (sq Seq.|> False)
-            m' -> go (m' - n) n sq
-      else pure i
-{-# INLINE reduceFibT #-}
+  go !m !h
+    | m >= pivot = do
+        pure m
+    | otherwise = do
+        p pivot >>= \case
+          True -> go pivot h
+          False -> go m pivot
+   where
+    pivot = (m + h) `shiftR` 1
diff --git a/rtree/src/Control/Monad/RTree.hs b/rtree/src/Control/Monad/RTree.hs
index f93cec6..2cf8793 100644
--- a/rtree/src/Control/Monad/RTree.hs
+++ b/rtree/src/Control/Monad/RTree.hs
@@ -33,12 +33,9 @@ module Control.Monad.RTree (
   module Data.RPath,
 ) where
 
-import Control.Applicative
 import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.Reduce
-import Control.Monad.Trans
-import Control.Monad.Trans.Maybe (MaybeT (runMaybeT))
 import Data.Foldable
 import Data.Foldable.WithIndex
 import Data.Function ((&))
@@ -123,15 +120,15 @@ reduce
   :: (Monad m)
   => (i -> m Bool)
   -> RTree l i
-  -> m (Maybe i)
-reduce p = runMaybeT . checkgo
+  -> m i
+reduce p = go
  where
-  checkgo r = do
-    t <- lift $ p (extract r)
-    guard t *> go r
   go = \case
     Done i -> pure i
-    Split _ lhs rhs -> checkgo lhs <|> go rhs
+    Split _ lhs rhs -> do
+      p (extract lhs) >>= \case
+        True -> go lhs
+        False -> go rhs
 {-# INLINE reduce #-}
 
 -- | An RTreeT Node
@@ -180,14 +177,11 @@ reduceT
   -> (i -> n Bool)
   -> RTreeT l m i
   -> n i
-reduceT lift_ p = checkgo
- where
-  checkgo (RTreeT r) = do
-    r' <- lift_ r
-    t <- p =<< lift_ (extractN r')
-    unless t mzero
-    go r'
-  go = \case
+reduceT lift_ p = fix \rec x ->
+  lift_ (unRTreeT x) >>= \case
     DoneN i -> pure i
-    SplitN _ lhs rhs -> checkgo lhs <|> (lift_ (unRTreeT rhs) >>= go)
+    SplitN _ lhs rhs -> do
+      lift_ (extractT lhs) >>= p >>= \case
+        True -> rec lhs
+        False -> rec rhs
 {-# INLINE reduceT #-}
diff --git a/rtree/src/Data/RPath.hs b/rtree/src/Data/RPath.hs
index e4de0af..0a58763 100644
--- a/rtree/src/Data/RPath.hs
+++ b/rtree/src/Data/RPath.hs
@@ -1,3 +1,6 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+
 module Data.RPath (
   -- * RPath
   RPath,
@@ -6,6 +9,13 @@ module Data.RPath (
   numberOfChoices,
   indexChoice,
 
+  -- * Annotated
+  annotateChoices,
+  AnnotatedChoice (..),
+  Choice (..),
+  toDecision,
+  numberOfUndecided,
+
   -- * As a predicate
   toPredicate,
   toPredicateDebug,
@@ -16,6 +26,8 @@ module Data.RPath (
 ) where
 
 import Data.Bool
+import Data.Foldable
+import Data.Function
 import Data.IORef (atomicModifyIORef, newIORef)
 import Data.Maybe
 import Data.String
@@ -32,16 +44,16 @@ the rpath points to.
 -}
 toPredicate :: RPath -> IO (i -> IO Bool)
 toPredicate rp = do
-  idx <- newIORef (-1)
+  idx <- newIORef 0
   pure . const $ do
     idx' <- atomicModifyIORef idx (\n -> (n + 1, n))
     pure $ indexChoice rp idx'
 
 -- | Like @toPredicate@, but with debugging information
-toPredicateDebug :: (Show i) => RPath -> IO (i -> IO Bool)
+toPredicateDebug :: (Show i, Show l) => RPath -> IO ([AnnotatedChoice l] -> i -> IO Bool)
 toPredicateDebug rp = do
-  idx <- newIORef (-1)
-  pure $ \i -> do
+  idx <- newIORef 0
+  pure $ \_ i -> do
     idx' <- atomicModifyIORef idx (\n -> (n + 1, n))
     putStr (debugShowWithDepth rp idx')
     putStr ": "
@@ -52,9 +64,7 @@ toPredicateDebug rp = do
 default to false. Indexing with a negative number gives True.
 -}
 indexChoice :: RPath -> Int -> Bool
-indexChoice (RPath v) idx
-  | idx < 0 = True
-  | otherwise = fromMaybe False (v VU.!? idx)
+indexChoice (RPath v) idx = fromMaybe False (v VU.!? idx)
 {-# INLINE indexChoice #-}
 
 -- | Get the number of choices in the RPath.
@@ -77,6 +87,18 @@ debugShowWithDepth rp i =
   (map (bool '0' '1') . take (i + 1) . toChoiceList $ rp)
     <> replicate (numberOfChoices rp - i - 1) '*'
 
+-- | Get a list of annotated choices.
+annotateChoices :: RPath -> [l] -> [AnnotatedChoice l]
+annotateChoices rp =
+  zipWith
+    AnnotatedChoice
+    (map (bool No Yes) (toChoiceList rp) <> repeat Undecided)
+
+numberOfUndecided :: [AnnotatedChoice l] -> Int
+numberOfUndecided =
+  0 & foldl' \a ->
+    (\case Undecided -> 1 + a; _ow -> a) . choice
+
 debugShow :: RPath -> String
 debugShow = map (bool '0' '1') . toChoiceList
 
@@ -88,3 +110,20 @@ instance Read RPath where
 
 instance IsString RPath where
   fromString = fromChoiceList . map (== '1')
+
+-- | A Choice
+data Choice = Yes | No | Undecided
+  deriving (Show, Read, Eq, Ord, Enum, Bounded)
+
+-- | An annotated choice
+data AnnotatedChoice l = AnnotatedChoice
+  { choice :: !Choice
+  , label :: l
+  }
+  deriving (Show, Eq, Ord)
+
+toDecision :: Choice -> Maybe Bool
+toDecision = \case
+  Yes -> Just True
+  No -> Just False
+  Undecided -> Nothing
diff --git a/rtree/test/expected/double-let-expr-ired b/rtree/test/expected/double-let-expr-ired
new file mode 100644
index 0000000..3dce08b
--- /dev/null
+++ b/rtree/test/expected/double-let-expr-ired
@@ -0,0 +1,6 @@
+1***: y := 2; 1 + y True
+11***: 1 + 2 True
+111: 1 False
+1101: 2 False
+11001: 3 False
+11000: 1 + 2 True
diff --git a/rtree/test/expected/double-let-expr-ireda b/rtree/test/expected/double-let-expr-ireda
new file mode 100644
index 0000000..9163803
--- /dev/null
+++ b/rtree/test/expected/double-let-expr-ireda
@@ -0,0 +1,4 @@
+****: x := 1; y := 2; x + y True
+1***: y := 2; 1 + y True
+11***: 1 + 2 True
+111: 1 False
diff --git a/rtree/test/expected/double-overloading-let-expr-ired b/rtree/test/expected/double-overloading-let-expr-ired
new file mode 100644
index 0000000..5d756a4
--- /dev/null
+++ b/rtree/test/expected/double-overloading-let-expr-ired
@@ -0,0 +1,6 @@
+1***: x := 2; x + x True
+11***: 2 + 2 True
+111: 2 False
+1101: 2 False
+11001: 4 False
+11000: 2 + 2 True
diff --git a/rtree/test/expected/double-overloading-let-expr-ireda b/rtree/test/expected/double-overloading-let-expr-ireda
new file mode 100644
index 0000000..2c81446
--- /dev/null
+++ b/rtree/test/expected/double-overloading-let-expr-ireda
@@ -0,0 +1,4 @@
+****: x := 1; x := 2; x + x True
+1***: x := 2; x + x True
+11***: 2 + 2 True
+111: 2 False
diff --git a/rtree/test/expected/small-let-expr-ired b/rtree/test/expected/small-let-expr-ired
new file mode 100644
index 0000000..8ec2335
--- /dev/null
+++ b/rtree/test/expected/small-let-expr-ired
@@ -0,0 +1,5 @@
+1***: 2 + 1 True
+11: 2 False
+101: 1 False
+1001: 3 False
+1000: 2 + 1 True
diff --git a/rtree/test/expected/small-let-expr-ireda b/rtree/test/expected/small-let-expr-ireda
new file mode 100644
index 0000000..949bb8c
--- /dev/null
+++ b/rtree/test/expected/small-let-expr-ireda
@@ -0,0 +1,3 @@
+***: x := 1; 2 + x True
+1***: 2 + 1 True
+11: 2 False
diff --git a/rtree/test/expected/small-opr-expr-ired b/rtree/test/expected/small-opr-expr-ired
new file mode 100644
index 0000000..e8e5ee3
--- /dev/null
+++ b/rtree/test/expected/small-opr-expr-ired
@@ -0,0 +1,4 @@
+1: 1 False
+01: 2 False
+001: 3 False
+000: 1 + 2 True
diff --git a/rtree/test/expected/small-opr-expr-ireda b/rtree/test/expected/small-opr-expr-ireda
new file mode 100644
index 0000000..3e13435
--- /dev/null
+++ b/rtree/test/expected/small-opr-expr-ireda
@@ -0,0 +1,2 @@
+***: 1 + 2 True
+1: 1 False
diff --git a/rtree/test/src/Control/Monad/IRTreeSpec.hs b/rtree/test/src/Control/Monad/IRTreeSpec.hs
index 2d31256..0bf6d60 100644
--- a/rtree/test/src/Control/Monad/IRTreeSpec.hs
+++ b/rtree/test/src/Control/Monad/IRTreeSpec.hs
@@ -1,20 +1,43 @@
 {-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Control.Monad.IRTreeSpec where
 
 import Control.Monad.IRTree
 import qualified Control.Monad.IRTree as IRTree
 import qualified Control.Monad.RTree as RTree
-import Control.Monad.Writer.Strict
 import Data.Bool
 import Data.Expr as Expr
 import Data.List.NonEmpty (nonEmpty)
 import qualified Data.List.NonEmpty as NE
 import qualified Data.Map.Strict as Map
+import System.IO
 import Test.Hspec
+import Test.Hspec.Glitter (onGlitter)
 
 spec :: Spec
-spec = describe "examples" do
+spec = do
+  binsearchSpec
+  expsearchSpec
+  examplesSpec
+
+binsearchSpec :: Spec
+binsearchSpec = describe "binsearch" do
+  it "should guess a number between 0 and 10" do
+    binsearch (\a -> do pure (a <= 6)) 0 10 `shouldReturn` 6
+    binsearch (\a -> do pure (a <= 3)) 0 10 `shouldReturn` 3
+
+expsearchSpec :: Spec
+expsearchSpec = describe "expsearch" do
+  it "should guess a number between 0 and 10" do
+    expsearch (\a -> do pure (a <= 6, 10 - a)) `shouldReturn` (6, True)
+    expsearch (\a -> do pure (a <= 3, 10 - a)) `shouldReturn` (3, True)
+    expsearch (\a -> do pure (a <= 9, 10 - a)) `shouldReturn` (9, False)
+    expsearch (\a -> do pure (a <= 12, 10 - a)) `shouldReturn` (9, False)
+
+examplesSpec :: Spec
+examplesSpec = describe "examples" do
   handle "small-opr-expr" $
     Opr (Cnt 1) (Cnt 2)
 
@@ -35,53 +58,85 @@ spec = describe "examples" do
 
     let re = runReaderT (Expr.rExpr e) Map.empty
 
+    it "should reduce like iinputs" do
+      forM_ (RTree.iinputs re) \(ii, e') -> do
+        p <- toPredicate ii
+        IRTree.reduce (const p) me `shouldReturn` e'
+
     let
       predicate :: Expr -> IO Bool
       predicate = pure . contains isOpr
 
     rex <- runIO $ RTree.reduce predicate re
 
-    -- onGlitterWith
-    --   ("test/expected/" <> str <> "-red")
-    --   ( \fp () -> do
-    --       (mex, result) <- runWriterT (IRTree.reduce (debugPredicate showString (prettyExprS 0) predicate) me)
-    --       writeFile fp (appEndo result "")
-    --       pure mex
-    --   )
-    --   do
-    --     it "should produce the same results as the RTree" \mex -> do
-    --       rex `shouldBe` mex
-
-    it "should find an opr exponentially" do
-      (mex, _) <- runWriterT (IRTree.reduceExp (debugPredicate showString (prettyExprS 0) predicate) me)
-      rex `shouldBe` mex
-
-    it "should find an opr fibonacci" do
-      (mex, _) <- runWriterT (IRTree.reduceFib (debugPredicate showString (prettyExprS 0) predicate) me)
-      rex `shouldBe` mex
-
-    it "should reduce like iinputs" do
-      forM_ (RTree.iinputs re) \(ii, e') -> do
-        p <- toPredicate ii
-        IRTree.reduce (const p) me `shouldReturn` Just e'
+    onGlitter
+      ("test/expected/" <> str <> "-ired")
+      ( \f -> do
+          withFile f WriteMode \h -> do
+            let p = debugPredicate' h predicate
+            IRTree.reduce p me `shouldReturn` rex
+      )
+      do
+        it "should find an opr normally" \_ -> do
+          IRTree.reduce (const predicate) me `shouldReturn` rex
+
+    onGlitter
+      ("test/expected/" <> str <> "-ireda")
+      ( \f -> do
+          withFile f WriteMode \h -> do
+            let p = debugPredicate' h predicate
+            let (i, l, _) = IRTree.probe me ""
+            _ <- p l i
+            IRTree.reduceAdaptive p me `shouldReturn` rex
+      )
+      do
+        it "should find an opr addabtively" \_ -> do
+          IRTree.reduceAdaptive (const predicate) me `shouldReturn` rex
+
+debugPredicate'
+  :: (Show l)
+  => Handle
+  -> (Expr -> IO Bool)
+  -> [AnnotatedChoice l]
+  -> Expr
+  -> IO Bool
+debugPredicate' h predicate lst i = do
+  x <- predicate i
+  hPutStr
+    h
+    ( map (debugChoice . choice) lst
+        <> ": "
+        <> prettyExprS 0 i ""
+        <> " "
+        <> show x
+        <> "\n"
+    )
+  pure x
+ where
+  debugChoice = \case
+    Yes -> '1'
+    No -> '0'
+    Undecided -> '*'
 
 debugPredicate
-  :: (Monad m)
-  => (l -> ShowS)
+  :: Handle
+  -> (l -> ShowS)
   -> (i -> ShowS)
-  -> (i -> m Bool)
-  -> [(Bool, l)]
+  -> (i -> IO Bool)
+  -> [AnnotatedChoice l]
   -> i
-  -> WriterT (Endo String) m Bool
-debugPredicate ppl ppi predicate lst i = do
-  x <- lift (predicate i)
-  tell . Endo $
-    showString (bool "0" "1" x)
-      . showString ": "
-      . ppi i
-      . showString "\n"
-      . case nonEmpty lst of
-        Nothing -> showString "initial\n"
-        Just lst' -> ppl (snd $ NE.last lst') . showString "\n"
-
+  -> IO Bool
+debugPredicate h ppl ppi predicate lst i = do
+  x <- predicate i
+  hPutStr
+    h
+    ( showString (bool "0" "1" x)
+        . showString ": "
+        . ppi i
+        . showString "\n"
+        . case nonEmpty lst of
+          Nothing -> showString "initial\n"
+          Just lst' -> ppl (label $ NE.last lst') . showString "\n"
+        $ ""
+    )
   pure x
diff --git a/rtree/test/src/Control/Monad/RTreeSpec.hs b/rtree/test/src/Control/Monad/RTreeSpec.hs
index 3097885..a1aee88 100644
--- a/rtree/test/src/Control/Monad/RTreeSpec.hs
+++ b/rtree/test/src/Control/Monad/RTreeSpec.hs
@@ -115,4 +115,4 @@ examplesSpec = describe "example" do
     it "should reduce like iinputs" do
       forM_ (iinputs re) \(ii, e') -> do
         p <- toPredicate ii
-        reduce p re `shouldReturn` Just e'
+        reduce p re `shouldReturn` e'
-- 
GitLab