Writing Prolog MRS Predicates

This section goes through the concrete process of actually writing Prolog to implement ERG predicates. It is a “How To” with lots of examples. It requires an understanding of the whole system, so I will link liberally to the copious background required as I hit new concepts.

One path for understanding Perplexity is to start with this section and read the links as you encounter them. The other is to start at the high level flow and work through it that way.

I do assume a decent understanding of how Prolog works in this section without giving background, but there are lots of resources around the Internet for getting up to speed on that. Prolog use in Perplexity isn’t very complex.

How to Start

The high-level flow describes the series of things that need to happen to actually “execute” an english phrase logically. But to summarize: to get to the point where Prolog for a phrase like “where are you” is ready to be executed, you first need to:

  1. Parse the phrase into MRS
  2. Scope-resolve the MRS into a set of trees
  3. Convert the trees into “canonical” Prolog

At that point, you need the Prolog implementation to actually run the thing. That’s the point of this section.

Probably obvious but: I found the best approach was to focus on a single MRS production and make it work fully. After it works, to then build a set of tests that ensure it keeps working as I build the next one. Because this is a logic system, it is a giant network of interrelated relationships. I found that implementing each new predicate would break the previous ones quite often. Without the giant battery of tests I wrote, I would have gone around in many many circles trying to get things right.

So, I literally started this exercise trying to implement the phrase “Where are you?” that generated this MRS and scope-resolved tree:

Type: question
[ TOP: h0
INDEX: e2
RELS: < 
[ pronoun_q__xhh LBL: h10 ARG0: x3 [ x PERS: 2 IND: + PT: std ] RSTR: h11 BODY: h12 ]
[ pron__x LBL: h9 ARG0: x3 [ x PERS: 2 IND: + PT: std ] ]
[ which_q__xhh LBL: h6 ARG0: x4 [ x PERS: 3 NUM: sg ] RSTR: h7 BODY: h8 ]
[ place_n__x LBL: h5 ARG0: x4 [ x PERS: 3 NUM: sg ] ]
[ loc_nonsp__exx LBL: h1 ARG0: e2 [ e SF: ques TENSE: pres MOOD: indicative PROG: - PERF: - ] ARG1: x3 ARG2: x4 ]
>
HCONS: < h0 qeq h1 h7 qeq h5 h11 qeq h9 > ]

                          ┌pron__x:x3
 pronoun_q__xhh:x3,h11,h12┤
                          │                     ┌place_n__x:x4
                          └which_q__xhh:x4,h7,h8┤
                                                └loc_nonsp__exx:e2,x3,x4
                                                
Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

Task #1: what do each of these predicates mean?

What Does a Predicate Mean?

English is vast, there are tons of predicates in the ERG, and the documentation on them is sparse. The documentation that does exist is here and there is a very responsive message board with very knowledgable and helpful people, too. Both of those resources were critical to my understanding.

At the end of the day, I found the best way to understand what these predicates mean was to:

As described in the section on MRS, the predicate name like place_n__x tells you a lot: the lemma is “place”, it is a noun. The arguments it takes also give strong hints as to what is going on:

Writing ERG Prolog Predicates 101

Let’s go through the process of writing the very first predicate, pronoun_q__xhh, from the “Where are you?” example:

Type: question
[ TOP: h0
INDEX: e2
RELS: < [ pronoun_q__xhh LBL: h10 ARG0: x3 [ x PERS: 2 IND: + PT: std ] RSTR: h11 BODY: h12 ]
[ pron__x LBL: h9 ARG0: x3 [ x PERS: 2 IND: + PT: std ] ]
[ which_q__xhh LBL: h6 ARG0: x4 [ x PERS: 3 NUM: sg ] RSTR: h7 BODY: h8 ]
[ place_n__x LBL: h5 ARG0: x4 [ x PERS: 3 NUM: sg ] ]
[ loc_nonsp__exx LBL: h1 ARG0: e2 [ e SF: ques TENSE: pres MOOD: indicative PROG: - PERF: - ] ARG1: x3 ARG2: x4 ]
>
HCONS: < h0 qeq h1 h7 qeq h5 h11 qeq h9 > ]

                          ┌pron__x:x3
 pronoun_q__xhh:x3,h11,h12┤
                          │                     ┌place_n__x:x4
                          └which_q__xhh:x4,h7,h8┤
                                                └loc_nonsp__exx:e2,x3,x4
                                                
Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

