Difference between revisions of "Language Reference/Terms/Anonymous Predicates"
< Language Reference | Terms
m (header levels) |
(Review) |
||
(13 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
==== Anonymous Predicates ==== | |||
An anonymous predicate is an expression that evaluates to a predicate value. The value can be bound to a variable, passed as an argument, or returned as a result, but it has no name in any class, interface, or implementation. | |||
==== Syntax ==== | Anonymous predicates have the ability to capture values from the context in which the expression occurs; this is a powerful ability that can be used to avoid a rather excessive amount of strange/unpleasant code. | ||
===== Syntax ===== | |||
Anonymous predicates are terms: | Anonymous predicates are terms: | ||
Line 15: | Line 17: | ||
<vipbnf><AnonymousPredicate> : one of | <vipbnf><AnonymousPredicate> : one of | ||
{ ( <Arg>-comma-sep-list ) = <Term> } | { ( <Arg>-comma-sep-list ) = <Term> } | ||
{ ( <Arg>-comma-sep-list ) = <Term> :- <Term> } | { ( <Arg>-comma-sep-list ) = <Term> :- <Term> } | ||
{ ( <Arg>-comma-sep-list ) :- <Term> } | { ( <Arg>-comma-sep-list ) :- <Term> } | ||
{ = <Term> } | { = <Term> } | ||
{ = <Term> :- <Term> } | { = <Term> :- <Term> } | ||
{ :- <Term> }</vipbnf> | { :- <Term> }</vipbnf> | ||
Leaving out the argument list means "the required number of arguments" and can be used whenever the arguments are not used. | Leaving out the argument list means "the required number of arguments" and can be used whenever the arguments are not used inside the predicate expression. | ||
==== Semantics ==== | ===== Semantics ===== | ||
An anonymous predicate expression evaluates to a predicate value. | An anonymous predicate expression evaluates to a predicate value. | ||
Line 54: | Line 56: | ||
inc(X) = X+1.</vip> | inc(X) = X+1.</vip> | ||
Where the clause <vp>(X) = X+1</vp> can be found in the last line | Where the clause <vp>(X) = X+1</vp> can be found in the last line; i.e., this time in a named predicate. | ||
Variables that are bound outside (i.e. before the occurrence of) an anonymous predicate can be used inside the anonymous predicate. | Variables that are bound outside (i.e., before the occurrence of) an anonymous predicate can be used inside the anonymous predicate. The value of the variable will be captured by the anonymous predicate. | ||
Variables that are bound in an anonymous predicate are local variables in the anonymous predicate. | Variables that are bound in an anonymous predicate are local variables in the anonymous predicate. | ||
Line 64: | Line 66: | ||
An anonymous predicate can capture context, which means that it can refer to things that are defined in its context, especially facts and variables from the clause. | An anonymous predicate can capture context, which means that it can refer to things that are defined in its context, especially facts and variables from the clause. | ||
===== Capturing | ===== Capturing variables ===== | ||
An anonymous predicate occurs in a clause, and this clause may contain variables. Those variables that are bound before the anonymous predicate is met can be used inside the anonymous predicate. | |||
This code illustrates how a variable is captured: | This code illustrates how a variable is captured: | ||
Line 84: | Line 86: | ||
stdio::writef("A = %, B = %", A, B).</vip> | stdio::writef("A = %, B = %", A, B).</vip> | ||
We call <vp>createAdder</vp> with <vp>17</vp> as argument. So in the <vp>createAdder</vp> clause <vp>A</vp> is <vp>17</vp>, and therefore the result is <vp>{ (X) = X+17 }</vp>. | We call <vp>createAdder</vp> with <vp>17</vp> as argument. So in the <vp>createAdder</vp> clause <vp>A</vp> is <vp>17</vp>, and therefore the result is <vp>{ (X) = X+17 }</vp>. We say that the anonymous predicate has captured the variable <vp>A</vp>. | ||
Since <vp>Add17</vp> is a predicate that adds <vp>17</vp> to its argument, the output of the code will be: | Since <vp>Add17</vp> is a predicate that adds <vp>17</vp> to its argument, the output of the code will be: | ||
Line 90: | Line 92: | ||
<source lang="text">A = 21, B = 37</source> | <source lang="text">A = 21, B = 37</source> | ||
===== Capturing ellipsis (...) ===== | |||
An anonymous predicate can capture the ellipsis variable (i.e., <vp>...</vp>): | |||
An anonymous predicate can capture the ellipsis variable (i.e. | |||
<vip>clauses | <vip>clauses | ||
Line 100: | Line 101: | ||
qqq(W).</vip> | qqq(W).</vip> | ||
<vp>W</vp> captures the ellipsis variable. <vp>qqq</vp> receives a | <vp>W</vp> captures the ellipsis variable. <vp>qqq</vp> receives a nullary predicate; when this predicate is invoked the captured ellipsis variable will be written to the standard output device. | ||
===== Capturing | ===== Capturing facts ===== | ||
An anonymous predicate can access facts. If it is created by a class predicate it can access class facts. | An anonymous predicate can access facts. If it is created by a class predicate it can access class facts. If it is created by an object predicate it can access both object and class facts. | ||
Consider this code that captures a class fact: | Consider this code that captures a class fact: | ||
Line 120: | Line 121: | ||
stdio::writef("B2 = %", B()).</vip> | stdio::writef("B2 = %", B()).</vip> | ||
Both <vp>A</vp> and <vp>B</vp> increment the class fact count, so the result is | Both <vp>A</vp> and <vp>B</vp> increment the class fact <vp>count</vp>, so the result is: | ||
<source lang="text">A1 = 1, B1 = 2, A2 = 3, B2 = 4</source> | <source lang="text">A1 = 1, B1 = 2, A2 = 3, B2 = 4</source> | ||
Line 139: | Line 140: | ||
stdio::writef("B2 = %", B()).</vip> | stdio::writef("B2 = %", B()).</vip> | ||
In this case <vp>A</vp> and <vp>B</vp> | In this case <vp>A</vp> and <vp>B</vp> come from two different objects, which each have a <vp>count</vp> fact, so the output will be: | ||
<source lang="text">A1 = 1, B1 = 1, A2 = 2, B2 = 2</source> | <source lang="text">A1 = 1, B1 = 1, A2 = 2, B2 = 2</source> | ||
Technically, the class version actually | Technically, the class version actually does not capture anything; it merely has access to the fact. Likewise, the object version does not actually capture the fact; instead it captures <vp>This</vp> and through <vp>This</vp> it obtains access to the object facts. | ||
===== Capturing This ===== | ===== Capturing This ===== | ||
As described above it is possible to capture <vp>This</vp> and thereby | As described above it is possible to capture <vp>This</vp> and thereby gain access to object facts. The same mechanism gives access to calling object predicates. | ||
<vip>clauses | <vip>clauses | ||
Line 153: | Line 154: | ||
clauses | clauses | ||
inc() :- count := count+1. | inc() :- count := count+1.</vip> | ||
This can also be used directly: | |||
clauses | <vp>This</vp> can also be used directly: | ||
<vip>clauses | |||
ppp() = { () = aaa::rrr(This) }.</vip> | ppp() = { () = aaa::rrr(This) }.</vip> | ||
===== Nesting ===== | ===== Nesting ===== | ||
Line 171: | Line 173: | ||
stdio::writef("R(11) = %", R(11)).</vip> | stdio::writef("R(11) = %", R(11)).</vip> | ||
To obtain <vp>Q</vp> we call <vp>P</vp> with <vp>3300</vp>, so <vp>A</vp> is <vp>3300</vp> and <vp>Q</vp> therefore becomes <vp>{ (B) = 3300+B | To obtain <vp>Q</vp> we call <vp>P</vp> with <vp>3300</vp>, so <vp>A</vp> is <vp>3300</vp> and <vp>Q</vp> therefore becomes <vp>{ (B) = 3300+B }</vp>; likewise <vp>R</vp> becomes <vp>{ (B) = 2200+B }</vp>. So the output is: | ||
<source lang="text">Q(11) = 3311, R(11) = 2211</source> | <source lang="text">Q(11) = 3311, R(11) = 2211</source> | ||
===== Syntactic Sugar ===== | |||
If you do not need the arguments they can be skipped. | |||
So this code fragment: | |||
<vip> | |||
P = { (_) :- succeed }, | |||
Q = { (_, _) = 0 }, | |||
R = { (_, _, _) = _ :- fail }. | |||
</vip> | |||
can be shortened to: | |||
<vip> | |||
P = { :- succeed }, | P = { :- succeed }, | ||
Q = { = 0 }, | Q = { = 0 }, | ||
R = { = _ :- fail }</vip> | R = { = _ :- fail }. | ||
</vip> | |||
Notice that the arguments are completely skipped. If you write <vp>()</vp> it means zero arguments, whereas skipping the arguments means "a suitable amount" of arguments. | Notice that the arguments are completely skipped. If you write <vp>()</vp> it means zero arguments, whereas skipping the arguments means "a suitable amount" of arguments. | ||
Line 193: | Line 200: | ||
==== Examples of practical usage ==== | ==== Examples of practical usage ==== | ||
The examples assume that the PFC scopes <vp>core</vp>, <vp>std</vp>, <vp>stdio</vp>, <vp>list</vp> and <vp>string</vp> are open. | |||
===== Dummy predicates ===== | ===== Dummy predicates ===== | ||
Line 199: | Line 206: | ||
Anonymous predicates are good for creating dummy predicate values: | Anonymous predicates are good for creating dummy predicate values: | ||
<vip>ppp( { = true } ), % | <vip> | ||
qqq( { :- succeed } ), % | ppp( { = true } ), % do not filter (boolean) | ||
rrr( { = 17 } ) | qqq( { :- succeed } ), % do not filter (determ) | ||
rrr( { = 17 } ). % all rows must have height 17 | |||
</vip> | |||
===== Adaptation ===== | ===== Adaptation ===== | ||
Line 207: | Line 216: | ||
In cases where you need a predicate and have one that is almost suitable, you can make the adaptation using an anonymous predicate. | In cases where you need a predicate and have one that is almost suitable, you can make the adaptation using an anonymous predicate. | ||
===== Index adaptation ===== | ====== Index adaptation ====== | ||
Consider the predicate write3: | Consider the predicate write3: | ||
Line 215: | Line 224: | ||
clauses | clauses | ||
write3(Indexer) :- | write3(Indexer) :- | ||
foreach I = fromTo(0,2) do | foreach I = std::fromTo(0,2) do | ||
write(Indexer(I), "\n") | write(Indexer(I), "\n") | ||
end foreach.</vip> | end foreach.</vip> | ||
Indexer implements an "array" of strings | Indexer implements an "array" of strings; <vp>write3</vp> will write the three strings found at the indexes <vp>0</vp>, <vp>1</vp> and <vp>2</vp>. So <vp>write3</vp> assumes that the "array" index is zero-based. | ||
However, the "array" we have uses a one-based index: | However, the "array" we have uses a one-based index: | ||
Line 231: | Line 241: | ||
raiseError().</vip> | raiseError().</vip> | ||
Using an anonymous predicate we can easily adapt the one-based array to the zero-based usage: | |||
<vip>% myArray is | <vip>% myArray is 1-based, write3 requires 0-based | ||
Arr = { (N) = myArray(N+1) }, | Arr = { (N) = myArray(N+1) }, | ||
write3(Arr)</vip> | write3(Arr).</vip> | ||
So we get the expected output: | So we get the expected output: | ||
Line 243: | Line 253: | ||
Third</source> | Third</source> | ||
====== Parameter adaptation ====== | |||
In this code <vp>listChildren</vp> will call a <vp>ChildWriter</vp> predicate for each "C is the child of P"-pair: | |||
In this code <vp>listChildren</vp> will call a <vp>ChildWriter</vp> predicate for each " | |||
<vip>class predicates | <vip>class predicates | ||
Line 256: | Line 265: | ||
CW("Son2", "Father").</vip> | CW("Son2", "Father").</vip> | ||
We will however prefer to list the " | We will however prefer to list the "P is the parent of C" using the predicate <vp>wParent</vp>: | ||
<vip>class predicates | <vip>class predicates | ||
Line 267: | Line 276: | ||
<vip>Swap = { (A,B) :- wParent(B,A) }, | <vip>Swap = { (A,B) :- wParent(B,A) }, | ||
listChildren(Swap)</vip> | listChildren(Swap).</vip> | ||
And then the | And then the output becomes the expected: | ||
<source lang="text">Father is the parent of Son1 | <source lang="text">Father is the parent of Son1 | ||
Line 285: | Line 294: | ||
<vip>Fewer = { (C,P) :- wKnowParent(C) }, | <vip>Fewer = { (C,P) :- wKnowParent(C) }, | ||
listChildren(Fewer)</vip> | listChildren(Fewer).</vip> | ||
The output will be: | The output will be: | ||
Line 294: | Line 303: | ||
We can also supply dummy arguments: | We can also supply dummy arguments: | ||
<vip>More = { (_,P) :- addChildren(P, 1) } | <vip>More = { (_,P) :- addChildren(P, 1) }, | ||
listChildren(More)</vip> | listChildren(More).</vip> | ||
Here <vp>addChildren</vp> will "add a count of children to | Here <vp>addChildren</vp> will "add a count of children to P". Since each invocation corresponds to one child we will call <vp>addChildren</vp> supplying <vp>1</vp> as a "dummy" argument. The <vp>More</vp> is thus an adaptor that both throws away an argument and supplies a dummy argument. | ||
===== Filters ===== | ===== Filters ===== | ||
Line 305: | Line 314: | ||
<vip>class predicates | <vip>class predicates | ||
writeFiltered : | writeFiltered : | ||
(string L, | (string L, predicate_dt{integer} Filter). | ||
clauses | clauses | ||
writeFiltered(Label, Filter) :- | writeFiltered(Label, Filter) :- | ||
Line 312: | Line 321: | ||
writef("%\t%\n", Label, FilteredList).</vip> | writef("%\t%\n", Label, FilteredList).</vip> | ||
Filter is used to filter the list <vp>[1,2,3,4,5,6,7,8,9]</vp>; the filtered list and the Label are written to the standard output. | <vp>Filter</vp> is used to filter the list <vp>[1,2,3,4,5,6,7,8,9]</vp>; the filtered list and the <vp>Label</vp> are written to the standard output. | ||
First we use the allow-all filter: | First we use the allow-all filter: | ||
<vip>All = { :- succeed }, | <vip>All = { :- succeed }, | ||
writeFiltered("All", All)</vip> | writeFiltered("All", All).</vip> | ||
This filter simply succeeds for any element, so the output is the entire list: | This filter simply succeeds for any element, so the output is the entire list: | ||
Line 326: | Line 335: | ||
<vip>None = { :- fail }, | <vip>None = { :- fail }, | ||
writeFiltered("None", None)</vip> | writeFiltered("None", None).</vip> | ||
The output from this is the empty list: | The output from this is the empty list: | ||
Line 332: | Line 341: | ||
<source lang="text">None []</source> | <source lang="text">None []</source> | ||
We can also create filters for elements greater than <vp>3</vp> and elements | We can also create filters for elements greater than <vp>3</vp> and elements divisible by <vp>3</vp>: | ||
<vip>GreaterThan3 = { (X) :- X > 3 }, | <vip>GreaterThan3 = { (X) :- X > 3 }, | ||
writeFiltered("> 3", GreaterThan3), | writeFiltered("> 3", GreaterThan3), | ||
Rem3 = { (X) :- 0 = X rem 3 }, | Rem3 = { (X) :- 0 = X rem 3 }, | ||
writeFiltered("Rem3", Rem3)</vip> | writeFiltered("Rem3", Rem3).</vip> | ||
The output from this is: | The output from this is: | ||
Line 343: | Line 352: | ||
<source lang="text">> 3 [4,5,6,7,8,9] | <source lang="text">> 3 [4,5,6,7,8,9] | ||
Rem3 [3,6,9]</source> | Rem3 [3,6,9]</source> | ||
===== Sorting ===== | ===== Sorting ===== | ||
The list package has a sort predicate. But sometimes the default order is not what you need. | The list package has a sort predicate. But sometimes the default order is not what you need. Therefore the list package also has a predicate <vp>sortBy</vp>, which sorts the elements using a programmer-defined compare operation. | ||
Let us first consider string sorting, using this predicate: | Let us first consider string sorting, using this predicate: | ||
Line 361: | Line 369: | ||
Sorted = sortBy(C, List), | Sorted = sortBy(C, List), | ||
write(Label, "\n"), | write(Label, "\n"), | ||
foreach S = getMember_nd(Sorted) do | foreach S = list::getMember_nd(Sorted) do | ||
writef(" %\n", S) | writef(" %\n", S) | ||
end foreach.</vip> | end foreach.</vip> | ||
Line 370: | Line 378: | ||
writeStringsSorted("Normal", Normal), | writeStringsSorted("Normal", Normal), | ||
Descending = { (A,B) = compare(B,A) }, | Descending = { (A,B) = compare(B,A) }, | ||
writeStringsSorted("Descending", Descending)</vip> | writeStringsSorted("Descending", Descending).</vip> | ||
The output looks like this: | The output looks like this: | ||
Line 396: | Line 404: | ||
<vip>domains | <vip>domains | ||
person = p(string First, string Last). | person = p(string First, string Last).</vip> | ||
For the demonstration we will use this test predicate: | For the demonstration we will use this test predicate: | ||
class predicates | |||
<vip>class predicates | |||
writePersonsSorted : | writePersonsSorted : | ||
(string Label, comparator{person} Comparator). | (string Label, comparator{person} Comparator). | ||
Line 413: | Line 423: | ||
Sorted = sortBy(C, List), | Sorted = sortBy(C, List), | ||
write(Label, "\n"), | write(Label, "\n"), | ||
foreach p(F,L) = getMember_nd(Sorted) do | foreach p(F,L) = list::getMember_nd(Sorted) do | ||
writef(" % %\n", F, L) | writef(" % %\n", F, L) | ||
end foreach.</vip> | end foreach.</vip> | ||
Line 422: | Line 432: | ||
writePersonsSorted("Normal", Normal), | writePersonsSorted("Normal", Normal), | ||
Descending = { (A,B) = compare(B,A) }, | Descending = { (A,B) = compare(B,A) }, | ||
writePersonsSorted("Descending", Descending)</vip> | writePersonsSorted("Descending", Descending).</vip> | ||
Since the compare predicate uses left-to-right lexicographic order on the <vp>p</vp>-functor, the result is the same as before: | Since the <vp>compare</vp> predicate uses left-to-right lexicographic order on the <vp>p</vp>-functor, the result is the same as before: | ||
<source lang="text">Normal | <source lang="text">Normal | ||
Line 448: | Line 458: | ||
<vip>LN = { (p(_,L1), p(_, L2)) = compare(L1,L2) }, | <vip>LN = { (p(_,L1), p(_, L2)) = compare(L1,L2) }, | ||
writePersonsSorted("LastName", LN)</vip> | writePersonsSorted("LastName", LN).</vip> | ||
The result is what we expect: | The result is what we expect: | ||
Line 461: | Line 471: | ||
Uma Thurman | Uma Thurman | ||
John Wayne</source> | John Wayne</source> | ||
===== Capturing context ===== | ===== Capturing context (threads & callbacks) ===== | ||
As mentioned a very powerful feature of anonymous predicates is the ability to capture context. | As mentioned a very powerful feature of anonymous predicates is the ability to capture context. The examples in this section show some ways you can use this. | ||
===== Background | ====== Background threads ====== | ||
The routine for starting a thread takes a | The routine for starting a thread takes a nullary predicate and runs it in the new thread. But you nearly always need to pass some input data to the job in the new thread. | ||
This is possible in several ways, but the absolutely simplest way is to use anonymous predicates. | This is possible in several ways, but the absolutely simplest way is to use anonymous predicates. | ||
The project <vp>bgDemo</vp> from the Visual Prolog example collection (that can be installed from the IDE) | The project <vp>bgDemo</vp> from the Visual Prolog example collection (that can be installed from the IDE) uses this method. | ||
The project has a form that can start background job and display status information from the job in a <vp>jobControl</vp> that is added to the form. | The project has a form that can start a background job and display status information from the job in a <vp>jobControl</vp> that is added to the form. | ||
A background job is a predicate that will receive a <vp>jobLog</vp>, which it can use to report status and completion degree: | A background job is a predicate that will receive a <vp>jobLog</vp>, which it can use to report status and completion degree: | ||
<vip>domains | <vip>domains | ||
job = (jobLog Log). | job = (jobLog Log).</vip> | ||
A jobLog looks like this: | |||
interface jobLog | A <vp>jobLog</vp> looks like this: | ||
<vip>interface jobLog | |||
properties | properties | ||
Line 488: | Line 499: | ||
end interface jobLog</vip> | end interface jobLog</vip> | ||
The job can report completion degree by setting the completion property (range <vp>0</vp> to <vp>1</vp>). | The job can report completion degree by setting the <vp>completion</vp> property (range <vp>0</vp> to <vp>1</vp>). Likewise, the <vp>status</vp> property can be used to reflect the current status of the job. | ||
The status and completion will be shown in the form together with a job name. | The status and completion will be shown in the form together with a job name. | ||
Line 504: | Line 515: | ||
_ = thread::start(Action).</vip> | _ = thread::start(Action).</vip> | ||
In this context it is the last three lines that are interesting. | In this context it is the last three lines that are interesting. <vp>thread::start</vp> takes a nullary predicate as argument, but a job is a predicate that takes a <vp>jobLog</vp> as argument. Therefore we create an anonymous predicate <vp>Action</vp>, which takes no arguments but invokes <vp>Job</vp> on the <vp>JobLog</vp>. The anonymous predicate has captured both <vp>Job</vp> and <vp>JobLog</vp> from the context, and subsequently both these values are transferred to the new thread even though this thread only receives a nullary predicate. | ||
The jobs in the <vp>bgDemo</vp> project are merely dummy jobs that only manipulate their <vp>jobLog</vp>. | |||
The jobs in the <vp>bgDemo</vp> project are merely dummy jobs that only manipulate their <vp>jobLog</vp>. One of them looks like this: | |||
<vip>clauses | <vip>clauses | ||
Line 523: | Line 535: | ||
Log:status := "finished".</vip> | Log:status := "finished".</vip> | ||
It has two loops which run from <vp>From</vp> to <vp>To</vp> and calculates the completion and sets it on the <vp>Log</vp>. | It has two loops which run from <vp>From</vp> to <vp>To</vp> and calculates the completion and sets it on the <vp>Log</vp>. It also sets the status text before, between and after the loops. | ||
You may notice that the job does not have the proper job type, because a proper job only has one argument (the <vp>jobLog</vp>) | |||
Again it is anonymous predicates that help us. | You may notice that the job does not have the proper job type, because a proper job only has one argument (the <vp>jobLog</vp>); this job has three arguments. | ||
Again it is anonymous predicates that help us. The code that adds the jobs to the form looks like this: | |||
<vip>predicates | <vip>predicates | ||
Line 542: | Line 555: | ||
...</vip> | ...</vip> | ||
In a more realistic program, it is most likely that <vp>From</vp> and <vp>To</vp> would not be constants, but rather parameters passed from some outer place. | In a more realistic program, it is most likely that <vp>From</vp> and <vp>To</vp> would not be constants, but rather parameters passed from some outer place. In that case these anonymous predicates would also capture variables from the context. | ||
The <vp>jobLog</vp> in the <vp>bgDemo</vp> illustrates one more usage of anonymous predicates. | |||
The solution is to | The <vp>jobLog</vp> in the <vp>bgDemo</vp> illustrates one more usage of anonymous predicates. The <vp>jobLog</vp> passes the completion and the status information to a <vp>jobControl</vp>. The <vp>jobControl</vp> is a GUI control on the <vp>jobForm</vp> capable of doing a suitable rendering of the information. This however gives a synchronization problem, because GUI controls are not thread safe and here we want to update some controls from a background thread. This can lead to conflicts, because it is the main thread that draws the controls. | ||
The solution is to transfer the update of the control to the GUI thread. We do this by posting actions to the control. | |||
The implementation of the status update looks like this: | The implementation of the status update looks like this: | ||
Line 552: | Line 567: | ||
jobCtrl:postAction(Action).</vip> | jobCtrl:postAction(Action).</vip> | ||
<vp>Action</vp> is a | <vp>Action</vp> is a nullary predicate that will set the status in the <vp>jobCtrl</vp>. We post this action to the <vp>jobCtrl</vp>. When the <vp>jobCtrl</vp> receives the action it invokes it and is thus updated. This way, the actual update of the control will be performed by the GUI thread. | ||
This anonymous predicate not only captures the <vp>Status</vp> variable it also captures the <vp>jobCtrl</vp> fact. | This anonymous predicate not only captures the <vp>Status</vp> variable it also captures the <vp>jobCtrl</vp> fact. | ||
===== Asynchronous callbacks ===== | ====== Asynchronous callbacks ====== | ||
Assume that we send commands to a remote service. The command execution is | Assume that we send commands to a remote service. The command execution is asynchronous, so when we execute a command we also give a callback action which will be invoked when the execution of the command is finished. To execute a command we must call this predicate: | ||
<vip>predicates | <vip>predicates | ||
Line 563: | Line 578: | ||
(command Cmd, predicate{} OnDone).</vip> | (command Cmd, predicate{} OnDone).</vip> | ||
Based on this predicate we want to create a similar predicate that can execute a list of commands. | Based on this predicate we want to create a similar predicate that can execute a list of commands. A certain command should be executed when the previous command completes. | ||
We will also make our list executor asynchronous, so we supply an action that will be invoked when the entire script of commands | We will also make our list executor asynchronous, so we supply an action that will be invoked when the entire script of commands is finished. | ||
Our script | Our script executor will have the form: | ||
<vip>predicates | <vip>predicates | ||
Line 572: | Line 587: | ||
If the script is empty we simply invoke the <vp>OnDone</vp> action. | If the script is empty we simply invoke the <vp>OnDone</vp> action. | ||
If the script has a command <vp>H</vp> and a rest script <vp>T</vp>, we must first execute <vp>H</vp>, and when it is finished we must execute the rest of the script <vp>T</vp>. | If the script has a command <vp>H</vp> and a rest script <vp>T</vp>, we must first execute <vp>H</vp>, and when it is finished we must execute the rest of the script <vp>T</vp>. So the <vp>OnDone</vp> action we supply when executing <vp>H</vp> must execute <vp>T</vp>. | ||
All in all, the implementation can look like this: | All in all, the implementation can look like this: | ||
Line 582: | Line 597: | ||
executeCommand(H, DoneH).</vip> | executeCommand(H, DoneH).</vip> | ||
We have used an anonymous predicate to perform the execution of the rest of the script. | We have used an anonymous predicate to perform the execution of the rest of the script. This anonymous predicate captures <vp>T</vp> and <vp>OnDone</vp>. | ||
<noinclude>{{LanguageReferenceSubarticle|Terms/Anonymous Predicates}}</noinclude> |
Latest revision as of 13:05, 22 August 2025
Anonymous Predicates
An anonymous predicate is an expression that evaluates to a predicate value. The value can be bound to a variable, passed as an argument, or returned as a result, but it has no name in any class, interface, or implementation.
Anonymous predicates have the ability to capture values from the context in which the expression occurs; this is a powerful ability that can be used to avoid a rather excessive amount of strange/unpleasant code.
Syntax
Anonymous predicates are terms:
Term : one of ... AnonymousPredicate ...
An anonymous predicate is a nameless clause in curly brackets. Certain parts are optional, giving these forms:
AnonymousPredicate : one of { ( Arg-comma-sep-list ) = Term } { ( Arg-comma-sep-list ) = Term :- Term } { ( Arg-comma-sep-list ) :- Term } { = Term } { = Term :- Term } { :- Term }
Leaving out the argument list means "the required number of arguments" and can be used whenever the arguments are not used inside the predicate expression.
Semantics
An anonymous predicate expression evaluates to a predicate value. Consider this code:
clauses run() :- Inc = { (X) = X+1 }, A = Inc(4), B = Inc(23), stdio::writef("A = %, B = %", A, B).
Inc becomes an increment predicate, so the program will write:
A = 5, B = 24
The code in this example corresponds to this code:
clauses run() :- Inc = inc, A = Inc(4), B = Inc(23), stdio::writef("A = %, B = %", A, B). class predicates inc : (integer X) -> integer R. clauses inc(X) = X+1.
Where the clause (X) = X+1 can be found in the last line; i.e., this time in a named predicate.
Variables that are bound outside (i.e., before the occurrence of) an anonymous predicate can be used inside the anonymous predicate. The value of the variable will be captured by the anonymous predicate.
Variables that are bound in an anonymous predicate are local variables in the anonymous predicate.
Capturing context
An anonymous predicate can capture context, which means that it can refer to things that are defined in its context, especially facts and variables from the clause.
Capturing variables
An anonymous predicate occurs in a clause, and this clause may contain variables. Those variables that are bound before the anonymous predicate is met can be used inside the anonymous predicate. This code illustrates how a variable is captured:
domains pred = (integer) -> integer. class predicates createAdder : (integer A) -> pred Adder. clauses createAdder(A) = { (X) = X+A }. clauses run() :- Add17 = createAdder(17), A = Add17(4), B = Add17(20), stdio::writef("A = %, B = %", A, B).
We call createAdder with 17 as argument. So in the createAdder clause A is 17, and therefore the result is { (X) = X+17 }. We say that the anonymous predicate has captured the variable A.
Since Add17 is a predicate that adds 17 to its argument, the output of the code will be:
A = 21, B = 37
Capturing ellipsis (...)
An anonymous predicate can capture the ellipsis variable (i.e., ...):
clauses ppp(...) :- W = { () :- stdio::write(...) }, qqq(W).
W captures the ellipsis variable. qqq receives a nullary predicate; when this predicate is invoked the captured ellipsis variable will be written to the standard output device.
Capturing facts
An anonymous predicate can access facts. If it is created by a class predicate it can access class facts. If it is created by an object predicate it can access both object and class facts. Consider this code that captures a class fact:
class facts count : integer := 0. clauses seq() = { () = count :- count := count+1 }. clauses run() :- A = seq(), B = seq(), stdio::writef("A1 = %, ", A()), stdio::writef("B1 = %, ", B()), stdio::writef("A2 = %, ", A()), stdio::writef("B2 = %", B()).
Both A and B increment the class fact count, so the result is:
A1 = 1, B1 = 2, A2 = 3, B2 = 4
In object predicates we can capture object facts. So assuming that seq is an object predicate in myClass, this code illustrates the capture of an object fact:
facts count : integer := 0. clauses seq() = { () = count :- count := count+1 }. clauses run() :- A = myClass::new():seq(), B = myClass::new():seq(), stdio::writef("A1 = %, ", A()), stdio::writef("B1 = %, ", B()), stdio::writef("A2 = %, ", A()), stdio::writef("B2 = %", B()).
In this case A and B come from two different objects, which each have a count fact, so the output will be:
A1 = 1, B1 = 1, A2 = 2, B2 = 2
Technically, the class version actually does not capture anything; it merely has access to the fact. Likewise, the object version does not actually capture the fact; instead it captures This and through This it obtains access to the object facts.
Capturing This
As described above it is possible to capture This and thereby gain access to object facts. The same mechanism gives access to calling object predicates.
clauses seq() = { () = count :- inc() }. clauses inc() :- count := count+1.
This can also be used directly:
clauses ppp() = { () = aaa::rrr(This) }.
Nesting
Anonymous predicates can be nested:
clauses run() :- P = { (A) = { (B) = A+B } }, Q = P(3300), R = P(2200), stdio::writef("Q(11) = %, ", Q(11)), stdio::writef("R(11) = %", R(11)).
To obtain Q we call P with 3300, so A is 3300 and Q therefore becomes { (B) = 3300+B }; likewise R becomes { (B) = 2200+B }. So the output is:
Q(11) = 3311, R(11) = 2211
Syntactic Sugar
If you do not need the arguments they can be skipped. So this code fragment:
P = { (_) :- succeed }, Q = { (_, _) = 0 }, R = { (_, _, _) = _ :- fail }.
can be shortened to:
P = { :- succeed }, Q = { = 0 }, R = { = _ :- fail }.
Notice that the arguments are completely skipped. If you write () it means zero arguments, whereas skipping the arguments means "a suitable amount" of arguments.
Examples of practical usage
The examples assume that the PFC scopes core, std, stdio, list and string are open.
Dummy predicates
Anonymous predicates are good for creating dummy predicate values:
ppp( { = true } ), % do not filter (boolean) qqq( { :- succeed } ), % do not filter (determ) rrr( { = 17 } ). % all rows must have height 17
Adaptation
In cases where you need a predicate and have one that is almost suitable, you can make the adaptation using an anonymous predicate.
Index adaptation
Consider the predicate write3:
class predicates write3 : (function{integer, string} Indexer). clauses write3(Indexer) :- foreach I = std::fromTo(0,2) do write(Indexer(I), "\n") end foreach.
Indexer implements an "array" of strings; write3 will write the three strings found at the indexes 0, 1 and 2. So write3 assumes that the "array" index is zero-based.
However, the "array" we have uses a one-based index:
class predicates myArray : (integer N) -> string Value. clauses myArray(1) = "First" :- !. myArray(2) = "Second" :- !. myArray(3) = "Third" :- !. myArray(_) = _ :- raiseError().
Using an anonymous predicate we can easily adapt the one-based array to the zero-based usage:
% myArray is 1-based, write3 requires 0-based Arr = { (N) = myArray(N+1) }, write3(Arr).
So we get the expected output:
First Second Third
Parameter adaptation
In this code listChildren will call a ChildWriter predicate for each "C is the child of P"-pair:
class predicates listChildren : (predicate{string,string} ChildWriter). clauses listChildren(CW) :- CW("Son1", "Father"), CW("Son2", "Father").
We will however prefer to list the "P is the parent of C" using the predicate wParent:
class predicates wParent : (string Parent, string Child). clauses wParent(P, C) :- writef("% is the parent of %\n", P, C).
wParent takes the arguments in the opposite order, but we can easily adapt using an anonymous predicate:
Swap = { (A,B) :- wParent(B,A) }, listChildren(Swap).
And then the output becomes the expected:
Father is the parent of Son1 Father is the parent of Son2
We can also throw away arguments, for example when calling this predicate that only needs a Child:
class predicates wKnowParent : (string Child). clauses wKnowParent(C) :- writef("We know a parent of %\n", C).
The adaptation looks like this:
Fewer = { (C,P) :- wKnowParent(C) }, listChildren(Fewer).
The output will be:
We know a parent of Son1 We know a parent of Son2
We can also supply dummy arguments:
More = { (_,P) :- addChildren(P, 1) }, listChildren(More).
Here addChildren will "add a count of children to P". Since each invocation corresponds to one child we will call addChildren supplying 1 as a "dummy" argument. The More is thus an adaptor that both throws away an argument and supplies a dummy argument.
Filters
Assume this predicate:
class predicates writeFiltered : (string L, predicate_dt{integer} Filter). clauses writeFiltered(Label, Filter) :- List = [1,2,3,4,5,6,7,8,9], FilteredList = filter(List, Filter), writef("%\t%\n", Label, FilteredList).
Filter is used to filter the list [1,2,3,4,5,6,7,8,9]; the filtered list and the Label are written to the standard output.
First we use the allow-all filter:
All = { :- succeed }, writeFiltered("All", All).
This filter simply succeeds for any element, so the output is the entire list:
All [1,2,3,4,5,6,7,8,9]
It is just as easy to create a filter that fails for all elements and thus allow-none:
None = { :- fail }, writeFiltered("None", None).
The output from this is the empty list:
None []
We can also create filters for elements greater than 3 and elements divisible by 3:
GreaterThan3 = { (X) :- X > 3 }, writeFiltered("> 3", GreaterThan3), Rem3 = { (X) :- 0 = X rem 3 }, writeFiltered("Rem3", Rem3).
The output from this is:
> 3 [4,5,6,7,8,9] Rem3 [3,6,9]
Sorting
The list package has a sort predicate. But sometimes the default order is not what you need. Therefore the list package also has a predicate sortBy, which sorts the elements using a programmer-defined compare operation. Let us first consider string sorting, using this predicate:
class predicates writeStringsSorted : (string Label, comparator{string} Comp). clauses writeStringsSorted(Label, C) :- List = ["John Wayne", "Uma Thurman", "Harrison Ford", "Nicolas Cage", "Elizabeth Taylor", "Cary Grant", "Jerry Lewis", "Robert De Niro"], Sorted = sortBy(C, List), write(Label, "\n"), foreach S = list::getMember_nd(Sorted) do writef(" %\n", S) end foreach.
We can call the predicate with the "normal" comparator, and using an anonymous predicate we can easily sort it descending as well:
Normal = compare, writeStringsSorted("Normal", Normal), Descending = { (A,B) = compare(B,A) }, writeStringsSorted("Descending", Descending).
The output looks like this:
Normal Cary Grant Elizabeth Taylor Harrison Ford Jerry Lewis John Wayne Nicolas Cage Robert De Niro Uma Thurman Descending Uma Thurman Robert De Niro Nicolas Cage John Wayne Jerry Lewis Harrison Ford Elizabeth Taylor Cary Grant
Let us also sort some more complex elements. Here a person has a first name and a last name, using this domain:
domains person = p(string First, string Last).
For the demonstration we will use this test predicate:
class predicates writePersonsSorted : (string Label, comparator{person} Comparator). clauses writePersonsSorted(Label, C) :- List = [p("John","Wayne"), p("Uma","Thurman"), p("Harrison","Ford"), p("Nicolas","Cage"), p("Elizabeth","Taylor"), p("Cary","Grant"), p("Jerry","Lewis"), p("Robert","De Niro")], Sorted = sortBy(C, List), write(Label, "\n"), foreach p(F,L) = list::getMember_nd(Sorted) do writef(" % %\n", F, L) end foreach.
Again we can sort using the normal and a descending comparator:
Normal = compare, writePersonsSorted("Normal", Normal), Descending = { (A,B) = compare(B,A) }, writePersonsSorted("Descending", Descending).
Since the compare predicate uses left-to-right lexicographic order on the p-functor, the result is the same as before:
Normal Cary Grant Elizabeth Taylor Harrison Ford Jerry Lewis John Wayne Nicolas Cage Robert De Niro Uma Thurman Descending Uma Thurman Robert De Niro Nicolas Cage John Wayne Jerry Lewis Harrison Ford Elizabeth Taylor Cary Grant
But with the more complex domain we can create a comparator that will sort on last name:
LN = { (p(_,L1), p(_, L2)) = compare(L1,L2) }, writePersonsSorted("LastName", LN).
The result is what we expect:
LastName Nicolas Cage Robert De Niro Harrison Ford Cary Grant Jerry Lewis Elizabeth Taylor Uma Thurman John Wayne
Capturing context (threads & callbacks)
As mentioned a very powerful feature of anonymous predicates is the ability to capture context. The examples in this section show some ways you can use this.
Background threads
The routine for starting a thread takes a nullary predicate and runs it in the new thread. But you nearly always need to pass some input data to the job in the new thread. This is possible in several ways, but the absolutely simplest way is to use anonymous predicates. The project bgDemo from the Visual Prolog example collection (that can be installed from the IDE) uses this method. The project has a form that can start a background job and display status information from the job in a jobControl that is added to the form. A background job is a predicate that will receive a jobLog, which it can use to report status and completion degree:
domains job = (jobLog Log).
A jobLog looks like this:
interface jobLog properties completion : real (i). properties status : string (i). end interface jobLog
The job can report completion degree by setting the completion property (range 0 to 1). Likewise, the status property can be used to reflect the current status of the job.
The status and completion will be shown in the form together with a job name. A job is started by calling the form's addJob predicate:
clauses addJob(JobName, Job) :- JobCtrl = jobControl::new(This), JobCtrl:name := JobName, JobCtrl:show(), assert(jobCtrl_fact(JobCtrl)), arrange(), JobLog = jobLog::new(JobCtrl), Action = { :- Job(JobLog) }, _ = thread::start(Action).
In this context it is the last three lines that are interesting. thread::start takes a nullary predicate as argument, but a job is a predicate that takes a jobLog as argument. Therefore we create an anonymous predicate Action, which takes no arguments but invokes Job on the JobLog. The anonymous predicate has captured both Job and JobLog from the context, and subsequently both these values are transferred to the new thread even though this thread only receives a nullary predicate.
The jobs in the bgDemo project are merely dummy jobs that only manipulate their jobLog. One of them looks like this:
clauses job(Log, From, To) :- Log:status := "Step 1", foreach N1 = std::fromTo(From, To) do Log:completion := (N1-From) / (To-From) / 2, programControl::sleep(3) end foreach, Log:status := "Step 2", foreach N2 = std::fromTo(From, To) do Log:completion := (N2-From) / (To-From) / 2 + 0.5, programControl::sleep(3) end foreach, Log:status := "finished".
It has two loops which run from From to To and calculates the completion and sets it on the Log. It also sets the status text before, between and after the loops.
You may notice that the job does not have the proper job type, because a proper job only has one argument (the jobLog); this job has three arguments. Again it is anonymous predicates that help us. The code that adds the jobs to the form looks like this:
predicates onFileNew : window::menuItemListener. clauses onFileNew(_Source, _MenuTag) :- JF = jobForm::display(This), Job11 = {(L) :- job1::job(L, 1, 1000)}, Job12 = {(L) :- job1::job(L, 200, 600)}, Job13 = {(L) :- job1::job(L, 1200, 3000)}, Job14 = {(L) :- job1::job(L, 1, 1000)}, JF:addJob("job1.1", Job11), JF:addJob("job1.2", Job12), JF:addJob("job1.3", Job13), JF:addJob("job1.4", Job14), ...
In a more realistic program, it is most likely that From and To would not be constants, but rather parameters passed from some outer place. In that case these anonymous predicates would also capture variables from the context.
The jobLog in the bgDemo illustrates one more usage of anonymous predicates. The jobLog passes the completion and the status information to a jobControl. The jobControl is a GUI control on the jobForm capable of doing a suitable rendering of the information. This however gives a synchronization problem, because GUI controls are not thread safe and here we want to update some controls from a background thread. This can lead to conflicts, because it is the main thread that draws the controls.
The solution is to transfer the update of the control to the GUI thread. We do this by posting actions to the control. The implementation of the status update looks like this:
clauses status(Status) :- Action = { :- jobCtrl:status := Status }, jobCtrl:postAction(Action).
Action is a nullary predicate that will set the status in the jobCtrl. We post this action to the jobCtrl. When the jobCtrl receives the action it invokes it and is thus updated. This way, the actual update of the control will be performed by the GUI thread. This anonymous predicate not only captures the Status variable it also captures the jobCtrl fact.
Asynchronous callbacks
Assume that we send commands to a remote service. The command execution is asynchronous, so when we execute a command we also give a callback action which will be invoked when the execution of the command is finished. To execute a command we must call this predicate:
predicates executeCommand : (command Cmd, predicate{} OnDone).
Based on this predicate we want to create a similar predicate that can execute a list of commands. A certain command should be executed when the previous command completes. We will also make our list executor asynchronous, so we supply an action that will be invoked when the entire script of commands is finished. Our script executor will have the form:
predicates executeScript : (command* Script, predicate{} OnDone).
If the script is empty we simply invoke the OnDone action. If the script has a command H and a rest script T, we must first execute H, and when it is finished we must execute the rest of the script T. So the OnDone action we supply when executing H must execute T. All in all, the implementation can look like this:
clauses executeScript([], OnDone) :- OnDone(). executeScript([H|T], OnDone) :- DoneH = { :- executeScript(T, OnDone) }, executeCommand(H, DoneH).
We have used an anonymous predicate to perform the execution of the rest of the script. This anonymous predicate captures T and OnDone.