Skip to content

Latest commit

 

History

History
207 lines (152 loc) · 5.09 KB

MicroKanren.org

File metadata and controls

207 lines (152 loc) · 5.09 KB

A MicroKanren Implementation in Haskell

Start here

This is a Literate Haskell program written in Org Mode.

To extract the source-code into a MicroKanren.hs file, you may run org-babel-tangle from within Emacs.

This is just a port of HemannMuKanren2013.pdf to Haskell.

module MicroKanren where

import Data.List (transpose)
import Data.Maybe (fromMaybe)

Terms: Syntax, Substitution and Unification

data Term
  = Var Int
  | Atom String [Term]
  deriving (Eq)

instance Show Term where
  show (Var i) = "_." ++ show i
  show (Atom a []) = a
  show (Atom a as) = a ++ show as
sym :: String -> Term
sym a = Atom a []

nil :: Term
nil = sym "Nil"

cons :: Term -> Term -> Term
cons a b = Atom "Cons" [a, b]

car :: Term -> Term
car (Atom "Cons" [a, _]) = a

cdr :: Term -> Term
cdr (Atom "Cons" [_, b]) = b

list :: [Term] -> Term
list = foldr cons nil

append :: Term -> Term -> Term
append l t
  | nil == l = t
  | otherwise = cons (car l) (append (cdr l) t)

A substitution is a map (or rather, an association list) from Variables to Terms.

type Substitution = [(Int, Term)]

substEmpty :: Substitution
substEmpty = []

substAdd :: Substitution -> Int -> Term -> Substitution
substAdd s i t = (i, t) : s

substApply :: Substitution -> Term -> Term
substApply s t@(Var i) = fromMaybe t (lookup i s)
substApply s (Atom a as) = Atom a (map (substApply s) as)

substApplyRec :: Substitution -> Term -> Term
substApplyRec s t =
  case substApply s t of
    t'@(Var i)
      | t' == t -> t'
      | otherwise -> substApplyRec s t'
    (Atom a as) -> Atom a (map (substApplyRec s) as)
unify :: Substitution -> Term -> Term -> Maybe Substitution
unify s t1 t2 = (++s) `fmap` unify' s (substApply s t1) (substApply s t2)
  where
    unify' s (Var i) b = Just (substAdd s i b)
    unify' s a (Var j) = Just (substAdd s j a)
    unify' s (Atom a as) (Atom b bs)
      | a == b && length as == length bs = unify'' s as bs
      | otherwise = Nothing
      where
        unify'' s [] [] = Just s
        unify'' s (x:xs) (y:ys) = unify' s x y >>= (\s' -> unify'' s' xs ys)

States and Goals

The State holds a Substitution and a counter for the next free variable.

type State = (Substitution, Int)
  
stateEmpty :: State
stateEmpty = (substEmpty, 0)

There are just 4 Goal Constructors.

type Goal = State -> [State]

(===) :: Term -> Term -> Goal
(===) a b (s, c) =
  case unify s a b of
    Nothing -> []
    Just s' -> [(s', c)]

exists :: (Term -> Goal) -> Goal
exists f (s, c) = f (Var c) (s, c + 1)

(/\) :: Goal -> Goal -> Goal
(/\) g1 g2 st = g1 st >>= g2

(\/) :: Goal -> Goal -> Goal
(\/) g1 g2 st = interleave (g1 st) (g2 st)
  where
    interleave x y = concat (transpose [x, y])

A few extras.

conde :: [Goal] -> Goal
conde = foldl1 (\/)

exists2 :: (Term -> Term -> Goal) -> Goal
exists2 f (s, c) = f (Var c) (Var $ c + 1) (s, c + 2)

exists3 :: (Term -> Term -> Term -> Goal) -> Goal
exists3 f (s, c) = f (Var c) (Var $ c + 1) (Var $ c + 2) (s, c + 3)

infixl 5 /\
infixl 5 \/

API

reifyState :: [Int] -> State -> Substitution
reifyState vars (s, _) = map (\i -> (i, substApplyRec s (Var i))) vars

reify :: [Int] -> [State] -> [Substitution]
reify vars = map (reifyState vars)

run :: Int -> [Int] -> Goal -> [Substitution]
run n vars g = take n (run' vars g)

run' :: [Int] -> Goal -> [Substitution]
run' vars g = reify vars (g stateEmpty)

Some Relational Programs

caro :: Term -> Term -> Goal
caro p a = exists (\b -> p === cons a b)

cdro :: Term -> Term -> Goal
cdro p b = exists (\a -> p === cons a b)

membero :: Term -> Term -> Goal
membero x l = conde [caro l x, exists (\b -> cdro l b /\ membero x b)]

appendo :: Term -> Term -> Term -> Goal
appendo l1 l2 l3 =
  conde
    [ nil === l1 /\ l2 === l3
    , exists3 (\x y z -> l1 === cons x y /\ l3 === cons x z /\ appendo y l2 z)
    ]

Some Usage Examples

For which values of x does membero x (list [x]) hold? There is only 1 answer: (0,._0), meaning that x (encoded by the variable number 0) can be mapped to any value _.0 (since this term is a variable, not an atom.)

-- [[(0,_.0)]]
egMembero1 = run' [0] $ exists (\x -> membero x (list [x]))

For which values of x does membero x (list [sym "X", sym "Y"]) hold? There are possible two answers: x = X or x = Y.

-- [[(0,X)],[(0,Y)]]
egMembero2 = run' [0] $ exists (\x -> membero x (list [sym "X", sym "Y"]))
-- [[(0,Cons[X,Cons[Y,Nil]])]]
egAppendo1 = run' [0] $ exists (\x -> appendo (list [sym "X"]) (list [sym "Y"]) x)

-- [[(0,Nil),(1,Cons[X,Cons[Y,Nil]])]
-- ,[(0,Cons[X,Nil]),(1,Cons[Y,Nil])]
-- ,[(0,Cons[X,Cons[Y,Nil]]),(1,Nil)]
-- ]
egAppendo2 = run' [0,1] $ exists2 (\x y -> appendo x y (list [sym "X", sym "Y"]))