the logical burrito - pattern matching, term rewriting and unification
TRANSCRIPT
The Logical Burrito Episode I: The Road to Unification
an exploration of pattern matching, term rewriting and unification (wrapped up in a warm flour tortilla)
Norman Richards [email protected]
@MaximoBurrito
Goals• Explore unification as groundwork for understanding logic
programming in general and core.logic (mini-kanren based) in specific
• Rather than focus on unification, this is a survey of some loosely related (in my mind) topics - pattern matching and and term rewriting
• Will demonstrate these ideas in other languages: ML for pattern matching, Mathematica for term rewriting, and Prolog for unification
Pattern Matching
as a dispatch mechanism (or simple conditional) a way to express the selection criteria for which code to execute
• In Clojure, dispatch is on arity: (defn foo ([x] …) ([x y] …))
• you can destructure but you can’t ask questions about the types or value
• OO dispatch - based on type of first arg (.getArea shape)
• Multiple dispatch - based on type of all args (method overriding is compile time) multimethods
Dispatch
Multimethods
(defmulti fact identity)
(defmethod fact 0 [_] 1)
(defmethod fact :default [n] (* n (fact (dec n))))
Multimethods
(defmulti fizzbuzz identity)
(defmethod fizzbuzz 1 [n] (str n)) (defmethod fizzbuzz 2 [n] (str n)) (defmethod fizzbuzz 3 [n] "fizz")
;; ... this isn't going to work
Multimethods;; we can make arbitrary dispatch rules (defmulti fizzbuzz (fn [n] [(zero? (mod n 3)) (zero? (mod n 5))])) ;; as long as we can match the defaults (defmethod fizzbuzz [true true] [n] "fizzbuzz") (defmethod fizzbuzz [true false] [n] "fizz") (defmethod fizzbuzz [false true] [n] "buzz") (defmethod fizzbuzz [false false] [n] (str n))
Multimethods
(defmulti beats vector)
;; or if we have a single default value (defmethod beats :default [m1 m2] false) (defmethod beats [:paper :rock] [m1 m2] true) (defmethod beats [:rock :scissors] [m1 m2] true) (defmethod beats [:scissors :paper] [m1 m2] true)
Multimethods
• Multimethods are great for what they do, but they aren't a replacement for pattern maching
• Clojure doesn't have great pattern matching, so we'll consider ML
Pattern Matching (SML)
(* SML matching at the function level*)
fun fac 0 = 1 | fac n = n * fac (n - 1);
Pattern Matching (SML)val div_3 = div_by 3; val div_5 = div_by 5; (* No guard statements, but we can match in the body of the function on derived values *) fun fizzbuzz n = case (div_3 n, div_5 n) of (true, true) => "FizzBuzz" | (true, false) => "Fizz" | (false,true) => "Buzz" | _ => Int.toString n (* underscore matches anything *)
Pattern Matching (SML)datatype suit = Clubs | Diamonds | Hearts | Spades datatype rank = Jack | Queen | King | Ace | Num of int type card = suit * rank datatype color = Red | Black (* non-default don't cares exceed what multimethods can do *) fun card_color (Diamonds, _) = Red | card_color (Hearts , _) = Red | card_color _ = Black (* note deep destructuring *) fun card_value (_, Num(n)) = n | card_value (_, Ace) = 11 | card_value _ = 10
core.match(defn card_color [[suit value]] (match [suit value] [:diamonds _] :red [:hearts _] :red [_ _] :black)) (defn card_value [card] (match card [_ (n :guard number?)] n [_ :ace] 11 :else 10))
Limitations of core.match
• No defnm to use at language level
• Syntax can be ugly
• Can't match non-linear patterns: [n n]
(define eval-exp (lambda (expr env) (pmatch expr [,x (guard (symbol? x)) ;; variable (lookup x env)] [(quote ,datum) datum] [(list . ,expr*) ;; (map (lambda (expr) (eval-exp expr env)) expr*) (eval-exp* expr* env)] [(lambda (,x) ,body) ;; abstraction `(closure ,x ,body ,env)] [(,e1 ,e2) ;; application (let ((proc (eval-exp e1 env)) (val (eval-exp e2 env))) (pmatch proc [(closure ,x ,body ,envˆ) ;; evaluate body in an extended environment (eval-exp body `((,x . ,val) . ,envˆ))] [,else (error 'eval-exp "e1 does not evaluate to a procedure")]))])))
A scheme example (pmatch)
Term rewriting
Term rewriting is a branch of theoretical computer science which combines elements of logic, universal algebra, automated theorem proving and functional programming. [...] This constitutes a Turing-complete computational model which is very close to functional programming.
- Term Rewriting and All That
Mathematica
The innermost kernel of the Mathematica language is essentially nothing else than a higher-order, conditional rewrite language, efficiently and professionally implemented.
- Mathematica as a Rewrite Language (Bruno Buchberger)
Rewriting in Mathematica
In[1]:= sum[m_, 0] := m; sum[m_, s[n_]] := s[sum[m, n]];
In[3]:= sum[s[s[0]], s[s[s[s[0]]]]]
Out[3]= s[s[s[s[s[s[0]]]]]]
Rewriting in Mathematica
In[4]:= map[f_, {}] := {}; map[f_, {x_, xs___}] := Prepend[map[f, {xs}], f[x]]; In[6]:= map[something, {1, 2, 3, 4, 5}] Out[6]= {something[1], something[2], something[3], something[4], something[5]}
Rewriting in Mathematica
In[7]:= something[n_] := dont_care /; OddQ[n] In[8]:= map[something, {1, 2, 3, 4, 5}] Out[8]= {dont_care, something[2], dont_care, something[4], dont_care}
Rewriting in Clojure (termito)
(defrules s-rule [(+ ?x 0) ?x] [(+ ?x (s ?y)) (s (+ ?x ?y))])
(simplify '(+ (s (s 0)) (s (s (s (s 0))))) s-rule)
;; (s (s (s (s (s (s 0))))))
Rewriting in Clojure (termito)(defnc numbers [x] (number? x)) (defnc commutative? [op] (or (= op '*) (= op '+))) (defnc associative? [op] (or (= op '*) (= op '+)))
(defrules zero-rules [(* 0 ?x) 0]) (defrules identity-rules [(* 1 ?x) ?x] [(+ 0 ?x) ?x]) (defrules associative-rules [(?op ?x (?op ?y ?z)) :when [?op ~associative? #{?x ?y} ~numberc] (?op (?op ?x ?y) ?z)])
Unification
Unification is a basic operation on terms. Two terms unify if substitutions can be made for any variables in the terms so that the terms are made identical. If no such substitution exists, then the terms do not unify.
- Clause and Effect
Unification with Prolog
?- 42 = 42. true.
?- six_times_nine = 42. false.
?- enough = enough. true.
Constants unify with themselves
Unification with Prolog
?- City = austin. City = austin.
?- 7 = Number. Number = 7.
uninstantiated logic variables unify with constants.
Unification with Prolog
?- X = Y. X = Y.
?- A=B, C=D, C=B. A = B, B = C, C = D.
uninstantiated variables unify with each other and co-refer
Unification with Prolog
?- A=B, C=D, C=B, A=pi. A = B, B = C, C = D, D = pi.
?- A=B, C=D, C=B, A=pi, D=tau. false.
uninstantiated variables unify with each other and co-refer
Unification with Prolog
?- foo(1) = x. false.
?- foo(1) = foo(2). false.
?- foo(X) = bar(X). false.
?- foo(X) = foo(Y). X = Y.
complex terms unify if they have the same head and all their parts unify
Unification with Prolog?- foo(X,Y)=foo(2,3). X = 2, Y = 3.
?- foo(X,5) = foo(7,Y). X = 7, Y = 5.
?- foo(X,5) = foo(9,X). false.
Unification with Prolog
?- foo(A,B,B) = foo(B, B, A). A = B.
?- foo(bar(Z)) = foo(X), X= bar(baz). Z = baz, X = bar(baz).
?- f(X, a(b,c)) = f(d, a(Z,c)). X = d, Z = b.
Unification with Clojure
• core.logic unifier has more features, more actively maintained
• core.unify has the prettier API, less baggage
core.unify vs core.logic
• core.logic • (unify [1 '?x]) => 1 • (unifier [1 '?x]) ==> {?x 1}
• core.unify • (unify 1 '?x) ==> {?x 1} • (unifier 1 '?x) ==> 1
core.logic unification(unifier '[dog dog]) ;; {}
(unifier '[dog cat]) ;; nil
(unifier '[?animal dog]) ;; {?animal dog} (unifier '[?foo ?bar]) ;; {?foo ?bar}
core.logic unification
(unifier '[{:name bob :age 42} {:name ?X :age ?Y}]) ;; {?X bob, ?Y 42} (unifier '[{:name bob :age 42} {:name ?X}]) ;; nil
core.logic unification
(unifier {:when {'?num numberc}} '[[?num ?animal] [:magical :unicorn]]) ;; nil (unifier {:when {'?num numberc}} '[[?num ?animal] [42 :unicorn]]) ;; {?animal :unicorn, ?num 42}
core.logic unification(unifier {:as '{?a 3}} '[?a ?b]) ;; {?b 3, ?a 3} (unifier {:as '{?a 3 ?b 4}} '[?a ?b]) ;; nil
(unifier {:as (unifier '[?a ?b])} '[(?c 5) (?a ?b)]) ;; {?b 5, ?a 5, ?c 5}
(defn unifier+ [& pairs] (loop [u {} pairs pairs] (if (seq pairs) (when-let [u' (unifier {:as u} (first pairs))] (recur (merge u u') (rest pairs))) u)))
(unifier+ '[?x 1] '[?y 2] '[?z [?x ?y]]) ;; {?z [1 2], ?y 2, ?x 1}
core.logic unification
(defn q [data query] (filter #(unifier+ (conj query %)) data))
(def things [{:name "thing 1" :color :red} {:name "thing 2" :color :blue} {:name "thing 3" :color :red}]) (q things '[{:name ?name :color :red}]) ;; ({:color :red, :name "thing 1"} {:color :red, :name "thing 3"})
core.logic unification
Why?
• Logic programming
• Machine Learning / Natural language processing
• Type inferencing / type checking
• Theorem proving / Equation solving
Until the next logical burrito,
(run* [your-location] (conde [(== your-location :home)] [succeed]) (!= your-location :here))
Norman Richards [email protected]
@MaximoBurrito