Ok so I actually got more benchmark results back from my interval analyser. I posted the basic setup last night (see page 1) but here's the Haskell I'm using for the coinductive iteration โ it replaces the standard widening loop with a coiterative unfold:
-- Coiterative abstract fixpoint using Stream comonad import Control.Comonad import Data.Stream (Stream(..)) import qualified Data.Stream as S -- Interval lattice data Interval = Bot | Iv Int Int -- [lo, hi] | Top deriving (Eq, Show) -- Join (least upper bound) joinIv :: Interval -> Interval -> Interval joinIv Bot x = x joinIv x Bot = x joinIv _ Top = Top joinIv Top _ = Top joinIv (Iv a b) (Iv c d) = Iv (min a c) (max b d) -- Coinductive iteration: unfold a stream of iterates -- stopping when two consecutive elements are equal coiterFix :: Eq a => (a -> a) -> a -> Stream a coiterFix f seed = S.unfold (\x -> (x, f x)) seed takeUntilFixed :: Eq a => Stream a -> [a] takeUntilFixed (Cons x xs@(Cons y _)) | x == y = [x] | otherwise = x : takeUntilFixed xs -- Concrete usage: run abstract transformer F until fixpoint coinductiveLFP :: (Eq a, Lattice a) => (a -> a) -> a -> a coinductiveLFP f bot = last $ takeUntilFixed $ coiterFix f bot
The key insight is that coiterFix doesn't commit to termination at definition time โ it produces an infinite stream of iterates lazily, and takeUntilFixed consumes it until a post-fixpoint is found. This is the dual of proving a property by induction over the iterate count.
Now, the issue I ran into: for the programs with unbounded loops, the stream never stabilises on its own, so you still need widening. But the coinductive framing makes it clear where to plug widening in โ you're essentially switching from observing the greatest fixpoint to computing an over-approximation of it. That clarity is really useful.
Benchmark results from 12 simple loop programs (no recursion):
- โ 10/12 converged without widening (bounded loops)
- โ ๏ธ 2/12 required widening injection at stream position 4
- โฑ๏ธ Average iteration count: 3.1 (vs 4.8 for standard Kleene)
So the lazy coiterative approach was slightly more efficient at detecting fixpoints. Not groundbreaking but interesting. Will try recursive programs next.