Upgrade to Pro — share decks privately, control downloads, hide ads and more …

µKanren: A Minimal Functional Core for Relation...

µKanren: A Minimal Functional Core for Relational Programming

Brian Hicks

May 15, 2017
Tweet

More Decks by Brian Hicks

Other Decks in Programming

Transcript

  1. µKanren A Minimal Functional Core For Relational Programming 1 Brian

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

    Brian Hicks for Papers we Love St. Louis, as presented May 2017
  3. 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
  4. 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
  5. 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
  6. 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
  7. µ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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. Using Infinite Streams callFresh nat init == Mature { subs

    = Dict.fromList [ ( 0, LVal 0) ] , next = 1 } (Immature <function>) 38 Brian Hicks for Papers we Love St. Louis, as presented May 2017
  39. That's all of µKanren but not the whole paper Questions?

    39 Brian Hicks for Papers we Love St. Louis, as presented May 2017
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. Using miniKanren! (run 1 (out) (fresh (x) (== out x)

    (== 3 x))) 49 Brian Hicks for Papers we Love St. Louis, as presented May 2017
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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