Slide 1

Slide 1 text

µKanren A Minimal Functional Core For Relational Programming 1 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 2

Slide 2 text

Brian Hicks [email protected] / @brianhicks STL Tech Slack: stltech.herokuapp.com 2 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 3

Slide 3 text

Prolog(ue) mother_child(trude, sally). father_child(tom, sally). father_child(tom, erica). father_child(mike, tom). parent_child(X, Y) :- father_child(X, Y). parent_child(X, Y) :- mother_child(X, Y). sibling(X, Y) :- parent_child(Z, X), parent_child(Z, Y). 3 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 4

Slide 4 text

Relational / Logic Programming! • sibling(X, Y) is a relation, not a function • unify over variables, which may be unified to values (like X and sally) • various strategies to get results 4 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 5

Slide 5 text

miniKanren (run 1 (out) (fresh (x) (== out x) (== 3 x))) Output: (3) 5 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 6

Slide 6 text

miniKanren (run 2 (out) (fresh (x) (== out x) (conde ((== 3 x)) ((== 4 x))))) Output: (3 4) 6 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 7

Slide 7 text

µKanren Hemann and Friedman, 2013 "We argue, though, that deeply buried within that 265- line miniKanren implementation is a small, beautiful, relational programming language seeking to get out. We believe µKanren is that language." 7 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 8

Slide 8 text

Terms type alias Var = Int type Term a = LVar Var | LVal a 8 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 9

Slide 9 text

State "A µKanren program proceeds through the application of a goal to a state." type alias Substitution a = Dict Var (Term a) type alias State a = { subs : Substitution a , next : Var } init = { subs = Dict.empty, next = 0 } 9 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 10

Slide 10 text

Goal "Goals are often understood by analogy to predicates. [...] a goal pursued to a given state can either succeed or fail." type alias Goal a = State a -> Stream a 10 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 11

Slide 11 text

Stream "A goal's success may result in a sequence of (enlarged) states, which we term a stream." type Stream a = Empty | Mature (State a) (Stream a) 11 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 12

Slide 12 text

Stream Constructors zero : Stream a zero = Empty singleton : State a -> Stream a singleton state = Mature state zero 12 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 13

Slide 13 text

Goal Constructors • ≡ (spelled identical) - unify two terms • call/fresh - introduce a new term • disj - takes two goals, either of whom may succeed • conj - takes two goals, both of whom must succeed 13 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 14

Slide 14 text

Utilities: unify unify : Term a -> Term a -> Substitution a -> Maybe (Substitution a) unify leftVar rightVar subs = let left = walk leftVar subs right = walk rightVar subs in case (left, right) of -- next slides! 14 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 15

Slide 15 text

Utilities: unify (two vars) (LVar leftRef, LVar rightRef) -> if leftRef == rightRef then Just sub else Just <| Dict.insert leftRef right subs 15 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 16

Slide 16 text

Utilities: unify (two vals) (LVal leftVal, LVal rightVal) -> if leftVal == rightVal then Just subs else Nothing 16 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 17

Slide 17 text

Utilities: unify (mismatch) (LVar ref, _) -> Just <| Dict.insert ref right subs (_, LVar ref) -> Just <| Dict.insert ref left subs 17 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 18

Slide 18 text

Utilities: unify unify (LVar 1) (LVar 2) Dict.empty == Just (Dict.fromList [ (2, LVar 1) ]) unify (LVar 1) (LVal "foo") Dict.empty == Just (Dict.fromList [ (1, LVal "foo") ]) unify (LVal "foo") (LVal "bar") Dict.empty == Nothing 18 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 19

Slide 19 text

Goals: identical identical : Term a -> Term a -> Goal a identical left right = \state -> case unify left right state.subs of Just unified -> singleton { state | subs = unified } Nothing -> zero 19 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 20

Slide 20 text

Goals: callFresh callFresh : (Term a -> Goal a) -> Goal a callFresh termToGoal = \state -> termToGoal (LVar state.next) { state | next = state.next + 1 } 20 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 21

Slide 21 text

Using callFresh and identical callFresh (\out -> identical out (LVal 1)) init == Mature { subs = Dict.fromList [ (0, LVal 1) ] } , next = 1 } Empty 21 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 22

Slide 22 text

