Don't Implement Unification by Recursion
The article discusses implementing unification in formal methods, emphasizing iterative approaches, state management, and variable substitutions. It contrasts unification with pattern matching and explores substitution strategies, providing Python code examples.
Read original articleThe article discusses the challenges and considerations involved in implementing unification, a process used in formal methods to solve equations. It highlights that while unification is often associated with recursive functional programming, it can be more effectively implemented using an iterative, imperative style. The author argues that unification involves complex state management and can benefit from a "todo queue" approach, which allows for better handling of variable substitutions. The article contrasts unification with pattern matching, noting that the latter is generally easier to implement. It also explores different strategies for substitution, including eager and lazy methods, and presents various implementations of unification in Python using the Z3 library. The author emphasizes the importance of choosing the right approach based on the specific requirements of the problem at hand, particularly in more complex domains like higher-order unification. The discussion includes code examples to illustrate the differences between recursive and iterative implementations, as well as the implications of using different data structures for managing the unification process.
- Unification is more effectively implemented in an iterative style rather than a recursive one.
- The use of a "todo queue" can simplify the management of variable substitutions.
- Pattern matching is generally easier to implement than unification.
- Different strategies for substitution (eager vs. lazy) can impact performance and complexity.
- The article provides practical code examples using the Z3 library to illustrate unification concepts.
Related
Unification in Elixir
Pattern matching in Elixir, inspired by Erlang and Prolog, includes unification for equality assertion and variable assignment. Unification handles terms, variables, and lists, allowing for symbolic equation solving efficiently.
Finding Simple Rewrite Rules for the JIT with Z3
At PLDI conference, CF Bolz-Tereick presented a paper with Max Bernstein on using Z3 for superoptimization. They encoded PyPy's JIT integer operations into Z3 formulas to find and prove simplification rules efficiently.
Primitive Recursive Functions for a Working Programmer
The article discusses Turing completeness in programming languages, arguing that non-Turing complete languages can effectively solve practical problems. It explores theoretical implications, Turing machines, and finite state automata, emphasizing computational equivalence.
Lisp's Grandfather Paradox
The article explores primitive recursion's historical context, key contributors, foundational functions, limitations, and implications in programming languages, emphasizing experiential learning and philosophical connections to Lisp.
Design Patterns Are Temporary, Language Features Are Forever
The article examines programming paradigms, emphasizing that modern features in languages like Java 21 can render certain design patterns obsolete, enhancing code readability and maintainability while simplifying problem-solving.
By all means, you should be familiar with the evaluation semantics of your runtime environment. If you don’t know the search depth of your input or can’t otherwise constrain stack height beforehand beware the specter of symbolic recursion, but recursion is so much more.
Functional reactive programs do not suffer from stack-overflow on recursion (implementation details notwithstanding). By Church-Turing every sombolic-recursive function can be translated to a looped iteration over some memoization of intermeduate results. The stack is just a special case of such memoization. Popular functional programming patterns provide other bottled up memoization strategies: Fold/Reduce, map/zip/join, either/amb/cond the list goes on (heh). Your iterating loop is still traversing the solution space in a recursive manner, provided you dot your ‘i’s and carry your remainders correctly.
Heck, any feedback circuit is essentially recursive and I have never seen an IIR-filter blow the stack (by itself, mind you).
I came to a similar conclusion, my implementation uses recursion but also leans on shared mutable variables, which is something I usually avoid especially for more "mathy" code. https://jasonhpriestley.com/short-simple-unification
Isn’t this why we have the State monad, or even just fold-like functions? There are multiple ways to tame this problem without resorting to mutable state and side-effects. We know how that deal with the devil usually turns out.
(And I have no idea what "spooky action at a distance" means in this context. If anything, functional programming eliminates such shenanigans by expressing behavior clearly in the type system.)
This is the Curry–Howard correspondence.
While it seems everyone wants to force individuals into one of the formal vs constructivist, it is best to have both in your toolbox.
Contex and personal preference and ability apply here, but in general it is easier for us mortals to use the constructivist approach when programming.
This is because, by simply choosing to not admit PEM a priori, programs become far more like constructive proofs.
If you look at the similarities between how Church approached the Entscheidungsproblem, the equivalence of TM and lambda calculus, the implications of Post's theorm and an assumption of PEM, it will be clear that the formalist approach demands much more of the programmer.
Functional programming grew out of those proofs-as-programs concepts, so if you prefer functional styles, the proofs are the program you write.
Obviously the above has an absurd amount of oversimplification, but I am curious what would have been different if one had started with more intuitional forms of Unification.
For me, I would have probably just moved to the formalist model to be honest like the author.
I just wanted to point out there is a real reason many people find writing algorithms in a functional/constructivist path.
Related
Unification in Elixir
Pattern matching in Elixir, inspired by Erlang and Prolog, includes unification for equality assertion and variable assignment. Unification handles terms, variables, and lists, allowing for symbolic equation solving efficiently.
Finding Simple Rewrite Rules for the JIT with Z3
At PLDI conference, CF Bolz-Tereick presented a paper with Max Bernstein on using Z3 for superoptimization. They encoded PyPy's JIT integer operations into Z3 formulas to find and prove simplification rules efficiently.
Primitive Recursive Functions for a Working Programmer
The article discusses Turing completeness in programming languages, arguing that non-Turing complete languages can effectively solve practical problems. It explores theoretical implications, Turing machines, and finite state automata, emphasizing computational equivalence.
Lisp's Grandfather Paradox
The article explores primitive recursion's historical context, key contributors, foundational functions, limitations, and implications in programming languages, emphasizing experiential learning and philosophical connections to Lisp.
Design Patterns Are Temporary, Language Features Are Forever
The article examines programming paradigms, emphasizing that modern features in languages like Java 21 can render certain design patterns obsolete, enhancing code readability and maintainability while simplifying problem-solving.