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
Well, all I can say is that the way to solve a problem depends largely on the problem itself. There is a set of problems which are amenable to solve using recursion, where Prolog is well suited to solve them.
In this kind of problems, one can derive a solution to a larger problem by dividing it in two or more case classes.
In one class we have the "base cases", where we provide a solution to the problem when the input cannot be further divided into smaller cases.
The other class is the "recursive cases", where we split the input into parts, solve them separately, and then "join" the results to give a solution to this larger input.
In the example for flatten/2 we want to take as input a list of items where each item may also be a list, and the result shall be a list containing all the items from the input. Therefore we split the problem in its cases. We will use an auxiliary argument to hold the intermediate flattened list, and thats the reason why we implement flatten/3.
Our flatten/2 predicate will therefore just call flatten/3 using an empty list as a starting intermediate flattened list:
flatten(List, Flattened):-
flatten(List, [], Flattened).
Now for the flatten/3 predicate, we have two base cases. The first one deals with an empty list. Note that we cannot further divide the problem when the input is an empty list. In this case we just take the intermediate flattened list as our result.
flatten([], Flattened, Flattened).
We now take the recursive step. This involves taking the input list and dividing the problem in two steps. The first step is to flatten the first item of this input list. The second step will be to recursively flatten the rest of it:
flatten([Item|Tail], L, Flattened):-
flatten(Item, L1, Flattened),
flatten(Tail, L, L1).
Ok, so the call to flatten(Item, L1, Flattened) flattens the first item but passes as intermediate list an unbound variable L1. This is just a trickery so that at the return of the predicate, the variable L1 still remain unbounded and Flattened will be of the form [...|L1] where ... are the flattened items of Item.
The next step, which calls flatten(Tail, L, L1) flattens the rest of the input list and the result is bounded with L1.
Our last clause is really another base case, the one that deals with single items (which are not lists). Therefore we have:
flatten(Item, Flattened, [Item|Flattened]):-
\+ is_list(Item).
which checks whether item is a list and when it is not a list it binds the result as a list with head=Item and as tail the intermediate flattened list.