Goals: disjoin disjoin : Goal a -> Goal a -> Goal a disjoin g1 g2 = \state -> mplus (g1 state) (g2 state) 22 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 23

Slide 23 text

Utils: mplus mplus : Stream a -> Stream a -> Stream a mplus s1 s2 = case s1 of Empty -> s2 Mature state stream -> Mature state (mplus s2 stream) 23 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 24

Slide 24 text

Using disjoin callFresh (\out -> disjoin (identical out (LVal 1)) (identical out (LVal 2)) ) init 24 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 25

Slide 25 text

Using disjoin (result) Mature { substitutions = Dict.fromList [ (0, LVal 1) ] , next = 1 } (Mature { substitutions = Dict.fromList [ (0, LVal 2) ] , next = 1 } Empty) 25 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 26

Slide 26 text

Goals: conjoin conjoin : Goal a -> Goal a -> Goal a conjoin g1 g2 = \state -> bind (g1 state) g2 26 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 27

Slide 27 text

Utilities: bind bind : Stream a -> Goal a -> Stream a bind stream goal = case stream of Empty -> zero Mature state next -> mplus (goal state) (bind next goal) 27 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 28

Slide 28 text

Using conjoin callFresh (\out -> conjoin (identical out (LVal 1)) (identical out (Lval 2)) ) init This fails (1 ≠ 2), so we get Empty 28 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 29

Slide 29 text

What About Infinite Streams? type Stream a = Empty | Immature (() -> Stream a) | Mature (State a) (Stream a) zzz : Goal a -> Goal a zzz goal = \stream -> Immature <| \_ -> goal stream 29 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 30

Slide 30 text

Infinite Streams With mplus mplus : Stream a -> Stream a -> Stream a mplus s1 s2 = case s1 of Empty -> s2 Immature next -> Immature <| \_ -> mplus s2 (next ()) Mature state stream -> Mature state (mplus s2 stream) 30 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 31

Slide 31 text

Infinite Streams With bind bind : Stream a -> Goal a -> Stream a bind stream goal = case stream of Empty -> zero Immature next -> Immature <| \_ -> bind (next ()) goal Mature state next -> mplus (goal state) (bind next goal) 31 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 32

Slide 32 text

Using Infinite Streams (naïve) fives : Term number -> Goal number fives term = disjoin (identical term (LVal 5)) (fives term) This recursive definition works, but causes a stack overflow. fives has to be evaluated in order to evaluate fives. 32 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 33

Slide 33 text

Using Infinite Streams (still naïve) fives : Term number -> Goal number fives term = disjoin (identical term (LVal 5)) (zzz <| fives term) Still overflows because fives has to be evaluated to send to zzz, in which fives has to be evaluated to send to zzz and so on. 33 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 34

Slide 34 text

Utilities: lazy lazy : (() -> Goal a) -> Goal a lazy goal = \state -> (goal ()) state 34 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 35

Slide 35 text

Utilities: lazy lazy : (() -> Goal a) -> Goal a lazy goal = \state -> (zzz <| goal ()) state 35 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 36

Slide 36 text

Using Infinite Streams fives : Term number -> Goal number fives term = disjoin (identical term (LVal 5)) (lazy <| \_ -> fives term) 36 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 37

Slide 37 text

Using Infinite Streams natStartingWith : Int -> Term Int -> Goal Int natStartingWith n term = disjoin (identical term (LVal n)) (lazy <| \_ natStartingWith (n + 1) term) nat = natStartingWith 0 37 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 38

Slide 38 text

Using Infinite Streams callFresh nat init == Mature { subs = Dict.fromList [ ( 0, LVal 0) ] , next = 1 } (Immature ) 38 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 39

Slide 39 text

That's all of µKanren but not the whole paper Questions? 39 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 40

Slide 40 text

Recovering miniKanren: disjoinAll disjoinAll : List (Goal a) -> Goal a disjoinAll goals = \state -> case goals of g :: rest -> disjoin (g state) (disjoinAll rest) [] -> zero 40 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 41

Slide 41 text

Recovering miniKanren: conjoinAll conjoinAll : List (Goal a) -> Goal a conjoinAll goals = \state -> case goals of g :: rest -> conjoin (g state) (conjoinAll rest) [] -> zero 41 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 42

Slide 42 text

