CPSC312-2019-Meal-Planner
Authors: Tim & Jordan
What is the problem?
We want to investigate whether Prolog is a practical language for building a meal-planning application. We plan to build a Prolog program that lets the user create databases of 'recipes' and an 'inventory', and then ask natural-language questions to get meal suggestions based on these. Ultimately we intend our program to be used like so:
>> omelette: eggs, cheddar_cheese, mushrooms, salt, pepper
(adding a 'recipe' to the database)
>> scrambled_eggs: eggs, salt, pepper
>> french_toast: bread, eggs, milk
>> I have eggs
(adding items to the 'inventory')
>> I have potatoes
>> I have cheddar_cheese
>> I have mushrooms
>> I have salt pepper sugar
(add multiple items)
>> What can I make?
omelette, scrambled_eggs.
>> Can I make french toast?
No, you are missing the following ingredients: bread, milk.
>> I used the salt
(removes "salt" from the inventory).
>> I used eggs pepper
(removes multiple ingredients from the inventory)
What is the something extra?
Customizable facts & rules sets
So far in class we have focused on rule systems that have hard-coded facts and constraints and provide answers to queries based on that static knowledge base. To be practical as a day-to-day tool, though, our program would need to have dynamic rule sets and facts - the user's food inventory is always changing and they would likely regularly add new recipes. To handle this, we investigated Prolog's assert, retract and dynamic features as a way to build mutable databases and rule sets. We introduce 2 dynamic predicates: have(ingredient) and requires(meal, [ingredients]) - we then let users build up and manipulate their own databases of recipes and ingredients by asserting and retracting these predicates.
Database persistence, file I/O, and multi-user programs
Another practical necessity for a program like this is that it be persistent - a user expects their recipes and inventory to remain intact after they close the program and be available to them once they open it again. Our strategy for this was to persist the state of the world (all asserted propositions about inventory and recipes) to a file when the user types 'save'. We can then load that same file whenever a user launches the program and bring their databases back into memory. The atom_concat function along with various I/O built-ins (open, write, exists_file) were used to make this step possible.
As a further extra, we wanted to make this program usable by more than one user - different users might have different recipes for french toast, for example, and we wanted a way to capture that variability. We achieved this by adding a third dynamic predicate, "current_user" which is supplied by the user when the program loads. All loads and saves of the program state are then routed to files with that username as the filename, allowing for a unique set of ingredients & recipes per user.
Some "extra" extras:
We added a few other extra features that either used technical aspects not covered in class or improved the usability of the program:
1) Use of cuts ('!')to short-circuit multi-case predicates;
2) Building an interactive input loop;
3) Extra natural-language parsing, for instance to deal with upper-vs-lowercase letters and the use of articles, as in "a chicken, an orange" instead of "chicken, orange".
What did we learn from doing this?
What worked great
1) Dynamic predicates and the assert/retract built-in functions made it straightforward to create a user-defined knowledge base and rule set.
2) Persisting program state with I/O: Prolog makes it fairly simple to write results to files, and once our dynamic predicates were designed correctly it was not difficult to write all the user's "have(item)" and "requires(meal, [items])" assertions to disk. Loading from disk was even more straightforward, since the written files constitute a full Prolog program that we could just load with [].
3) Input parsing. With the exception of some edge cases (see below) parsing the user's input worked very well in Prolog. We were able to exploit the conversion of input into lists both to parse sentences and to create simple entries for recipe requirements. We were also pleasantly surprised by the default handling of punctuation, which made it easier to exclude superfluous characters than we expected.
What worked not-so-great
1) English is complicated. While we did what we were able to handle variations in input (case-sensitivity, dealing with various phrasings, ignoring extra words like 'the', 'a' etc), there were obviously many linguistic nuances we couldn't easily capture with Prolog. Plurals are a tricky case: if recipe calls for "a bun" but the user claims to have "buns", what should we do? We could ignore S's at the end of words, but this creates problems for irregular plurals such as "raspberry" vs "raspberries". Ultimately for our proof-of-concept we left such complexities to NLP researchers and require the user to be consistent between recipes and inventory.
2) Dealing with object quantities. While it was quite straightforward to assert and retract straightforward facts like "I have eggs", handling more complex relations and constraints, such as "I have 3 eggs", "I used 200g of steak", and "Eggs have 150 calories". One way we explored to capture these facts was to replace have(eggs) with have(amt(3, eggs)), or have(grams(200, steak)), and so on. But handling all these variations adds significant complexity to the code. Many more error cases must be handled as well (for example, if a user has 200g of steak and types "remove 250g of steak", what should be done?). Ultimately, we felt that this added complexity was not worth fully implementing as it didn't really require anything 'extra' from Prolog apart from more code.
Bottom Line: Conclusion
We decided that Prolog is a solid choice for designing an application of this type - that is, applications where we want to allow the user to build up their own knowledge base and rule set and then provide answers to queries based on that set. Prolog made it simple to parse user input, add rules and facts, persist data over time, and provide answers to questions about the state of the world. There are aspects of the food/recipe problem domain that are challenging to deal with (natural-language parsing for plurals being one example), but these difficulties don't stem from the use of Prolog. To the contrary, Prolog is a natural choice for dealing with certain natural-language phenomena.