Prolog: d_pronoun_q__xhh(arg(X56613, var([name(x3), type(reg), pers(2)])), arg(d_pron__x(arg(X56613, var([name(x3), type(reg), pers(2)]))), term), arg(d_which_q__xhh(arg(X56615, var([name(x4), type(reg), pers(3), num(sg)])), arg(d_noun__x(arg('place', word('place')), arg(X56615, var([name(x4), type(reg), pers(3), num(sg)]))), term), arg(conj(d_loc_nonsp__exx(arg(E56616, var([name(e2), index(yes), tense(pres)])), arg(X56613, var([name(x3), type(reg), pers(2)])), arg(X56615, var([name(x4), type(reg), pers(3), num(sg)])), arg(create, var([type(quote)]))), conj(query_loc_nonsp__exx(arg(E56616, var([name(e2), index(yes), tense(pres)]))))), term), arg(Quote56619, var([type(quote)]))), term), arg(Quote56617, var([type(quote)]))).

What Does pronoun_q__xhh Mean?

This was literally the first question I asked on the ERG message board and didn’t really get answered, but got answered in one of my next questions. The short version is that, in addition to their role in English, quantifiers are the thing that “introduce” x variables in the ERG. So, some quantifier was needed for filling in the pronoun value. There are some quantifiers you’ll see like udef_q__xhh whose only job is to introduce an x variable so that might have worked here, too.

Honestly, I couldn’t find any reason why a special quantifierpronoun_q__xhh was used instead of just a generic one like udef_q__xhh, since the quantifier isn’t what is choosing the pronoun here, pron__x is. It is unclear what semantic it is adding. There are many cases of quantifiers having a special name but using the same default behavior that I’ve found in the ERG. I’ve implemented helpers that “generic” quantifiers can call since, so far, they all map to the same thing in Perplexity.

So, we’re going to implement pronoun_q__xhh as an example of a generic quantifier, which is a good example since they come up a lot.

The Logic For a Generic Quantifier: pronoun_q__xhh

Before implementation, what do we need it to do? All quantifiers have the same arguments:

“Classic” quantifiers like “the”, “a”, “some”, “many” would be implemented by ensuring that the x value only ends up having a certain set of values:

Implementing the logic for each is a challenging programming and linguistics problem. In this example, though, the quantifier doesn’t do anything. It is just being asked to:

A nice simple example.

Implementing Predicates That Have Scopal Arguments: pronoun_q__xhh

If a predicate is given a scopal (i.e. h) argument, it is responsible for figuring out what to do with it. There are many reasons why this predicate might need a whole branch of the tree. Often, as in this case, it means that it should logically evaluate it and do something with the result. In fact, since this is degenerate example, it isn’t actually going do anything with the result. It just needs to evaluate both arguments and call it good.

This is really easy in Prolog:

pronoun_q__xhh(X, Rstr, Body) :-
  Rstr,
  Body.

All variables are “existentially quantified” (i.e. declared) and visible to the entire statement in Prolog so nothing needs to be done to “introduce” the variable.

pronoun_q__xhh(X, Rstr, Body) :-
  Rstr,
  Body.

It is not quite that simple, though. As described in the section on “quoting”, any predicate that has scopal arguments needs to decide whether to evaluate them or quote them. Because this is a simple case, it doesn’t have any policy of its own. Run on its own (as it is here), it would default to “eval”. However, it might be run as a scopal argument of some other predicate. In that case, the entire branch it is contained in might be getting quoted and so it would need to “pass that through” to its own scopal arguments.

So, because quantifiers have scopal arguments, they need to know if what context they are being run in (quoted or evaluated) and pass that through. This means it will be given an extra argument at the end during conversion from MRS to Prolog like this:

pronoun_q__xhh(X, Rstr, Body, Quote) :-
  Rstr,
  Body.

Next problem: We need to set Quote to eval if it isn’t already set by whatever is calling pronoun_q. Let’s build a simple predicate to do that since it will happen often:

% Sets the Quote default to eval if it isn't already set  
defaultToEval(eval) :- !.  
defaultToEval(_).

and call it:

pronoun_q__xhh(X, Rstr, Body, Quote) :-
  defaultToEval(Quote),
  Rstr,
  Body.

