Correcting a Widespread Error in Unification Algorithms
Peter Norvig
Sandy Vanderbleek
Data Scientist, Publicis Media
sandy.vanderbleek@publicismedia.com
hiring data engineers/data scientists/web developers 🙏
twitter.com/haskellandchill
Approach
- Establish notation
- Define problem
- Identify error and correct result
- Analyze error and correct algorithm
(a b c)
p'
p''
p?
{p => q, r => s}
{}[p => q]
{}[p]
{}/p
a \in b
pattern
first
rest
check single
substitution
update
lookup
apply
element
Notation
Examples
(a) \in {(a) => (b)} == true
(a) \in (a b) == true
(a b c)' == (a)
(a b c)'' == (b c)
(a)? == true
{a => b}/(a) == (b)
(a) \in {} == false
(a) \in (b c) == false
(a)' == (a)
(a)'' == ()
()' == ()
()'' == ()
(a b)? == false
()? == false
{a => b}/(c) == (c)
Problem
unify a b == s
unify a b == fail
.:. s/a == s/b
.:. ~E s | s/a == s/b
unify (a) (a) == {}
unify (a) (b) == {a => b}
unify (a a) (a) == fail
unify (a) (b b) == {a => b b}
- Typically many answers possible, most general unifier can generate them
- Wikipedia page is a good overview
Syntactic Unification
Error
unify (x y) (y x) == fail
Present in algorithm in several popular textbooks including Norvig's (twice) and SICP
Caused by failing to dereference bindings
Correct
unify (x y) (y x) == {(x) => (y)}
{x => y}/(x y) == (y y)
{x => y}/(y x) == (y y)
Wrong
unify p q s =
p == q -> s
p? -> variable p q s
q? -> variable q p s
_ -> unify p'' q'' (unify p' q' s)
variable a p s =
a \in s -> unify s[a] p s
a \in s[p] -> fail
_ -> s[a => p]
Correct
unify p q s =
p == q -> s
p? -> variable p q s
q? -> variable q p s
_ -> unify p'' q'' (unify p' q' s)
variable a p s =
a \in s -> unify s[a] p s
p \in s -> unify a s[p] s
a \in s[p] -> fail
_ -> s[a => p]
Evaluation
unify (y) (x) (unify (x) (y) {})
by _ -> unify p'' q'' (unify p' q' s)
as unify (y) (x) (unify (y) (x))
unify (y) (x) {x => y}
by p? -> variable p q s
as (y)? -> variable (y) (x) {}
by _ -> s[a => p]
as _ -> {}[y => x]
fail
by a \in s[p] -> fail
as (y) \in {x => y}[x] -> fail
Error 😱
Evaluation
...
unify (y) (y) {x => y}
by p \in s -> unify a s[p] s
as (x) \in {x => y} -> unify (y) {x => y}[x] {x => y}
{(x) => (y)}
by p == q -> s
as (y) == (y) -> {x => y}
Correct 😇
Norving's Hot Take
Functional Programming advocates have claimed that assignment (to a variable or data structure) is dangerous because it violates referential transparency. Curiously, this note shows that for unification, the functional approach is error-prone, while the procedural, state-modification approach tends to lead to a correct solution.
🔥
Serious Next Day Thread
They all failed to either test the resulting code sufficiently or attempt even an informal proof of correctness. Either approach could have uncovered the bug that has remained hidden until now.
✅
Future Work
A proof of correctness for unification in Athena
in my talk "Athena: An Educational Language For Proofs In Computer Science"
to be delivered in Boulder June 4th 2018 and put online... someday 🙃
http://proofcentral.org/athena/
Fundamental Proof Methods in Computer Science