As you see from other answers, there is a straight-forward way to express this with Prolog, using either disjunctions (leaving choice-points), or by explicitly reasoning about sets and their intersections.
Another way is to use constraints, which let you defer goals until more is known: You can for example map your atoms to integers, and then express a variable's membership to a set as its having a specific domain of integers, using CLP(FD) constraints. Alternatively, you can implement a custom constraint solver that reasons over atoms, using attributed variables or Constraint Handling Rules (CHR). The key advantage in both cases is that you gain more freedom to reorder your goals, and the constraint reasoning is implicitly invoked when further constraints are posted.
EDIT: As an example, consider using CLP(FD) constraints to solve your task. With at most minimal modifications, the following example works in SICStus, SWI, YAP and other systems. Depending on your Prolog system, you may need to import suitable libraries to use some of these predicates:
fruit_integer(apple, 0). fruit_integer(pear, 1). fruit_integer(orange, 2). variable_fruits(Var, Fruits) :- maplist(fruit_integer, Fruits, Integers), foldl(domain_, Integers, 1..0, D), Var in D. domain_(E, D0, D0 \/ E).
The key idea in this case is to map fruits to integers, so that you can use CLP(FD) constraints to express everything that holds.
Your example queries and answers::
?- variable_fruits(X, [apple,pear]), fruit_integer(apple, X). X = 0. ?- variable_fruits(X, [apple,pear]), fruit_integer(apple, X), fruit_integer(pear, X). false. ?- variable_fruits(X, [apple,pear,orange]), variable_fruits(X, [apple,orange]). X in 0\/2. ?- variable_fruits(X, [apple,orange]), variable_fruits(X, [pear,orange]). X = 2. ?- variable_fruits(X, [apple,orange]), fruit_integer(pear, X). false.
Obviously, you can use fruit_integer/2
also in the other direction, and convert such integers and domains back to lists of atoms. I leave this as an easy exercise.
It is for this reason that CLP(FD) constraints are called constraints over finite domains: All finite domains can be mapped to finite subsets of integers. Hence, CLP(FD) constraints are not only useful to express integer arithmetic in general, but also to reason about arbitrary finite sets. See clpfd for more information.
A few additional notes:
- All most widely used Prolog systems ship with CLP(FD) constraints, making your solution quite portable if you use them.
- Some Prolog systems ship with dedicated constraints solvers for sets. This may be worth looking into if your system supports this.
- CHR is a useful language for defining custom propagation rules and is well worth looking into especially for more complex tasks.
- Using the attributed variables interface directly will render your solution less portable, so I recommend you use one of the higher-level approaches instead. Try CLP(FD) first (since it is easiest to apply), then have a look at CHR, and only then consider implementing a custom solver.