OK, but how does the value of Quote get used by Rstr and Body? Well, each of those are actually a branch of the tree that (potentially) has a bunch of Prolog predicates, which may themselves have a Quote argument. pronoun_q is really only responsible for telling its immediate children whether to quote or eval and then they tell their children.

So, it needs to fish out the Quote arguments for its immediate children and set them. Let’s build a predicate that does that:

% Set the Quote variable (if exists) to Value
setQuoteVariable(Term, Value) :-  
  % The implementation of quoteVariable() is a bunch of Prolog that finds
  % the Quote variable for immediate children and sets Variable to it
  quoteVariable(Term, Variable, 0),  
  % Then we set the found Variable to the Value
  Variable = Value,  
  !.
  
% if there aren't any, don't fail  
setQuoteVariable(_, _).

and use it:

pronoun_q__xhh(X, Rstr, Body, Quote) :-
  defaultToEval(Quote),
  setQuoteVariable(Rstr, Quote),
  Rstr,
  setQuoteVariable(Body, Quote),
  Body.

There is one final step: as described in the “reporting failures from Prolog” section, we want scopal arguments to report failure as if they were top-level predicates and we use the reportErrorNextTerm predicate to do that:

pronoun_q__xhh(X, Rstr, Body, Quote) :-
  defaultToEval(Quote),
  setQuoteVariable(Rstr, Quote),
  reportErrorNextTerm(Rstr),
  setQuoteVariable(Body, Quote),
  reportErrorNextTerm(Body).

The final clean up phase is to notice that we really are doing the same thing and to group it all into a predicate (and remove the X from the predicate since we’re not directly using it):

pronoun_q__xhh(_, Rstr, Body, Quote) :-
  resolveTerm(Rstr, Quote),
  resolveTerm(Body, Quote).
  
% Used by terms with scopal arguments to actually execute them and  
% set their quoting properly  
resolveTerm(Term, Quote) :-  
  % needs to set the value to *something* if it hasn't been set yet  
  defaultToEvalArg(Quote),  
  setQuoteVariable(Term, Quote),  
  reportErrorNextTerm(Term).

That is literally the code that use used to implement pronoun_q__xhh and many other “default” quantifiers like it…

…with one difference. We need to pass some extra information in the arguments for better error reporting. See next section.

Argument Metadata

As described in the section on reporting failure, all the variables that come from the ERG have a bunch of metadata that needs to be passed around so predicates can report errors well and use it to make decisions (i.e. for variables that are plural).

While this predicate doesn’t do anything with them, I still use a stylized way of writing predicates so it is clear the metadata is available. Otherwise, the code can get really confusing.

The format of arguments that include metadata is: arg(PrologVariable, Metadata). Metadata can hold complex terms, but PrologVariable will always just be a variable. An example of an x type argument with metadata is:

arg(X57360, var([name(x4), type(reg), pers(3), num(sg)]))

I call this type of term an Arg term (since the term is arg/2). To say that an argument to a Prolog term is getting an Arg term, I always name them ending with Arg and add some extra terms that “declare” their form after the term declaration, like this:

d_pronoun_q__xhh(VariableArg, RstrArg, BodyArg, QuoteArg) :- 
  arg(_, _) = VariableArg, 
  arg(_, _) = RstrArg, 
  arg(_, _) = BodyArg, 
  arg(_, _) = QuoteArg,
  
  resolveTerm(RstrArg, QuoteArg),
  resolveTerm(BodyArg, QuoteArg).

In this case, these Arg declarations aren’t doing anything, but many predicate implementations need to get at the variables and metadata in them and so I decided to just always include this to reduce programmer error. We’ll see examples next.

So that is actually the implementation of d_pronoun_q__xhh used in Perplexity. Whew!

Handling Errors: pron__x

d_pronoun_q__xhh doesn’t have anything that is going to fail, it is the Rstr and Body scopal arguments that do the interesting stuff. So let’s implement the Rstr predicate: pron__x:

                          ┌pron__x:x3
 pronoun_q__xhh:x3,h11,h12┤
                          │                     ┌place_n__x:x4
                          └which_q__xhh:x4,h7,h8┤
                                                └loc_nonsp__exx:e2,x3,x4
                                                
Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