Recovering miniKanren: conde conde : List ( Goal a, List (Goal a) ) -> Goal a conde = List.map (\( condition, body ) -> condition :: body) >> List.map conjoinAll >> disjoinAll 42 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 43

Slide 43 text

Recovering miniKanren: fresh fresh1 : (Term a -> Goal a) -> Goal a fresh1 fn = callFresh fn fresh2 : (Term a -> Term a -> Goal a) -> Goal a fresh2 fn = fresh1 (\v1 -> callFresh <| fn v1) fresh3 : (Term a -> Term a -> Term a -> Goal a) -> Goal a fresh3 fn = fresh2 (\v1 v2 -> callFresh <| fn v1 v2) 43 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 44

Slide 44 text

Recovering miniKanren: pull pull : Stream a -> Stream a pull stream = case stream of Immature next -> pull <| next () _ -> stream 44 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 45

Slide 45 text

Recovering miniKanren: takeAll takeAll : Stream a -> Stream a takeAll = case stream of Empty -> Empty Immature _ -> takeAll <| pull stream Mature state stream -> Mature state (takeAll stream) 45 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 46

Slide 46 text

Recovering miniKanren: take take : Int -> Stream a -> Stream a take n = if n == 0 then Empty else case stream of Empty -> Empty Immature _ -> take n <| pull stream Mature state stream -> Mature state (take (n - 1) stream) 46 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 47

Slide 47 text

Recovering miniKanren: reify reify : State a -> Term a reify state = walk (LVar 0) state.subs 47 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 48

Slide 48 text

Recovering miniKanren: run and run* runAll : (Term a -> Stream a) -> List (Term a) runAll = flip fresh1 init >> takeAll >> toList >> List.map reify run : Int -> (Term a -> Stream a) -> Stream a -> List (Term a) run n = flip fresh1 init >> take n >> toList >> List.map reify 48 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 49

Slide 49 text

Using miniKanren! (run 1 (out) (fresh (x) (== out x) (== 3 x))) 49 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 50

Slide 50 text

Using miniKanren! run 1 <| \out -> fresh1 <| \x -> conjoin (identical out x) (identical x (LVal 3)) -- output: [3] 50 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 51

Slide 51 text

Using miniKanren! run 2 <| \out -> fresh1 <| \x -> conjoin (identical out x) (disjoin (identical x (LVal 3)) (identical x (LVal 4))) -- output: [3, 4] 51 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 52

Slide 52 text

Next: My Enhancements Questions? 52 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 53

Slide 53 text

Renames From To Why? mplus interleave a b a b a b a b a b bind andThen Elm convention disjoin / disjoinAll either / any either/any condition can succeed conjoin / conjoinAll both / all both/all conditions must succeed zzz infinite really only useful for infinite lists 53 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 54

Slide 54 text

Error Handling Error Handling! type Error a = CouldNotUnify (Term a) (Term a) type alias State a = Result (Error a) { substitutions : Substitution a , nextVar : Var } 54 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 55

Slide 55 text

Error Handling 55 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 56

Slide 56 text

Error Handling 56 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 57

Slide 57 text

Our First Example, Again relation : List ( a, a ) -> Term a -> Term a -> Goal a relation tuples left right = tuples |> List.map (\( a, b ) -> both (identical (LVal a) left) (identical (LVal b) right) ) |> any 57 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 58

Slide 58 text

Our First Example, Again motherChild : Term String -> Term String -> Goal String motherChild = relation [ ( "trude", "sally" ) ] fatherChild : Term String -> Term String -> Goal String fatherChild = relation [ ( "tom", "sally" ) , ( "tom", "erica" ) , ( "mike", "tom" ) ] 58 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 59

Slide 59 text

Our First Example, Again parentChild : Term String -> Term String -> Goal String parentChild parent child = either (motherChild parent child) (fatherChild parent child) sibling : Term String -> Term String -> Goal String sibling x y = fresh1 <| \parent -> both (parentChild parent x) (parentChild parent y) 59 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 60

Slide 60 text

Our First Example, Again run 1 <| \child -> sibling (LVal "erica") child -- [ LVal "sally" ] 60 Brian Hicks for Papers we Love St. Louis, as presented May 2017

Slide 61

Slide 61 text

Thanks! 61 Brian Hicks for Papers we Love St. Louis, as presented May 2017