I am studying prolog at university and facing some problems. What I already found out is just solution to a problem. However, I\'m more interested in the way to think, i.e. how
First, I'll show you my approach to the problem, then I've got some resources for learning to think recursively.
Here's my solution to the problem "flatten a list of lists (of lists ...)". I've annotated it to show how I got there:
First, let's define the public interface to our solution. We define flatten/2
. It's body consists of a call to the internal implementation flatten/3, which takes an accumulator, seeded as an empty list.
flatten ( X , R ) :-
flatten ( X , [] , R ) ,
.
That was easy.
The internal predicate flatten/3
is a little more complex, but not very.
First, we have the boundary condition: the empty list. That marks the end of what we need to do, so we unify the accumulator with the result:
flatten( [] , X , X ).
The next (and only) other case is a non-empty list. For this, we examine the head of the list. Our rule here is that it needs to flattened and appended to the result. A good rule of programming is to write descriptive code, and Prolog is itself a descriptive, rather than procedural, language: one describes the solution to the problem and lets the inference engine sort things out.
So...let's describe what needs to happen now, and punt on the mechanics of flattening the head of the list:
flatten( [X|Xs] , T , Y ) :-
flatten_head(X,X1) ,
append( T,X1,T1) ,
flatten( Xs , T1 , Y )
.
That, too, was easy.
That's the essence of the entire solution, right there. We've broken our problem into 3 pieces:
Let's move on to the implementation of how to flatten a single list element. That's easy, too. We've got two cases, here: the list item might be a list, or it might be something else.
First, the list element might be an unbound variable. We don't want untowards behaviour, like unbounded recursion happening, so let's take care of that straightaway, by disallowing unbound terms (for now). If the element is bound, we try to flatten it by invoking our public interface, flatten\2
again (oooooooh...more recursion!)
This accomplishes two things
flatten/2
fails if handed something other than a list.flatten_head/2
is done.
Here's the code:
flatten-head( X , Y ) :-
nonvar(X) ,
flatten( X , Y )
.
Finally, the last case we have to consider is the case of list elements that aren't lists (unbound vars, atoms or some other prolog term). These are already "flat"...all we need to do is wrap them as a single element list so that the caller (flatten\3
) gets consistent semantics for its "return value":
flatten-head( X , [X] ).
Here's the complete code:
flatten ( X , R ) :-
flatten ( X , [] , R )
.
flatten( [] , X , X ) .
flatten( [X|Xs] , T , Y ) :-
flatten_head(X,X1) ,
append( T,X1,T1) ,
flatten( Xs , T1 , Y )
.
flatten-head( X , Y ) :-
nonvar(X) ,
flatten( X , Y )
.
flatten-head( X , [X] ) .
Each individual step is simple. It's identifying the pieces and weaving them together that's difficult (though sometimes, figuring out how to stop the recursion can be less than obvious).
Some Learning Resources
To understand recursion, you must first understand recursion—anonymous
Eric Roberts' Thinking Recursively (1986) is probably the best (only?) book specifically on developing a recursive point-of-view WRT developing software. There is an updated version Thinking Recursively With Java, 20th Anniversary Edition (2006), though I've not seen it.
Both books, of course, are available from the Usual Places: Powell's, Amazon, etc.
You might also want to read Douglas Hofstadtler's classic Gödel, Escher, Bach: An Eternal Golden Braid Some consider it to be the best book ever written. YMMV.
Also available from the Usual Suspects:
A new book, though not directly about recursive theory, that might be useful, though I've not seen it (it's gotten good reviews) is Michael Corballis' The Recursive Mind: The Origins of Human Language, Thought, and Civilization