pron__x only takes one argument of type x. Looking at this in the context of the sentence intuitively, it appears as though it is supposed to stuff something into x3 that represents the person that the pronoun is referring to. But how does the predicate tell which pronoun it is?

If you examine the Prolog that is generated, you see that it is passed an Arg with metadata that supplies the needed information :

Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

Prolog: 
d_pronoun_q__xhh(
    arg(X56613, var([name(x3), type(reg), pers(2)])), 
    
    % The first argument of d_pron__x has metadata to show which pronoun is referenced
    arg(d_pron__x(arg(X56613, var([name(x3), type(reg), pers(2)]))), term), 
    
    arg(d_which_q__xhh(
        arg(X56615, var([name(x4), type(reg), pers(3), num(sg)])), 
        arg(d_noun__x(arg('place', word('place')), arg(X56615, var([name(x4), type(reg), pers(3), num(sg)]))), term), 
        arg(conj(d_loc_nonsp__exx(arg(E56616, var([name(e2), index(yes), tense(pres)])), arg(X56613, var([name(x3), type(reg), pers(2)])), arg(X56615, var([name(x4), type(reg), pers(3), num(sg)])), arg(create, var([type(quote)]))), 
            conj(query_loc_nonsp__exx(arg(E56616, var([name(e2), index(yes), tense(pres)]))))), term), 
        arg(Quote56619, var([type(quote)]))), term), 
    arg(Quote56617, var([type(quote)]))).
    

It is this: arg(X56613, var([name(x3), type(reg), pers(2)]))

The pers(2) part of the metadata indicates that this variable should contain something in the “second person”, i.e. the value of the “second person pronoun”, i.e. “you”. So, the d_pron__x predicate needs to use that metadata and fill in the symbol that means “whoever this pronoun means”. In our case this is easy because all Perplexity knows about is “You”. “You” always means idLexi since the user is always talking to Lexi:

% Sets the variable to the symbol that the pronoun represents  
% Right now we only do "you"  
d_pron__x(PronounArg) :- 
  arg(Pronoun, PronounMeta) = PronounArg,  
  % argVariableMetadata is a predicate that is true if the metadata in the
  % the first argument is actually in the metadata in the second argument 
  tryError(argVariableMetadata(pers(2), PronounMeta),
    context(d_pron__x, dontKnowPronounReference, PronounArg))),  
  % For now, defaultActor always returns idLexi in Pronoun
  defaultActor(Pronoun).    

What if the user asked “Where am I?” The same predicates are generated, this time with first person metadata, and the argVariableMetadata(pers(2), PronounMeta) predicate fails. The tryError predicate is used, as described in the section on failures, so that we can give a good error back to the user describing why. In this case, Perplexity translates dontKnowPronounReference to “I don’t know who you’re referring to!”

Handling Wh-Questions: which_q__xhh

Next up is which_q__xhh:

                          ┌pron__x:x3
 pronoun_q__xhh:x3,h11,h12┤
                          │                     ┌place_n__x:x4
                          └which_q__xhh:x4,h7,h8┤
                                                └loc_nonsp__exx:e2,x3,x4
                                                
Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

Based on _q_ and the signature: xhh, we can see which_q__xhh is a quantifier. Looking at the tree intuitively it appears that it is there to introduce the x4 variable that will hold the location we are looking for in “Where are you?” In fact, it doesn’t seem like it needs to do any other “quantification”, so its implementation will be the same “default quantifier” implementation that pronoun_q had:

d_which_q__xhh(VariableArg, RstrArg, BodyArg, QuoteArg) :- 
  arg(_, _) = VariableArg, 
  arg(_, _) = RstrArg, 
  arg(_, _) = BodyArg, 
  arg(_, _) = QuoteArg,
  
  resolveTerm(RstrArg, QuoteArg),
  resolveTerm(BodyArg, QuoteArg).

However, the reason it has a specific name and not a generic one like udef_q__xhh is that it is giving us a crucial piece of data: this is a “wh-question” (where, what, who, etc). The user expects an actual answer or set of answers back from it in the variable it introduces: x4. This doesn’t affect how it is implemented, but it does affect how we process the result and respond to the user. The code that responds to the user special cases this predicate to answer properly.

Handling Nouns: place_n__x

The next predicate, place_n__x, is just checking if x4 is a “place” or listing all of the “places” in it:

                          ┌pron__x:x3
 pronoun_q__xhh:x3,h11,h12┤
                          │                     ┌place_n__x:x4
                          └which_q__xhh:x4,h7,h8┤
                                                └loc_nonsp__exx:e2,x3,x4
                                                
Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

This is the kind of scenario Prolog was built for. We just need to check to see if the identifier in x4 is an instance or specializes place using our ontology. In fact, this is how all nouns work. The process of transforming MRS to Prolog transforms all noun predicates from being unique predicates to the same predicate with the noun lemma as an argument:

place_n__x(x4) converts to d_noun__x('place', x4)

So, d_noun__x is implemented one time and reused for all cases. Note there are several interpretations of noun and some special cases, but here is the implementation for this MRS:

% X is a noun of type X if it specializes it  
d_noun__x(TypeNameArg, XArg) :- 
  arg(TypeName, _) = TypeNameArg, 
  arg(X, _) = XArg,  
  % Nouns come in the way the user said them, i.e. as a *string*  
  % They need to be mapped to an ID using nameOf()
  % nameOf() looks for things that have something in the specialization chain 
  % that uses the supplied name
  tryError(nameOf(X, _, TypeName, _),  
   context(d_noun__x1, contextNoItemsNamed, TypeNameArg)).

We report an error with tryError that translates to “There aren’t any X” if we can’t find anything.

Note that adjectives are treated uniformly like this too, as described in the section about ontology.

Handing Events: loc_nonsp__exx

The last predicate is the trickiest from many perspectives: loc_nonsp__exx:

                          ┌pron__x:x3
 pronoun_q__xhh:x3,h11,h12┤
                          │                     ┌place_n__x:x4
                          └which_q__xhh:x4,h7,h8┤
                                                └loc_nonsp__exx:e2,x3,x4
                                                
Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

What Does loc_nonsp__exx Mean?

There is a great write-up on this term in the ERG documentation. It is called an “implicit locative”. Basically it means that the thing in the first argument x3 is “at” the location in x4. I use the term “at” because it is really generic: and that’s what an “implicit locative” does, it locates something very generally “at” a place.

So this predicate needs to be true if x3 is “at” x4 or (in this case) return all the places that x3 could be validly said to be “at”.

Handling Events/Prepositions as Verbs: loc_nonsp__exx

This is the first predicate that has an argument that is an event (i.e. begins with an e). Events are a deep area that is described in its own section. Because of this, when the system converts MRS to Prolog, it will add a Quote argument to the signature like this:

d_loc_nonsp__exx(E56616, X56613, X56615, Quote)

This means d_loc_nonsp__exx needs to be prepared to quote or eval during execution.

Moreover, you’ll notice that there is no verb predicate anywhere in the MRS:

                          ┌pron__x:x3
 pronoun_q__xhh:x3,h11,h12┤
                          │                     ┌place_n__x:x4
                          └which_q__xhh:x4,h7,h8┤
                                                └loc_nonsp__exx:e2,x3,x4
                                                
Logic: pronoun_q__xhh(x3, pron__x(x3), which_q__xhh(x4, place_n__x(x4), loc_nonsp__exx(e2, x3, x4)))

In many cases like this where the verb is “be” the ERG will simply not include it and treat the preposition like a verb. So loc_nonsp__exx is treated like the verb “be at a location”. This means we need to follow all the guidelines for implementing loc_nonsp__exx as a verb even though it is really a preposition. Those guidelines include how to handle quoting.

For reasons described in that section, we’ll need a few predicates:

In Perplexity, loc_nonsp__exx just delegates to the preposition d_at_p_loc__exx since that is what it means:

d_loc_nonsp__exx(EventIDArg, ThingArg, LocationArg, CreateOrEvalArg) :- 
    arg(_, _) = EventIDArg, 
    arg(_, _) = ThingArg, 
    arg(_, _) = LocationArg, 
    arg(_, _) = CreateOrEvalArg,  
  d_at_p_loc__exx(EventIDArg, ThingArg, LocationArg, CreateOrEvalArg).  
  
query_loc_nonsp__exx(EventIDArg) :- 
    arg(_, _) = EventIDArg,  
  query_at_p_loc__exx(EventIDArg).

