================================================== QUERYING AND UPDATING A PROLOG DATABASE Approach #1: Structured objects and a single database assertion ================================================== Here is the basic scenario: suppose we store for each employee: -- name -- id number -- wage rate -- a boolean saying whether s/he is a manager or not. Furthermore, we might have a list or database of employees that we would want to manipulate in the following ways: 1. find the name of an employee given the Id number 2. change the wage rate of an employee 3. add a new employee to a list or database 4. remove an old one from a list or database 5. give everybody a raise 6. get a list of all well-paid managers (which we'll define to be wage > 50) We will begin with the first two, which apply to individual objects rather than to lists/databases. In the first approach we will use a structured data object (not a predicate) to represent our employees. Instead of making assertions of the form employee('Fred', 2, 10, false) in the database, we will carry these objects around just as Lisp structures or Smalltalk Objects. ================== 1. To extract the ID number (a classic "getter" function) we just say this (as we did in class): employee_id(employee(_, Id, _, _), Id). Notice that we have an extra argument in the predicate to hold the "return result". 2. To change a wage we just say: change_wage(employee(N, I, _, M), NewWage, employee(N, I, NewWage, M)). Here we have three arguments: the input employee, the new wage, and the output employee. Notice that we let the matcher do all the work for us---that's the Prolog way. ==================== Now one weird thing about this scheme is that there seems to be no way to assert that somebody *is* an employee, or to store a list of employees somewhere. In other words, we can build the structured object employee('Mary', 3, 20, true) but what do we do with it? What we will to is to build a list of employees, and we will store that list by making a single assertion: database([ employee('Fred', 1, 15, false), employee('Sally', 2, 25, true), employee('Mary', 3, 10, true), employee('Bill', 0, 1000, true) ]). (the predicate "database" was chosen arbitrarily). Now we can start playing games with this list. For example, here's how we return somebody's name given their ID: first we retrieve the database, then we find the name in a list of employees: %%%%%%%%%%%%%%%%%%%%%%%%%%% find_name(Id, Name) :- database(D), find_name_in_list(Id, D, Name). find_name_in_list(Id, [employee(Name, Id, _, _) | _], Name). find_name_in_list(Id, [_ | Rest], Name) :- find_name_in_list(Id, Rest, Name). %%%%%%%%%%%%%%%%%%%%%%%%%%% 3. Adding a new employee is easy, except we have to retract the old database and assert the new one. First we retrieve the database to save it, then retract the old one, then re-assert the database with the new employee on the list. (This isn't really a bullet-proof piece of code because it would allow *any* object to be put on the database list, and probably we only want objects of the form employee(_, _, _, _) on it. Also it would allow multiple employees with the same ID to go on the list.) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% add_employee(E) :- database(D), retract(database(_)), assert(database[E|D]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4. Removing a name is also pretty easy, but once again we have to assert and retract the current database. Also we need to write a predicate to remove the employee with that ID from the list. This code also assumes that there is only one employee with a particular Id in the list. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% remove_employee(Id) :- database(D), retract(database(_)), remove_id_from_list(Id, D, NewD), assert(database(NewD)). remove_id_from_list(Id, [], []). remove_id_from_list(Id, [employee(_, Id, _, _) | Rest], Rest). remove_id_from_list(Id, [E | Rest], [E | Remainder]) :- remove_id_from_list(Id, Rest, Remainder). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 5. Now let's give everybody in the database a 10% raise: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% give_raise :- database(D), retract(database(_)), give_raise(D, NewD), assert(database(NewD)). give_raise([], []). give_raise([employee(Name, Id, W1, M) | Rest1], [employee(Name, Id, W2, M) | Rest2]) :- W2 is W1 * 1.10, give_raise(Rest1, Rest2). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6. And finally let's get the list of well-paid managers: We will have to talk about what the exclamation points mean! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% rich_managers(L) :- database(D), extract_rich_managers(D,L). extract_rich_managers([], []) :- !. extract_rich_managers([employee(N, I, S, true) | R1], [employee(N, I, S, true) | R2]) :- S > 200, !, extract_rich_managers(R1, R2). extract_rich_managers([_| R1], R2) :- extract_rich_managers(R1, R2). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%