-- https://gist.github.com/msullivan/4223fd47991acbe045ec import Control.Applicative (Alternative(..)) import Control.Monad (MonadPlus(..)) import qualified Control.Monad.Stream as Stream import Control.Monad.Stream (Stream) import Control.Monad.State (MonadState(..), StateT(..), execStateT, mapStateT) import Test.Hspec (hspec, it, shouldBe) type Var = Integer type Subst = [(Var, Term)] type State = (Subst, Integer) type Program = StateT State Stream data Term = Atom String | Pair Term Term | Var Var deriving (Eq, Show) -- Apply a substitution to the top level of a term walk :: Term -> Subst -> Term walk (Var v) s = case lookup v s of Nothing -> Var v Just us -> walk us s walk u s = u extS :: Var -> Term -> Subst -> Subst extS x v s = (x, v) : s -- Try to unify two terms under a substitution; -- return an extended subst if it succeeds unify :: Term -> Term -> Subst -> Maybe Subst unify u v s = un (walk u s) (walk v s) where un (Var x1) (Var x2) | x1 == x2 = return s un (Var x1) v = return $ extS x1 v s un u (Var x2) = return $ extS x2 u s un (Pair u1 u2) (Pair v1 v2) = do s' <- unify u1 v1 s unify u2 v2 s' un (Atom a1) (Atom a2) | a1 == a2 = return s un _ _ = mzero fresh :: Program Term fresh = do (s, c) <- get put (s, c+1) return (Var c) -- microKanren program formers zzz :: Program a -> Program a zzz = mapStateT Stream.suspended equiv :: Term -> Term -> Program () equiv u v = do (s, c) <- get case unify u v s of Nothing -> mzero Just s' -> put (s', c) callFresh :: (Term -> Program a) -> Program a callFresh = (fresh >>=) disj :: Program a -> Program a -> Program a disj = (<|>) conj :: Program a -> Program b -> Program b conj = (>>) -- Recovering miniKanren interface reify :: [State] -> [Term] reify = map reifyState where reifyState :: State -> Term reifyState (s, _) = let v = walk' (Var 0) s in walk' v (reifyS v []) reifyS :: Term -> Subst -> Subst reifyS v s = case walk v s of Var v -> let n = reifyName (length s) in (v, n) : s Pair u v -> reifyS v $ reifyS u s _ -> s reifyName :: Int -> Term reifyName n = Atom $ "_." ++ show n walk' :: Term -> Subst -> Term walk' v s = case walk v s of Pair u v -> Pair (walk' u s) (walk' v s) v -> v callEmptyState :: Program () -> Stream State callEmptyState g = execStateT g ([], 0) run :: Int -> (Term -> Program ()) -> [Term] run n = reify . Stream.observeMany n . callEmptyState . callFresh run' :: (Term -> Program ()) -> [Term] run' = reify . Stream.observeAll . callEmptyState . callFresh -- Tests main :: IO () main = hspec $ do let ab = conj (callFresh (\a -> equiv a (Atom "7"))) (callFresh (\b -> disj (equiv b (Atom "5")) (equiv b (Atom "6")))) five x = equiv x (Atom "5") fives x = disj (equiv x (Atom "5")) (zzz $ fives x) fivesRev x = disj (zzz $ fivesRev x) (equiv x (Atom "5")) sixes x = disj (equiv x (Atom "6")) (zzz $ sixes x) p56 x = disj (fives x) (sixes x) p010 q = do x <- fresh; y <- fresh equiv q (Pair x (Pair y x)) <|> equiv q (Pair y (Pair x y)) it "ab" $ Stream.observeAll (callEmptyState ab) `shouldBe` [([(1,Atom "5"),(0,Atom "7")],2) ,([(1,Atom "6"),(0,Atom "7")],2)] it "five" $ run' five `shouldBe` [Atom "5"] it "fives" $ run 10 fives `shouldBe` replicate 10 (Atom "5") it "fivesRev" $ run 10 fivesRev `shouldBe` replicate 10 (Atom "5") it "p56" $ run 10 p56 `shouldBe` concat (replicate 5 [Atom "5",Atom "6"]) it "null" $ run' (const $ pure ()) `shouldBe` [Atom "_.0"] it "p010" $ run 2 p010 `shouldBe` [Pair (Atom "_.0") (Pair (Atom "_.1") (Atom "_.0")) ,Pair (Atom "_.0") (Pair (Atom "_.1") (Atom "_.0"))]