Difference between revisions of "Language Reference/Terms/Anonymous Predicates"
< Language Reference | Terms
m (1 revision(s)) |
|
(No difference)
|
Revision as of 15:22, 15 September 2008
An anonymous predicate is an expression that evaluates to a predicate value. The predicate value can be bound to a variable, passed as arguments or returned as result, but the value does not have a 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 rather powerful ability that can be used to avoid 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.
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 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
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 zero-arity 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 comes 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 doesn't capture anything, it merely have access to the fact. Likewise, the object version doesn't 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 gaining access to objects 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 don't need the arguments they can be skipped. So this code-fragment:
P = { (_) :- succeed }, Q = { (_, _) = 0 }, R = { (_, _, _) = _ :- fail } Can be shortened down to this: 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
This section shows some cases where anonymous predicates are very handy. 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 } ), % don't filter (boolean) qqq( { :- succeed } ), % don't 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 = 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().
But using an anonymous predicate we can easily adapt the one-based array to the zero-based usage:
% myArray is 0-based, write3 requires 1-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 out 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 addChild 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, filterPredicate{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 dividable 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 = 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) = 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
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 treads
The routine for starting a thread takes a null-ary predicate and run 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) use this method. The project has a form that can start 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 null-ary 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 null-ary 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 pass 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 make transfer the 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 null-ary 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 that 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 asynchron-ous, 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 are finished. Our script executer 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.