Last active
August 21, 2025 14:57
-
-
Save VictorTaelin/60d3bc72fb4edefecd42095e44138b41 to your computer and use it in GitHub Desktop.
The Collapse Monad
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Control.Monad (ap, forM_) | |
import qualified Data.Map as M | |
-- The Collapse Monad | |
-- ------------------ | |
-- The Collapse Monad allows collapsing a value with labelled superpositions | |
-- into a flat list of superposition-free values. It is like the List Monad, | |
-- except that, instead of always doing a cartesian product, it will perform | |
-- pairwise merges of distant parts of your program that are "entangled" | |
-- under the same label. Examples: | |
-- - collapse (&0{1 2}, 3) == [(1,3), (2,3)] | |
-- - collapse (&0{1 2}, &0{3 4}) == [(1,3), (2,4)] | |
-- - collapse (&0{1 2}, &1{3 4}) == [(1,3), (1,4), (2,3), (2,4)] | |
-- - collapse (&0{1 2}, &1{3 4}, 5) == [(1,3,5), (1,4,5), (2,3,5), (2,4,5)] | |
-- Note how the second line above doesn't return the full cartesian product of | |
-- {1 2} and {3 4}; instead, it combines elements pairwise, because both | |
-- superpositions have the same label. I'm posting this file as a template to | |
-- recall later. This algorithm is used to flatten HVM3's results, which can be | |
-- superposed, back to a list of λ-terms without superpositions. | |
-- A bit-string | |
data Bin | |
= O Bin | |
| I Bin | |
| E | |
deriving Show | |
-- A Collapse is a tree of superposed values | |
data Collapse a = Sup Int (Collapse a) (Collapse a) | Val a | |
-- The Collapse Monad | |
-- Note: could be optimized using an IntMap instead of a List | |
bind :: Collapse a -> (a -> Collapse b) -> Collapse b | |
bind a f = fork a (repeat (\x -> x)) where | |
fork (Val v) paths = pass (f v) (map (\x -> x E) paths) | |
fork (Sup k x y) paths = Sup k (fork x (mut k putO paths)) (fork y (mut k putI paths)) | |
pass (Val v) paths = Val v | |
pass (Sup k x y) paths = case paths !! k of | |
E -> Sup k x y | |
O p -> pass x (mut k (\_->p) paths) | |
I p -> pass y (mut k (\_->p) paths) | |
putO bs = \x -> bs (O x) | |
putI bs = \x -> bs (I x) | |
-- Collapses a Term and flattens into a list | |
flatten :: Collapse a -> [a] | |
flatten (Val x) = [x] | |
flatten (Sup _ x y) = flatten x ++ flatten y | |
-- Mutates an element at given index in a list | |
mut :: Int -> (a -> a) -> [a] -> [a] | |
mut 0 f (x:xs) = f x : xs | |
mut n f (x:xs) = x : mut (n-1) f xs | |
mut _ _ [] = [] | |
instance Functor Collapse where | |
fmap f (Val v) = Val (f v) | |
fmap f (Sup k x y) = Sup k (fmap f x) (fmap f y) | |
instance Applicative Collapse where | |
pure = Val | |
(<*>) = ap | |
instance Monad Collapse where | |
return = pure | |
(>>=) = bind | |
-- Example | |
-- ------- | |
-- Collapsing a simple DSL with Superpositions | |
-- A simple DSL with tuples, numbers and superpositions | |
data Term | |
= TNum Int -- number | |
| TTup Term Term -- tuple | |
| TSup Int Term Term -- superposition | |
-- Shows a term with superpositions | |
showTerm :: Term -> String | |
showTerm (TNum n) = show n | |
showTerm (TTup x y) = "(" ++ showTerm x ++ "," ++ showTerm y ++ ")" | |
showTerm (TSup k a b) = "&" ++ show k ++ "{" ++ showTerm a ++ " " ++ showTerm b ++ "}" | |
-- A Collapser for our DSL | |
-- - On normal values, we just use `<-` | |
-- - On superpositons, we return a Sup | |
collapse :: Term -> Collapse Term | |
collapse (TNum x) = do | |
return $ TNum x | |
collapse (TTup l r) = do | |
l <- collapse l | |
r <- collapse r | |
return $ TTup l r | |
collapse (TSup k a b) = | |
Sup k (collapse a) (collapse b) | |
-- Test program | |
main :: IO () | |
main = do | |
-- Builds the term ((&0{1 2},&1{3 4}),(&0{5 6},&1{7 8})) | |
let tup0 = TTup (TSup 0 (TNum 1) (TNum 2)) (TSup 1 (TNum 3) (TNum 4)) | |
let tup1 = TTup (TSup 0 (TNum 5) (TNum 6)) (TSup 1 (TNum 7) (TNum 8)) | |
let term = TTup tup0 tup1 | |
-- Collapses it | |
let coll = flatten (collapse term) | |
-- Prints the sup-free term list | |
forM_ coll $ \ term -> | |
putStrLn $ showTerm term |
Improved / Faster Version
The implementation below uses SemiTerms (a term with N holes) to accumulate the current partial term during the collapsing process, resulting in a much simpler and faster implementation of the collapse logic above.
-- Simplified / Faster Collapser
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
import qualified Data.Kind as DK
-- Term
-- ----
data Term
= Var String
| Lam String (Term -> Term)
| App Term Term
| Tup Term Term
| Era
| Sup Int Term Term
| Col Int Int Term
instance Show Term where
show (Var k) = k
show (Lam n f) = "λ" ++ n ++ "." ++ show (f (Var n))
show (App f x) = "(" ++ show f ++ ")(" ++ show x ++ ")"
show (Tup x y) = "(" ++ show x ++ "," ++ show y ++ ")"
show Era = "*"
show (Sup l x y) = "&" ++ show l ++ "{" ++ show x ++ "," ++ show y ++ "}"
show (Col l 0 x) = "&" ++ show l ++ "<" ++ show x
show (Col l 1 x) = "&" ++ show l ++ ">" ++ show x
-- SemiTerm
-- --------
-- A term with N holes. Example:
-- - `λf.λx.(f _)` is a term with 1 hole
-- - `(A,(_,_))` is a term with 2 holes
-- Allows filling the holes left-to-right.
data Nat = Z | S Nat
data SNat :: Nat -> DK.Type where
SZ :: SNat Z
SS :: SNat n -> SNat (S n)
type family Add (n :: Nat) (m :: Nat) :: Nat where
Add Z m = m
Add (S n) m = S (Add n m)
addSNat :: SNat n -> SNat m -> SNat (Add n m)
addSNat SZ m = m
addSNat (SS n) m = SS (addSNat n m)
type family Ctr (n :: Nat) :: DK.Type where
Ctr Z = Term
Ctr (S p) = Term -> Ctr p
data SemiTerm where
SemiTerm :: SNat k -> (Term -> Ctr k) -> SemiTerm
-- An initial term with no hole.
start :: SemiTerm
start = SemiTerm SZ id
-- Replaces the leftmost hole in S, by a term T with N holes.
-- When T has no holes (N=0):
-- - If S has a leftmost hole: fill it.
-- - Else: apply S to T, extending the spine.
-- Examples:
-- - extend (_,_) X ~> (X,_) -- fill the leftmost hole
-- - extend (A,_) (_,X) ~> (A,(_,X)) -- fill the leftmost hole
-- - extend (A,B) X ~> ((A,B) X) -- extend the spine
extend :: SemiTerm -> SNat n -> Ctr n -> SemiTerm
extend (SemiTerm k l) n ctr = case k of
SZ -> SemiTerm n (ini l n ctr)
SS k' -> SemiTerm (addSNat n k') (ext k' l n ctr)
where ini :: (Term -> Term) -> SNat n -> Ctr n -> (Term -> Ctr n)
ext :: SNat k -> (Term -> Ctr (S k)) -> SNat n -> Ctr n -> Ctr (S (Add n k))
ini l SZ ctr = \k -> App (l k) ctr
ini l (SS n') ctr = \x -> ini l n' (ctr x)
ext _ l SZ ctr = l ctr
ext k l (SS n') ctr = \x -> ext k l n' (ctr x)
-- Fills remaining holes with a placeholder variable.
complete :: SemiTerm -> Term
complete (SemiTerm SZ l) = l (Var "F")
complete (SemiTerm (SS p) l) = complete (SemiTerm p (l (Var "X")))
-- Evaluation
-- ----------
wnf :: Term -> Term
wnf (App f x) = app f x
wnf (Col l i x) = col l i x
wnf v = v
col :: Int -> Int -> Term -> Term
col l i (wnf -> Var k) = Var k
col l i (wnf -> Lam k f) = Lam k (\x -> Col l i (f x))
col l i (wnf -> App f x) = App f x
col l i (wnf -> Tup x y) = Tup (Col l i x) (Col l i y)
col l i (wnf -> Era) = Era
col l i (wnf -> Sup r x y) = if l == r then [x,y] !! i else Sup r (Col l i x) (Col l i y)
col l i (wnf -> Col r j x) = Col l i (Col r j x)
app :: Term -> Term -> Term
app (wnf -> Lam k f) (wnf -> x) = wnf $ f x
app (wnf -> Sup l x y) (wnf -> v) = Sup l (App x (Col l 0 v)) (App y (Col l 1 v))
app f x = App f x
-- Collapsing
-- ----------
collapse :: [Term] -> SemiTerm -> Term
collapse [] semi =
case complete semi of
App _ x -> x
otherwise -> error "unreachable"
collapse ((wnf -> tm) : tms) semi =
case tm of
Var k -> collapse tms $ extend semi SZ (Var k)
Lam k f -> collapse (f (Var k) : tms) $ extend semi (SS SZ) (\f -> Lam k (\_ -> f))
App f x -> collapse (f : x : tms) $ extend semi (SS (SS SZ)) App
Tup x y -> collapse (x : y : tms) $ extend semi (SS (SS SZ)) Tup
Era -> Era
Sup l a b -> Sup l (collapse (a : map (Col l 0) tms) semi) (collapse (b : map (Col l 1) tms) semi)
Col l i x -> collapse (x : tms) $ extend semi (SS SZ) (Col l i)
-- Flattening
-- ----------
data SQ a = SQ [a] [a]
sqNew :: SQ a
sqNew = SQ [] []
sqPop :: SQ a -> Maybe (a, SQ a)
sqPop (SQ [] []) = Nothing
sqPop (SQ [] ys) = sqPop (SQ (reverse ys) [])
sqPop (SQ (x:xs) ys) = Just (x, SQ xs ys)
sqPut :: a -> SQ a -> SQ a
sqPut x (SQ xs ys) = SQ xs (x:ys)
flatten :: Term -> [Term]
flatten term = go term (sqNew :: SQ Term) where
go (Sup k a b) sq = go Era (sqPut b $ sqPut a $ sq)
go Era sq = case sqPop sq of { Just (v,sq) -> go v sq ; Nothing -> [] }
go x sq = x : go Era sq
-- Example
-- -------
main :: IO ()
main = do
print $ flatten $ collapse [Tup (Sup 0 (Var "A") (Var "B")) (Sup 0 (Var "C") (Var "D"))] start
print $ flatten $ collapse [Tup (Sup 0 (Var "A") (Var "B")) (Sup 1 (Var "C") (Var "D"))] start
Below is a step-by-step of its workings:
collapse _ [λx.(&0{A,B},&1{C,D})]
---------------------------------
collapse λx._ [(&0{A,B},&1{C,D})]
---------------------------------
collapse λx.(_,_) [&0{A,B},&1{C,D}]
-----------------------------------
&0{(collapse λx.(_,_) [A,&1{C,D}<0])
(collapse λx.(_,_) [B,&1{C,D}>0])}
-------------------------------------
&0{(collapse λx.(A,_) [&1{C,D}<0])
(collapse λx.(B,_) [&1{C,D}>0])}
-----------------------------------
&0{(collapse λx.(A,_) [&1{C<0,D<0}])
(collapse λx.(B,_) [&1{C>0,D>0}])}
-------------------------------------
&0{&1{(collapse λx.(A,_) [C<0<1])
(collapse λx.(A,_) [D<0>1])}
&1{(collapse λx.(B,_) [C>0<1])
(collapse λx.(B,_) [D>0>1])}}
-----------------------------------
&0{&1{(collapse λx.(A,_) [C])
(collapse λx.(A,_) [D])}
&1{(collapse λx.(B,_) [C])
(collapse λx.(B,_) [D])}}
-------------------------------
&0{&1{(collapse λx.(A,C) [])
(collapse λx.(A,D) [])}
&1{(collapse λx.(B,C) [])
(collapse λx.(B,D) [])}}
------------------------------
&0{&1{λx.(A,C)
λx.(A,D)}
&1{λx.(B,C)
λx.(B,D)}}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note: it seems like the code above had a small bug, here's the version we're currently using on HVM3: