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:
- Parse the phrase into MRS
- Scope-resolve the MRS into a set of trees
- 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:
- Rely on my native understanding of what English words mean
- Learn how to interpret the meaning of the predicate name and signature
- Look at it in context in the scope-resolved tree and see if it was obvious what it was trying to do …and use the resources above if I couldn’t figure it out
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:
place_n__x
takes a singlex
argument which is a classic logic variable. Probably, its job is to see ifx
holds a place.loc_nonsp__exx
introduces ane
argument so probably it can get passed information from other predicates as described in the section on Eventsloc_nonsp__exx
takes two x variables, one is filled in bypron__x
(probably representing the pronoun “You”) the other filled byplace_n__x
probably representing “places”. So, logically its job must be to be true if “you” are in the “place”- etc.
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:
- an
x
argument which is the logical variable that is “introduced” or “created” by the predicate - the first
h
argument calledRSTR
which is the branch of the tree that is the “thing being quantified” - the second
h
argument calledBODY
which is the branch of the tree that is “doing something” with the quantified value
“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:
- “one, but any one will do” in the case of “a”
- “one, and I mean a special one” in the case of “the”
- “a few” in the case of “some”
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:
- provide the variable
X
- logically execute whatever is in
RSTR
- logically execute whatever is in
BODY
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:
- a event predicate: to quote/unquote the arguments into a data structure.
- a query predicate: to unquote the arguments using the event predicate then execute them using one or more default world predicates
- one or more default world predicates: to evaluate the different meanings of the word
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)]) ).
methodName
is the name of your method.htnMethod
is an engine predicate that you must use like the example.Context
must be the last argument passed in to the predicate (it is used by the engine)Option1, Option2
are options on how the method executes. OnlyallOf
is currently supported and works just like described in the linksmethodOrOperationToRun1/2
are the methods to run if theif
clause succeeds
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)]) ).
operationName
is the name of the operationArg1
,Arg2
are the arguments to it- The name of the predicate is passed again to the
htnOperation
predicate for logging - The
del
andadd
predicates just take a list of relations to add or delete
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.