O'Keefe's Revenge - Methods of Programming

At page 115 of TCOP, O’Keefe writes:

The general scheme for this (he means subsetting a list of items) is:

subset([], []).
subset([Item|Items], [Item|Subset]) :-
               desirable(Item),
               subset(Items, Subset).
subset([Item|Items], Subset) :-
               undesirable(Item),
               subset(Items, Subset).

In my head (don’t know if there is any correspondence to reality) instead of calling a predicate in the body of a rule such as desirable(Item) or undesirable(Item), using some kind of “matching” in the head of the rule itself would make things more efficient, like the following:

subset([], []).
subset([desirable(Item)|Items], [Item|Subset]) :-
               subset(Items, Subset).
subset([undesirable(Item)|Items], Subset) :-
               subset(Items, Subset).

Is it correct or is it just a fancy of mine and in fact it doesn’t make any difference?
Thanks

You should try it out with a concrete example. Please tell us what you find out.

1 Like

That was very helpful…and I like it when someone speaks in terms of “we”…

From the point of view of the user of the predicate, the name doesn’t really tell me what it does (desirable is hidden in the definition, rather than the predicate being named desirable_subset), so its a bad example. However I’m not sure of why you think desirable(Item) is evaluated in your second example, it isn’t. However, trying to move the filter logic outside the predicate is what the include/3 and exclude/3 predicates do.
e.g.

include(desirable,MyList,DesiredList).

or

exclude(undesirable,MyList,DesiredList).
2 Likes

That’s the predicate O’Keefe himself uses to abstract from any given predicate one might be interested in…his starting point is the so-called Dutch National Flag problem in which items should be arranged based on their color (in that case the predicates would be red(Item), white(Item) and blue(Item). The general question was whether avoiding to call predicates in a rule’s body and using pattern-matching in the rule’s head is generally more efficient or not…my guts feeling, so to say, tends to say yes, but I was hoping in an answer from the experts

Please try it out. The textbook solution and your proposed alternative are different in meaning. It will become obvious if you programmed a small concrete example. If the results surprise you please ask us, the readers of your messages, for further help.

1 Like

So possibly I’ll have to use a big list of items and time the two alternatives to see if there’s (or there isn’t) a significant change in terms of performance

No, a very small example will show you that what you propose does something different. Regardless of speed.

1 Like

This appears to be inelegantly and inefficiently checking desirability twice.

That is O’Keefe’s…

If this bothers you, write it like this (O’Keefe knows how to write efficient code, but that obscures the point he was making in the book):

subset([], []).
subset([Item|Items], SubsetResult) :-
(  desirable(Item)
*-> SubsetResult = [Item|Subset]
;   SubsetResult = Subset
), 
subset(Items, Subset).

You could write the code like this (substituting the “desirable” predicate’s name for “condition”):

subset_on_condition([], []).
subset_on_condition([Item|Items], [Item|Subset]) :-
               condition(Item),
               subset_on_condition(Items, Subset).
subset_on_condition([Item|Items], Subset) :-
               \+ condition(Item),
               subset_on_condition(Items, Subset).

@anon419083 - I suggest taking @Boris 's advice – try your variant with a concrete example. Then try mine with a concrete example.

More general code is:

subset(_Condition, [], []).
subset(Condition, [Item|Items], [Item|Subset]) :-
               call(Condition, Item),
               subset(Condition, Items, Subset).
subset(Condition, [Item|Items], Subset) :-
               \+ call(Condition, Item),
               subset(Condition, Items, Subset).

But introducing this diverts the textbook from a general pattern of processing lists to a discussion of metapredicates.

1 Like

Here is a ?-trace. call on your script:

marco subset

I ran the same trace and list on O’Keefe’s script (I can only embed one picture), and the initial call was exactly the same so it looks like the initial call structure of ?- subset(Item,Items). results in the same initial call.

I would recommending defining desirable/1 and undesirable/1 in both scripts and testing it on short lists, as others have recommended. Please let us know how it goes!

The O’Keefe call and list:

okeefe subset

Well as a side observation: today tracing some queries with @peter.ludemann’s definition for subset_on_condition I was surprised to see how the not provable predicate (\+) is traced:

?- trace.
true.

[trace]  ?- subset_on_condition([skull, store, scare, slope],X).
   Call: (12) subset_on_condition([skull, store, scare, slope], _22032) ? creep
   Call: (13) red(skull) ? creep
   Exit: (13) red(skull) ? creep
   Call: (13) subset_on_condition([store, scare, slope], _23400) ? creep
   Call: (14) red(store) ? creep
   Fail: (14) red(store) ? creep
   Redo: (13) subset_on_condition([store, scare, slope], _23400) ? creep
   Call: (14) red(store) ? creep
   Fail: (14) red(store) ? creep
   Redo: (13) subset_on_condition([store, scare, slope], _23400) ? creep
   Call: (14) subset_on_condition([scare, slope], _23400) ? creep
   Call: (15) red(scare) ? creep
   Exit: (15) red(scare) 

When the second clause is called why does it fail instead of succeeding? Given the following facts:

red(rain). 
red(sector). 
red(mask).
red(skull). 
red(scare). 
red(light).

\+ red(store) should succeed, right? I don’t know, I don’t find the trace particularly clear in this respect…but this is a side observation. I’ll try to understand things better for the general matter.

PS: the trace doesn’t make clear that the first time red(store) is called, but the second time \+ red(store) is called

If you replace the \+red(Item) with not(red(Item)), the trace might make more sense to you.

BTW, you don’t need to type creep – just hit return.

(There was a typo in my code, but I presume you fixed that)

PS, you might find the graphic debugger gtrace/0 to be easier to understand than the classic trace/0.

(The reason that not/1 shows differently when traced is than (\+)/1 is that the latter is compiled but the former is interpreted.)

1 Like

I don’t type it. I hit return and creep gets added at the end of line…

Again at pg.115 he writes:

“Let’s take the case of finding blue items. There is one way that an item can be desirable: it can be blue. There are two ways it can be undesirable: it can be red or it can be white”
Version 1

blue_items([], []).
blue_items([Item|Items], [Item|Subset]) :-
                blue(Item),
                blue_items(Items, Subset).
blue_items([Item|Items], Subset) :-
                ( red(Item) ; white(Item) ),
                blue_items(Items, Subset).

Version 2

blue_items([], []).
blue_items([Item|Items], [Item|Subset]) :-
                blue(Item),
                blue_items(Items, Subset).
blue_items([Item|Items], Subset) :-
                red(Item),
                blue_items(Items, Subset).
blue_items([Item|Items], Subset) :-
                white(Item),
                blue_items(Items, Subset).

Now my question doesn’t have to do with the Dutch National Flag problem, but rather (a classic) with choice points. In Version 1 we have three clauses, in Version 2 four ones. Does it make a difference relative to the choice points left? Because if it does, then Version 1 would leave fewer choice points and would be therefore more deterministic than Version 2? Thanks

There will be slightly different choice points between Version 1 and Version 2. Version 2 will leave a choice point after the head in the 3rd clause; Version 1 will leave a choice point at the semi-colon in red(Item);white(Item). You should be able to see this if you trace the code.

1 Like