So, here’s the implementation of the d_at_p_loc__exx event predicate. It does the work of quoting/unquoting the arguments. All the helper predicates are building up the event structure for the verb as described in the events section :

% Event Predicate
d_at_p_loc__exx(EventIDArg, ThingArg, LocationArg, QuoteArg):- 
    arg(_, _) = EventIDArg, 
    arg(_, _) = ThingArg, 
    arg(_, _) = LocationArg, 
    arg(_, _) = QuoteArg,  
  eventPredicateArg(EventIDArg, QuoteArg),  
  relArgCreate(EventIDArg, EventIDArg, instanceOf, arg(idEvent, term), QuoteArg),  
  actionForEvent(EventIDArg, arg(task_at_p_loc__exx, term), QuoteArg),  
  locativePreposition(EventIDArg, ThingArg, arg(d_at_p_loc__exx, term), LocationArg, QuoteArg).  

The default world predicates are responsible for doing the semantic work of all the alternative meanings in the default (i.e. real) world. The physics ontology defines the location of a thing as being “the object it is a part of, inside or adjacent to”. This is all implemented by the locationOf predicate in the first alternative below. The second alternative is needed due to implementation details of the logic that I won’t go into. Basically, sometimes the location isn’t specific to a side. It is just generally “somewhere” and that somewhere isn’t specific about what side it is touching:

% default world predicate for when Location is an inside face  
d_at_p_loc__exx(EventIDArg, ThingArg, LocationArg, CreateOrEvalArg):- 
    arg(default, _) = EventIDArg, 
    arg(Thing, _) = ThingArg, 
    arg(Location, _) = LocationArg, 
    arg(eval, _) = CreateOrEvalArg,  
  defaultActor(Actor),  
  tryError(locationOf(Actor, Thing, Location),  
    location(d_at_p_loc__exx, thingHasNoLocation, Thing, Location)).  
  
% default world predicate for when Location is just an object.  
% Needed to support "Are you there?"  
d_at_p_loc__exx(EventIDArg, ThingArg, LocationArg, CreateOrEvalArg):- 
    arg(default, _) = EventIDArg, 
    arg(Thing, _) = ThingArg, 
    arg(Location, _) = LocationArg, 
    arg(eval, _) = CreateOrEvalArg,  
  tryError(containedIn(Thing, Location, _),  
    location(d_at_p_loc__exx, thingHasNoLocation, Thing, Location)).  

Finally, we need the query predicate to glue it all together. This works just like all verbs and details like the verbStructure and storeArg predicates is described in the section on verbs:

query_at_p_loc__exx(EventIDArg) :- 
    arg(_, _) = EventIDArg, 
  % verbStructure() checks to make sure the right information has been
  % built up *somehow* by the time we got here 
  verbStructure(EventIDArg, 
    [tree(req, idLocativePreposition, []), 
    tree(req, idLocativePrepositionLeft, []), 
    tree(req, idLocativePrepositionRight, []), 
    tree(opt, idStativeEvent, [])]),  
  % retrieve the original arguments from the event predicate
  d_at_p_loc__exx(EventIDArg, ThingArg, LocationArg, arg(eval, term)),  
  % actually execute the default world predicates
  d_at_p_loc__exx(arg(default, term), ThingArg, LocationArg, arg(eval, term)),  
  % Store any free variables
  storeArg(EventIDArg, ThingArg),  
  storeArg(EventIDArg, LocationArg).

So, even though _at_p_loc__exx is really a preposition, we implement it as a verb for this MRS. The pattern we use for implementing verbs also ensures that it can be called as a preposition when it is used differently. It’ll just use the default world predicates.

Handling i, u and p Arguments

As described in the overview of the MRS, most arguments you’ll encounter in the ERG are of type e, x, or h, but there are occasionally i, u, and p arguments to deal with.

If it is an i variable representing a dropped argument (or a u or p argument is not used), the MRS to Prolog converter will not pass it as a variable. It will use an atom representing its type like i . This allows the predicate to detect the case easily and perhaps behave differently.

Here’s an example of d_yell_v_1__eix being passed a dropped i argument and filling in a default value for it:

