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

Made with Slides.com