my_computer([
case([
motherboard([board(plastic),ports(metal),slots(plastic),capacitors(plastic)]),
power_supply_unit([casing(metal),cables(plastic),connectors(plastic),capacitors(plastic),fan(plastic),transformer(metal)]),
central_processing_unit([board(plastic),fan(plastic),heatsink(metal)]),
random_access_memory([board(plastic)]),
graphics_processing_unit([board(plastic),ports(metal),capacitors(plastic),fan(plastic),heatsink(metal)])
]),
monitor([
lcd_screen(plastic),inverter(plastic),frame(plastic)
]),
keyboard([
key(plastic),frame(plastic),cable(plastic)
]),
mouse([
key(plastic),wheel(plastic),casing(plastic),cable(plastic)
])
]).
What should I do to in order to run questions like monitor(X). or motherboard(X) to give one or all layers of (sub)materials (Like my_computer(X). would do) ?
Would the code below be more useful for asking such questions? Question about one layer of submaterials is answered easily this way.
my_computer([case,monitor,keyboard,mouse]).
case([motherboard,power_supply_unit,central_processing_unit,random_access_memory,graphics_processing_unit]).
motherboard([board,ports,slots,capacitors]).
power_supply_unit([casing,cables,connectors,capacitors,fan,transformer]).
central_processing_unit([board,fan,heatsink]).
random_access_memory([board]).
graphics_processing_unit([board,ports,capacitors,fan,heatsink]).
monitor([lcd_screen,inverter,frame]).
keyboard(keys,frame,cable).
mouse([keys,wheel,casing,cable]).
The short answer to your questions would be:
monitor(X) :-
my_computer([_, monitor(X), _, _]).
And similarly for keyboard or mouse, etc. The motherboard would be a layer deeper:
motherboard(X) :-
my_computer([case([motherboard(X), _, _, _, _), _, _, _]).
These predicates of course assume a fixed structure. If you wanted it a bit more general, you could do a more elaborate "hunt" for the embedded functors (monitor, motherboard, etc).
Depending upon your broader application goals, it's not clear to me that this is the best representation of the data. Good enough for now, but context might want to take it in a different direction.
Here's another approach, thinking of the data as individual facts which imply a tree relationship. Basically just has relationships. Separate the "material" facts as material(Item, Type):
item(my_computer, case).
item(my_computer, monitor).
item(my_computer, keyboard).
item(my_computer, mouse).
item(case, motherboard).
item(case, power_supply_unit).
item(case, central_processing_unit).
item(case, random_access_memory).
item(case, graphics_processing_unit).
item(motherboard, board).
item(motherboard, ports).
item(motherboard, slots).
item(motherboard, capacitors).
item(power_supply_unit, casing).
item(power_supply_unit, cable).
item(power_supply_unit, connectors).
item(power_supply_unit, capacitors).
item(power_supply_unit, fan).
item(power_supply_unit, transformer).
item(central_processing_unit, board).
item(central_processing_unit, fan).
item(central_processing_unit, heatsink).
item(random_access_memory, board).
item(graphics_processing_unit, board).
item(graphics_processing_unit, ports).
item(graphics_processing_unit, capacitors).
item(graphics_processing_unit, fan).
item(graphics_processing_unit, heatsink).
item(monitor, lcd_screen).
item(monitor, inverter).
item(monitor, frame).
item(keyboard, key).
item(keyboard, frame).
item(keyboard, cable).
item(mouse, key).
item(mouse, wheel).
item(mouse, casing).
item(mouse, cable).
material(board, plastic).
material(slots, plastic).
material(capacitors, plastic).
material(ports, metal).
material(casing, metal).
material(cable, plastic).
material(connectors, plastic).
material(fan, plastic).
material(heatsink, metal).
material(lcd_screen, plastic).
material(inverter, plastic).
material(frame, plastic).
material(key, plastic).
material(cable, plastic).
Then you can define a predicate to generate the tree for whatever level you wish. Here's an example that does it in the form of terms (not lists):
structure(Item, Structure) :-
( item(Item, _)
-> findall(T, (item(Item, R), structure(R, T)), Rs),
Structure =.. [Item |Rs]
; Structure = Item
).
So then:
:- structure(case, S).
S = case(motherboard(board,ports,slots,capacitors),
power_supply_unit(casing,cable,connectors,capacitors,fan,transformer),
central_processing_unit(board,fan,heatsink),
random_access_memory(board),
graphics_processing_unit(board,ports,capacitors,fan,heatsink)
)
This could easily be changed to provide results in a list form instead. For example, here's a predicate that takes the above facts and gives the form you originally presented in your question:
structure(Item, Tree) :-
( item(Item, _)
-> findall(T, (item(Item, R), structure(R, T)), Rs),
Tree =.. [Item, Rs]
; material(Item, Material),
Tree =.. [Item, Material]
).
And the item becomes a trivial result for a where_used(Item, Parent) predicate:
where_used(Item, Parent) :-
item(Parent, Item).
Again, it all depends upon how you want to use and manage the data.
Edit:
For fun, I cobbled together a pair of rules that generalize arbitrary depth access to structures of the form name([attr_1([attr_1_1,...attr_1_n([...])]), ..., attr_N([...])); i.e., to data that is stored as a fact that has the same general form as your my_computer/1. You can use it, if you like. In any case, it's a nice little demonstration of Prolog's potent recipe of higher-order predicates + explicit evaluation + homoiconicity:
attribute(Thing, Path) :-
call(Thing, Attributes),
path_through_attributes(Path, Attributes).
path_through_attributes(Path, Attributes) :-
( Path = (Func -> NextPath), atom(Func)
->
Attr =.. [Func, NextAttributes],
member(Attr, Attributes),
path_through_attributes(NextPath, NextAttributes)
;
compound(Path),
member(Path, Attributes)
).
Supposing we have a fact like my_computer/1 (just as it appears in the first part of your question), the the predicate attribute/2 can be used to access any of its attributes, nested at any depth, like so:
?- attribute(my_computer, case(X)).
X = [motherboard([board(plastic), ports(metal), slots(plastic), capacitors(plastic)]), power_supply_unit([casing(metal), cables(plastic), connectors(plastic), capacitors(plastic), fan(plastic), transformer(...)]), central_processing_unit([board(plastic), fan(plastic), heatsink(metal)]), random_access_memory([board(plastic)]), graphics_processing_unit([board(plastic), ports(metal), capacitors(...)|...])]
?- attribute(my_computer, case -> power_supply_unit(X)).
X = [casing(metal), cables(plastic), connectors(plastic), capacitors(plastic), fan(plastic), transformer(metal)]
?- attribute(my_computer, case -> power_supply_unit -> transformer(X)).
X = metal
I chose the ->/2 operator for the accessor-path simply because it has a convenient associativity (i.e., right: (a -> (b -> c))) and it is sort of suggestive of "reaching into a series of nested terms". But it's probably not a great choice. If I were going to use this frequently, I'd come up with a good operator for the purpose and declare it with op/3.
Since all the terms in my_computer/1 are in a list, you could make a general access predicate using member:
computer_attribute(Attr) :-
my_computer(Attributes),
member(Attr, Attributes).
It can be used like so:
?- computer_attribute(monitor(X)).
X = [lcd_screen(plastic), inverter(plastic), frame(plastic)]
Of course, because monitor is nested in case, you'll need to have a more specific approach to access the former.
Depending on how elaborate the data gets, this might be a good use case for pairs, association lists, or records. (There is also the new SWI Prolog extension providing the dict compound, but I don't recommend it. I have found them to be more trouble then they are worth. But that might just be a shortcoming on my side.)
The indentation of your second block of code makes it look like there is a hierarchical relationship between the my_computer/1 fact and the others, but that is an illusion. Proper indentation makes clear that each is simply stated as an independent fact:
my_computer([case,monitor,keyboard,mouse]).
case([motherboard,power_supply_unit,central_processing_unit,random_access_memory,graphics_processing_unit]).
motherboard([board,ports,slots,capacitors]).
power_supply_unit([casing,cables,connectors,capacitors,fan,transformer]).
central_processing_unit([board,fan,heatsink]).
random_access_memory([board]).
graphics_processing_unit([board,ports,capacitors,fan,heatsink]).
monitor([lcd_screen,inverter,frame]).
keyboard(keys,frame,cable).
mouse([keys,wheel,casing,cable]).
As a result, this representation will only work if you only have one computer with the relevant parts in the given namespace. Otherwise, if you have a second computer, you won't be able to tell which call to mouse(X) is supposed to be associated with which computer. However, you could give each computer a name and then define the different attributes as relations between the computers name and a list of terms. Then your representation would like so:
computer(mine).
case(mine, [motherboard,power_supply_unit,central_processing_unit,random_access_memory,graphics_processing_unit]).
....
This code can be queried like so:
?- computer(Name), motherboard(Name, Specs).
Name = mine,
Specs = [board, ports, slots, capacitors].
(Once you have a firm grasp of Prolog, you might want to investigate Logtalk, which is an OOP extension for Prolog. I have never used it, but it sound fun.)
You can also define a bill of materials (BOM) using definite clause grammar rules (DCGs). For example:
my_computer -->
case, monitor, keyboard, mouse.
case -->
motherboard, power_supply_unit, central_processing_unit,
random_access_memory, graphics_processing_unit.
motherboard -->
[board], [ports], [slots], [capacitors].
power_supply_unit -->
[casing], [cables], [connectors], [capacitors], [fan], [transformer].
central_processing_unit -->
[board], [fan], [heatsink].
random_access_memory -->
[board].
graphics_processing_unit -->
[board], [ports], [capacitors], [fan], [heatsink].
monitor -->
[lcd_screen], [inverter], [frame].
keyboard -->
[keys], [frame], [cable].
mouse -->
[keys], [wheel], [casing], [cable].
Parts that have components, e.g. monitor, are represented as non-terminals. Parts that are not made of other parts, e.g. keys, are represented as terminals (i.e. between square brackets). The standard phrase/2 predicate can be used to get the bill of materials for a specific part. For example:
| ?- phrase(monitor, Parts).
Parts = [lcd_screen,inverter,frame]
yes
| ?- phrase(case, Parts).
Parts = [board,ports,slots,capacitors,casing,cables,connectors,capacitors,fan,transformer,board,fan,heatsink,board,board,ports,capacitors,fan,heatsink]
yes
If you need to represent detailed information about a specific part, e.g. that a fan have three different modes, which one with its own RPMs, you can interpret the part name (which is an atom) as either a Prolog module or a Logtalk object identifier. The module or object can then nicely hold the details about the part.
Related
I have been working with Prolog since today, and wanted to create a simple test case. The basic idea was to have multiple sports defined, and it looks as follows:
soccer :- category(ball_sport),
check(has_11_players_per_team),
check(large_ball),
check(use_feet).
tennis :- category(ball_sport),
...
category(ball_sport) :-
check(has_a_ball).
Now I wanted to create a testcase, to see if both sports are of the ball_sport category, but have no idea to check these sports against eachother .. I thought it would be something like the code below, but it's obvious not. Is there an easy way to check these predicate categories? Thanks
both_ballsports(sport_one, sport_two) :-
has_category(sport_one, ball_sport),
has_category_sport_two, ball_sport).
It seems that first of all, you want to declaratively state attributes of a sport.
For example:
sport_attributes(soccer, [ball_sport,players(22),ball(large),use(feet)]).
sport_attributes(tennis, [ball_sport,players(2),players(4),ball(small),use(racket)]).
Note that I am relating sports to attributes. For comparison, the goals of the form check(X) you use above all seem to lack a critical argument, namely the actual sport for which they hold (or not). For example, the goal check(use_feet) either holds or not, but there is no means to qualify a unary predicate of this kind and state different facts for different sports.
Note the naming convention: We describe what each argument means, separated by underscores.
With this representation, both_ballsports/2 could look like this:
both_ballsports(Sport1, Sport2) :-
ballsport(Sport1),
ballsport(Sport2).
ballsport(Sport) :-
sport_attributes(Sport, As),
member(ball(_), As).
Sample query and answer:
?- both_ballsports(Sport1, Sport2).
Sport1 = Sport2, Sport2 = soccer ;
Sport1 = soccer,
Sport2 = tennis ;
Sport1 = tennis,
Sport2 = soccer ;
Sport1 = Sport2, Sport2 = tennis ;
false.
This can be used in all directions!
I want to parse a logical expression using DCG in Prolog.
The logical terms are represented as lists e.g. ['x','&&','y'] for x ∧ y the result should be the parse tree and(X,Y) (were X and Y are unassigned Prolog variables).
I implemented it and everything works as expected but I have one problem:
I can't figure out how to parse the variable 'x' and 'y' to get real Prolog variables X and Y for the later assignment of truth values.
I tried the following rule variations:
v(X) --> [X].:
This doesn't work of course, it only returns and('x','y').
But can I maybe uniformly replace the logical variables in this term with Prolog variables? I know of the predicate term_to_atom (which is proposed as a solution for a similar problem) but I don't think it can be used here to achieve the desired result.
v(Y) --> [X], {nonvar(Y)}.:
This does return an unbound variable but of course a new one every time even if the logical variable ('x','y',...) was already in the term so
['X','&&','X'] gets evaluated to and(X,Y) which is not the desired result, either.
Is there any elegant or idiomatic solution to this problem?
Many thanks in advance!
EDIT:
The background to this question is that I'm trying to implement the DPLL-algorithm in Prolog. I thought it would by clever to directly parse the logical term to a Prolog-term to make easy use of the Prolog backtracking facility:
Input: some logical term, e.g T = [x,'&&',y]
Term after parsing: [G_123,'&&',G_456] (now featuring "real" Prolog variables)
Assign a value from { boolean(t), boolean(f) } to the first unbound variable in T.
simplify the term.
... repeat or backtrack until a assignment v is found so that v(T) = t or the search space is depleted.
I'm pretty new to Prolog and honestly couldn't figure out a better approach. I'm very interested in better alternatives! (So I'm kinda half-shure that this is what I want ;-) and thank you very much for your support so far ...)
You want to associate ground terms like x (no need to write 'x') with uninstantiated variables. Certainly that does not constitute a pure relation. So it is not that clear to me that you actually want this.
And where do you get the list [x, &&, x] in the first place? You probably have some kind of tokenizer. If possible, try to associate variable names to variables prior to the actual parsing. If you insist to perform that association during parsing you will have to thread a pair of variables throughout your entire grammar. That is, instead of a clean grammar like
power(P) --> factor(F), power_r(F, P).
you will now have to write
power(P, D0,D) --> factor(F, D0,D1), power_r(F, P, D1,D).
% ^^^^ ^^^^^ ^^^^
since you are introducing context into an otherwise context free grammar.
When parsing Prolog text, the same problem occurs. The association between a variable name and a concrete variable is already established during tokenizing. The actual parser does not have to deal with it.
There are essentially two ways to perform this during tokenization:
1mo collect all occurrences Name=Variable in a list and unify them later:
v(N-V, [N-V|D],D) --> [N], {maybesometest(N)}.
unify_nvs(NVs) :-
keysort(NVs, NVs2),
uniq(NVs2).
uniq([]).
uniq([NV|NVs]) :-
head_eq(NVs, NV).
uniq(NVs).
head_eq([], _).
head_eq([N-V|_],N-V).
head_eq([N1-_|_],N2-_) :-
dif(N1,N2).
2do use some explicit dictionary to merge them early on.
Somewhat related is this question.
Not sure if you really want to do what you asked. You might do it by keeping a list of variable associations so that you would know when to reuse a variable and when to use a fresh one.
This is an example of a greedy descent parser which would parse expressions with && and ||:
parse(Exp, Bindings, NBindings)-->
parseLeaf(LExp, Bindings, MBindings),
parse_cont(Exp, LExp, MBindings, NBindings).
parse_cont(Exp, LExp, Bindings, NBindings)-->
parse_op(Op, LExp, RExp),
{!},
parseLeaf(RExp, Bindings, MBindings),
parse_cont(Exp, Op, MBindings, NBindings).
parse_cont(Exp, Exp, Bindings, Bindings)-->[].
parse_op(and(LExp, RExp), LExp, RExp)--> ['&&'].
parse_op(or(LExp, RExp), LExp, RExp)--> ['||'].
parseLeaf(Y, Bindings, NBindings)-->
[X],
{
(member(bind(X, Var), Bindings)-> Y-NBindings=Var-Bindings ; Y-NBindings=Var-[bind(X, Var)|Bindings])
}.
It parses the expression and returns also the variable bindings.
Sample outputs:
?- phrase(parse(Exp, [], Bindings), ['x', '&&', 'y']).
Exp = and(_G683, _G696),
Bindings = [bind(y, _G696), bind(x, _G683)].
?- phrase(parse(Exp, [], Bindings), ['x', '&&', 'x']).
Exp = and(_G683, _G683),
Bindings = [bind(x, _G683)].
?- phrase(parse(Exp, [], Bindings), ['x', '&&', 'y', '&&', 'x', '||', 'z']).
Exp = or(and(and(_G839, _G852), _G839), _G879),
Bindings = [bind(z, _G879), bind(y, _G852), bind(x, _G839)].
I'm trying to retract and assert a fact in another file. One (fruit1.pl) contains a couple of facts, and another (fruit.pl) contains a predicate start which designates which fact that another predicate insert_fruit will update:
fruit1.pl
fruit(apple, [[2, yellow], [1, brown]]).
fruit(orange, [[3, orange], [2, orange]]).
fruit.pl
:- dynamic fruit/2.
start :-
consult('fruit1.pl'),
File = 'fruit1.pl',
Name = apple,
Price = 2,
Color = red,
insert_fruit(File, Name, Price, Color).
insert_fruit(File, Name, Price, Color) :-
open(File, update, Stream),
retract(fruit(Name, Information)),
assert(fruit(Name, [[Price, Color]|Information])),
close(Stream).
However insert_fruit is not working as intended, as I believe it needs to include Stream to modify the other file, although I have no idea how (retract(Stream, ...) doesn't work). Is there some I would be able to get the retract and assert predicates to function in the other file?
In SWI-Prolog you can assert/retract facts from a file that is used as a persistent fact store by using library persistency:
You declare fruit/3 as persistent. Optionally: you annotate the arguments with a type for automatic type checking.
You attach a file that will serve as the persistent fact store upon initialization of the fruit module (in this case fruit1.pl).
You add predicates for inserting (i.e., add_fruit/3) and querying (i.e., current_fruit/3) fruity facts. Retraction is handled similarly.
Notice that you can use the fact store in a multi-threaded environment by using with_mutex/2 (especially useful when you start retracting facts as well).
Code
:- module(
fruit,
[
add_fruit/3, % +Name:atom, +Price:float, +Color:atom
current_fruit/3 % ?Name:atom, ?Price:float, ?Color:atom
]
).
:- use_module(library(persistency)).
:- persistent(fruit(name:atom, price:float, color:atom)).
:- initialization(db_attach('fruit1.pl', [])).
add_fruit(Name, Price, Color):-
with_mutex(fruit_db, assert_fruit(Name, Price, Color)).
current_fruit(Name, Price, Color):-
with_mutex(fruit_db, fruit(Name, Price, Color)).
Illustration of use
Start Prolog, load fruit.pl, execute:
?- add_fruit(apple, 1.10, red).
Close Prolog, start Prolog (again), execute:
?- current_fruit(X, Y, Z).
X = apple,
Y = 1.1,
Z = red
You are now reading facts from fruit1.pl!
Illustration of automatic type checking
As mentioned before, the library also performs type checking for you, e.g.:
?- add_fruit(pear, expensive, green).
ERROR: Type error: `float' expected, found `expensive' (an atom)
I'm currently trying to to interpret user-entered strings via Prolog. I'm using code I've found on the internet, which converts a string into a list of atoms.
"Men are stupid." => [men,are,stupid,'.'] % Example
From this I would like to create a rule, which then can be used in the Prolog command-line.
% everyone is a keyword for a rule. If the list doesn't contain 'everyone'
% it's a fact.
% [men,are,stupid]
% should become ...
stupid(men).
% [everyone,who,is,stupid,is,tall]
% should become ...
tall(X) :- stupid(X).
% [everyone,who,is,not,tall,is,green]
% should become ...
green(X) :- not(tall(X)).
% Therefore, this query should return true/yes:
?- green(women).
true.
I don't need anything super fancy for this as my input will always follow a couple of rules and therefore just needs to be analyzed according to these rules.
I've been thinking about this for probably an hour now, but didn't come to anything even considerable, so I can't provide you with what I've tried so far. Can anyone push me into the right direction?
Consider using a DCG. For example:
list_clause(List, Clause) :-
phrase(clause_(Clause), List).
clause_(Fact) --> [X,are,Y], { Fact =.. [Y,X] }.
clause_(Head :- Body) --> [everyone,who,is,B,is,A],
{ Head =.. [A,X], Body =.. [B,X] }.
Examples:
?- list_clause([men,are,stupid], Clause).
Clause = stupid(men).
?- list_clause([everyone,who,is,stupid,is,tall], Clause).
Clause = tall(_G2763):-stupid(_G2763).
I leave the remaining example as an easy exercise.
You can use assertz/1 to assert such clauses dynamically:
?- List = <your list>, list_clause(List, Clause), assertz(Clause).
First of all, you could already during the tokenization step make terms instead of lists, and even directly assert rules into the database. Let's take the "men are stupid" example.
You want to write down something like:
?- assert_rule_from_sentence("Men are stupid.").
and end up with a rule of the form stupid(men).
assert_rule_from_sentence(Sentence) :-
phrase(sentence_to_database, Sentence).
sentence_to_database -->
subject(Subject), " ",
"are", " ",
object(Object), " ",
{ Rule =.. [Object, Subject],
assertz(Rule)
}.
(let's assume you know how to write the DCGs for subject and object)
This is it! Of course, your sentence_to_database//0 will need to have more clauses, or use helper clauses and predicates, but this is at least a start.
As #mat says, it is cleaner to first tokenize and then deal with the tokenized sentence. But then, it would go something like this:
tokenize_sentence(be(Subject, Object)) -->
subject(Subject), space,
be, !,
object(Object), end.
(now you also need to probably define what a space and an end of sentence is...)
be -->
"is".
be -->
"are".
assert_tokenized(be(Subject, Object)) :-
Fact =.. [Object, Subject],
assertz(Fact).
The main reason for doing it this way is that you know during the tokenization what sort of sentence you have: subject - verb - object, or subject - modifier - object - modifier etc, and you can use this information to write your assert_tokenized/1 in a more explicit way.
Definite Clause Grammars are Prolog's go-to tool for translating from strings (such as your English sentences) to Prolog terms (such as the Prolog clauses you want to generate), or the other way around. Here are two introductions I'd recommend:
http://www.learnprolognow.org/lpnpage.php?pagetype=html&pageid=lpn-htmlse29
http://www.pathwayslms.com/swipltuts/dcg/
I'm developing a Guess Who? game using Prolog. The mechanics of the game are very easy. A player (in this case, the human) chooses one person among many possible ones, and the other player (the computer) starts asking yes/no questions about some attributes of the person. Eventually, with the given answers, there will only be one possible person.
So far, I've been able to make a set of rules and predicates so that the computer can guess the person based on the questions that have been asked so far. I have a set of suspects - those are the people that, with the available clues, could fit.
suspect('Person 1') :- eyes(blue) , age(old) , gender(male).
The predicates for the attributes are defined so that they will be true either if the question regarding that attribute has not been asked yet, or if the question has been asked and the answer matches the attribute of the suspect.
gender(X) :- not(asked_gender) ; value_of(gender, X).
That way, if two suspects share the same eyes and age and different gender, as long as the gender remains unasked, both of them will be plausible suspects.
However, the hard part now is to automate the process of asking those questions. Basically, I'm looking forward to a solution where Prolog were able to get the possible values for the attributes from the suspects' predicates, instead of listing theme somewhere else. I'm pretty sure there must be a way of doing this, given prolog is able to use the program's code as data itself.
How could I do that?
This works in SWI-Prolog :
:- dynamic value/1.
suspect('Person 1') :- eyes(blue) , age(old) , gender(male).
suspect('Person 2') :- eyes(green) , age(young) , gender(male).
suspect('Person 3') :- eyes(brown) , age(young) , gender(male).
fetch(Criterion, Value) :-
retractall(value(_)),
assert(value([])),
forall(clause(suspect(_), Body),
check(Body, Criterion)),
retract(value(Value)).
check((F, T), Criterion) :-
F =..[Criterion, V1],
retract(value(V2)),
( member(V1, V2) -> V3 = V2; V3 = [V1 | V2]),
assert(value(V3)).
check(T, Criterion).
check((_F, T), Criterion) :-
check(T, Criterion).
check((F), Criterion) :-
F =..[Criterion, V1],
retract(value(V2)),
( member(V1, V2) -> V3 = V2; V3 = [V1 | V2]),
assert(value(V3)).
check((_F), _Criterion).
For example :
?- fetch(gender, Value).
Value = [male].
?- fetch(eyes, Value).
Value = [brown,green,blue].
Well, I would imagine a construction like this:
go :-
findall(People,suspect(People),SuspectList),
length(SuspectList,1),
member(Perb,SuspectList),
write('It is '),write(Perb),write('!!'),nl,!.
go :-
askQuestion,
go.
Where in askQuestion/0 you'd ask questions with the read/1 predicate and assert/1 the answers.
This is where you could try and make it 'intelligent' or you could just iterate over the different questions.