Language Reference/Terms/Anonymous Predicates

From wiki.visual-prolog.com

< Language Reference‎ | Terms

Revision as of 11:14, 8 November 2016 by Tbn (talk | contribs) (Background threads: minor typos)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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 = 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().

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, 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 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 = 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

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 null-ary 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) use 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 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, 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 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.