If you access redundancy in your code by striking out data from logic, you end up with a much more declarative code.
So, encode the data structure and provide a โtranslatorโ capable of asking questions and inferring consequences.
for example
dt(food_type, [indian -> dt(spicy, [ y -> curry, n -> curma ]) ,chinese -> dt(fry, [ y -> stirFry, n -> chicken ]) ,malay -> dt(chili, [ y -> sambal, n -> singgang ]) ]). interpreter(dt(About, Choices), Choice) :- % present a menu for choices % recurse on selected path % when it reach a leaf, just unify interpreter(Choice, Choice).
You may want to specialize the menu only for y / n, but it is up to you
edit
Presentation of the menu and acceptance of the selection require additional logical programming, for example:
solve(Choice) :- dt(About, Choices), interpreter(dt(About, Choices), Choice). % present a menu for choices % recurse on selected path interpreter(dt(About, Choices), Choice) :- ask_user(About, Choices, ChoiceAbout), interpreter(ChoiceAbout, Choice). % when it reach a leaf, just unify interpreter(Choice, Choice). ask_user(About, Choices, Choice) :- format('your choice about ~w ?~n', [About]), % show user the context of choice forall(member(C->_,Choices), format(' ~w~n', [C])), read(U), memberchk(U->Choice, Choices). % note: if memberchk above fails (user doesn't input a correct choice) % you should provide an alternate ask_user here, otherwise ... % just see what the code does %
Session Example:
% /home/carlo/Desktop/pl/choices compiled into choices 0.00 sec, 0 clauses ?- solve(C). your choice about food_type ? indian chinese malay |: chinese. your choice about fry ? y n |: n. C = chicken .