% default world predicate that has an unused i argument
% fills in "who" to be the default actor (idLexi)
d_yell_v_1__eix(EventIDArg, UnusedIArg, WhatArg, CreateOrEvalArg):- 
  arg(default, _) = EventIDArg, 
  % HERE is where we detect the unused `i` argument
  arg(i, _) = UnusedIArg, 
  arg(_, _) = WhatArg, 
  arg(eval, _) = CreateOrEvalArg,  
  defaultActor(Actor), 
  % And then just do the normal "yell" predicate that takes an `x` as ARG1
  % with this default actor 
  d_yell_v_1__exx(EventIDArg, arg(Actor, term), WhatArg, CreateOrEvalArg).  
  
% This version of "yelling" gets passed *who* is supposed to yell in
% the position of the unused argument above
d_yell_v_1__exx(EventIDArg, WhoArg, WhatArg, CreateOrEvalArg):- 
  arg(default, _) = EventIDArg, 
  arg(Who, _) = WhoArg, 
  arg(What, _) = WhatArg, 
  arg(eval, _) = CreateOrEvalArg, 
  % See if that Actor is yelling 
  tryError(isYelling(Who),
    context(d_yell_v_1__exx, actorNotYelling, WhoArg))). 

If the i, u, and p is actually used by two predicates, then it is clearly not dropped. The the MRS to Prolog converter will just a create a variable for it. I’ve found that treating variables created in this way like an x logical argument normally works fine.

Hierarchical Task Network

As described in the section on implementing verbs, to implement a verb that is a command like “go to the green room”, you need to use the Hierarchical Task Network planner and implement HTN Methods to do the work.

Those links will give you the background to understand this planning approach, but I’ll describe here the format of HTNs that I’ve implemented in Prolog. After understanding the concepts, you’ll be able to use these predicates to build them.

HTN Methods

The basic format of an HTN Method in the Perplexity engine is

methodName(Arg1, Arg2, ... , Context) :- htnMethod([Option1], Context,  
  if([
    ... Your predicates go here ...
  ]),  
  do([methodOrOperationToRun1(Arg1), methodOrOperationToRun2(Arg1, Arg2)]) ).

An example is after the next section.

HTN Operations

The format an HTN Operation in Perplexity is:

operationName(Arg1, Arg2, ..., Context) :- 
  htnOperation(Context, operationName(Arg1, Arg2),  
    del([rel(Subject, Relation, Object)]),  
    add([rel(Subject, Relation, Object)]) ).

An example is shown next.

HTN Example

Here is an example of a set of HTN Methods and one HTN Operation that turn an object, for example, inside a safe from “unknown” to “known” by the system:

% if nothing inside, just make the inside face itself known  
makeInsideObjectsKnown(InitialObject, Context) :- htnMethod([], Context,  
  if([  
     % We could be passed a face or an object, so get the inside face of whatever  
   % this represents  
   insideOfObject(InitialObject, InsideFace), 
   getFace(ContainerObject, idLocalInside, InsideFace),  
   not(containedIn(_, ContainerObject, 1))  
  ]),  
  do([makeKnown(InsideFace)]) ).  
  
makeInsideObjectsKnown(InitialObject, Context) :- htnMethod([], Context,  
  if([  
   % We could be passed a face or an object, so get the inside face of whatever  
   % this represents  insideOfObject(InitialObject, InsideFace), 
   getFace(ContainerObject, idLocalInside, InsideFace),  
   containedIn(ContainedObject, ContainerObject, 1)  
  ]),  
  do([makeKnown(InsideFace), makeKnown(ContainedObject)]) ).  
  
% If it is already known, nothing to do!  
makeKnown(Object, Context) :- htnMethod([], Context,  
  if([  
        known(Object)  
  ]),  
  do([]) ).  
  
makeKnown(Object, Context) :- htnMethod([], Context,  
  if([  
        unknownProperty(Object, PropertyID, State)  
  ]),  
  do([opMakeKnown(Object, PropertyID, State)]) ).

opMakeKnown(Object, PropertyID, State, Context) :- 
  htnOperation(Context, opMakeKnown(Object, PropertyID, State),  
  del([rel(PropertyID, propertyOf, Object), 
     rel(PropertyID, instanceOf, idUnknownObject), rel(PropertyID, hasState, State)]),  
  add([]) ).

Summary

Now you’ve seen how build all of the predicates for the phrase “Where are you?”. Once you understand the basic approach, the real work is in figuring out what these predicates mean and how to represent that meaning logically.