I just finished a recent war with Prolog. As a veteran procedural programmer of many years I wanted to put down my thoughts while they are still fresh and by fresh I mean I just got my code working minutes ago.
If you are having a war with Prolog it is usually because you are still trying to write your code using procedural logic. Rather than hit you with the canon “think declaratively” and run away as you will find many articles and books on the Web do, I’ll say “think in Prolog’s execution model” and explain what that means. “Think declaratively” has always been a problem for me for two reasons:
- The answers to the question “What does that really mean?” that I have read have always been unsatisfying to me. In other words, I could never use those answers to change my programming habits
- That assertion completely ignores Prolog’s execution model where the order of clauses is critically important
So what do I mean by saying “Think in Prolog’s execution model”?
Rather than express my opinion, a dubious exercise considering my troublesome path with Prolog and the fact that I am certainly not an expert Prolog programmer, I will simply recount the symptoms that manifested during my recent war with Prolog and how I solved the problems with my code that created those symptoms. Note, the problems I had with Prolog were all my fault and were all due to my incorrect thinking that stemmed from my long history of procedural programming.
SYMPTOMS OF PROCEDURAL POISONING IN PROLOG
- You are in a war with Prolog to begin with
- You are trying to do too much in one clause, especially if you find yourself using the disjunction operator (";") to create multiple code paths in the same predicate clause
- You are trying to accumulate solutions for the same goal in too many directions (i.e. – breadth and depth at the same time, etc.)
That last one deserves further expansion because that should have been a warning sign to me that I was coding my logic in a procedural manner and thereby causing myself many hours of pain.
I was trying to accumulate solutions as I iterated a list an elements, one element at a time while using recursion. At the same time I (i.e. – in the same predicate clause) I was also accumulating them by descending down a tree of nodes to also pick up solutions via recursive descent of a node tree. Worse, some of the control path logic was handled by a disjunction in a bloated predicate that was doing way too much. In the middle of this procedural disaster I found myself fighting conflicting unification problems with difference lists and wishing I had chose a different way to earn a living than programming.
I ended up giving up late last night, frustrated and having failed. When I woke in the morning the answer on how to code the logic in harmony with Prolog’s amazing execution model came to me effortlessly. Apparently during sleep I finally decided to stop trying to code the logic my way and do it the Prolog way
THINKING IN HARMONY WITH PROLOG’S POWERFUL EXECUTION MODEL
The main “enlightenment” I had was to stop thinking of my current objective as the accumulation of solutions and to understand that I was actually creating a generator. As soon as I did this I could start shaping my current objective in harmony with Prolog’s execution model. This simple reframing of the problem radically simplified the problem I was trying to solve.
Instead of suffering the burden of trying to build a list of solutions in a painfully complex set of ways all at the same time, I just had to figure out how to create (i.e - "generate) one solution at a time and let findall do the rest of the work. That allowed me to immediately unravel the spaghetti logic that existed in one bloated clause into multiple clauses that were much simpler, with the side benefit of the code being much easier to read and understand now.
This led me to some useful principles in breaking the hold of procedural thinking when refactoring a failed complex logic attempt:
-
Turn over as much of the task to Prolog’s backtracking and recursion model. This is very hard for procedural programmers, at least like myself, because we want micro-control over every step in the control flow.
-
Keep simplifying your code in an iterative manner into smaller pieces of logic. The most common tools here are: recursive descent, backtracking, more clauses for a complex predicate, and more predicates too.
-
Learn to stop worrying and love findall(). Turn your logic into a generator wherever possible and let findall() do all the busy work. If you are iterating a list argument “horizontally” just to get at each element, that frequently indicates you should be using findall() and a generator instead.
SYMPTOMS OF A SUCCESSFUL REFACTORING FROM PROCEDURAL TO PROLOG
- You are shocked at how much of your old code you are deleting and how little code it takes to express the same logic with one notable difference. Now your code works beautifully
- You feel your thoughts changing from “Why the hell doesn’t this code work the way I want it to?” to “How do I restructure my thoughts and code to the way Prolog works?”
- You learn to trust Prolog and let it do all the work. You also find Prolog beautiful.
CONCLUSION
I’ve said this in other places and many times. Prolog is very hard for most veteran procedural programmers to adapt to but it is worth the effort many, many times over. The breathtaking experience you have when you finally succeed is unique and indescribable. I hope this helps some of my fellow veteran procedural programmers out there.