I think there are a few other ways to solve it in addition to JB's recommendation. One cheesy trick is to enforce an ordering over ProductTypeN
by adding this to the end of your query:
ProductType1 @> ProductType2,
ProductType2 @> ProductType3.
This will get you one permutation of the three product types, eliminating the combinatorial explosion you're seeing.
A more sophisticated technique would be to use setof/3
to enumerate solutions. Because it produces all the answers as a set, it must sort the values, which removes duplicates in basically the same fashion as the cheesy trick.
more_than_two_product_types(Manufacturer) :-
setof(Manufacturer, T^manufacturer(T,Manufacturer), Manufacturers),
member(Manufacturer, Manufacturers),
setof(Type, Thing^(manufacturer(Thing, Manufacturer), store(Thing, Type)), [_,_,_|_]).
This is pretty complex so let's break it down. First, this condition produces a list of manufacturers:
setof(Manufacturer, T^manufacturer(T,Manufacturer), Manufacturers),
The setof/3
meta-predicate takes a constructor expression, a template expression, and returns a list of results. This one is going to gather up all the solutions for manufacturer(T, Manufacturer)
and collect them into a list. We're only interested in the name of the manufacturer, so the template argument is just Manufacturer
. The T^
syntax tells setof/3
that T
is a free variable in manufacturer(T, Manufacturer)
, so we don't care what it instantiates to. This is essential, or setof/3
itself will produce one solution for each type, which isn't what we want.
This line iterates the new list of manufacturers:
member(Manufacturer, Manufacturers),
This complex line finds us all the types of products made by a manufacturer:
setof(Type, Thing^(manufacturer(Thing, Manufacturer), store(Thing, Type)), [_,_,_|_]).
The goal expression here is the sequence (manufacturer(Thing, Manufacturer), store(Thing, Type))
. This says, find a Thing
made by this manufacturer and then find the Type
for that thing. Again, the Thing^
syntax says we don't really care what the things are, so get all the Type
solutions at once. Instead of binding this to a list for us to process, the template [_,_,_|_]
unifies with any list with at least three items. We don't really care what those items are, so they're all blank. Test in your console and see what it unifies with:
?- [1,2,3] = [_,_,_|_].
true.
?- [1,2] = [_,_,_|_].
false.
?- [1,2,3,4] = [_,_,_|_].
true.
This is going to generate at least three solutions and then throw them away to succeed, or fail if it generates fewer.
As you can see, there's more than one way to skin a cat with Prolog. :)