Presenters

From wiki.visual-prolog.com

Revision as of 15:43, 1 July 2019 by Thomas Linder Puls (talk | contribs) (expand native)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Visual Prolog can write any value, but often the printed version of a value is not very useful. An object is for example written as the address of the object. A presenter is a predicate that is attached to a domain and can be used to write (i.e. "present") the values of that domain in a more meaningful way.

Example A map object from the class mapM_redBlack is written as the address of the object. So this code:
clauses
    run() :-
        M = mapM_redBlack::new(),
        M:set(1, "a"),
        stdio::writef("M = %\n", M).

will simply write the address of the map object:

M = 01757F78

The using "%p" in the format string instead of just "%" will write the map using the presenter that is attached to the map classes:

clauses
    run() :-
        M = mapM_redBlack::new(),
        M:set(1, "a"),
        stdio::writef("M = %p\n", M).

and then the output will look like this:

M = [1 -> "a"]

Presenters are however also used in the debugging process to provide more expressive/simpler presentations in the Variables Window (and in similar contexts).

Example The map above will appear like this in the Variables Window: Presenter-variables.png

As you can see in the example above the presenter not only affect the writing of the map object it also alters the way it is expanded in the window. The map above is actually an object that contains a fact with a red-black-tree which contains the actual mapping of 1 to "a", but all these details are hidden in favor of a much more semantically meaningful presentation.

If you need access to the low-level expansion of something in the debugger you can right click the value and switch on "Native View". This will affect all values of that type; which will get an extra child node which contains the "native" (i.e. unpresented) view of the value.

The notion of presenters is relatively complex due to the double role it serves both as plain text presentation and as recipe for expansion in the debugger. Subsequently, it can be difficult to write a good presenter for a complex domain.

Presenting

Any text-mode outputStream can present a value of a domains that has presenters attached. This can be done either by calling the predicate present or by using "%p" in a format string. The present predicate works like the write predicate except that all values are written using the attached presenters rather than in the default/built-in way. Likewise, a value corresponding to "%p" in a format string will be written using the attached presenters.

string::present(V) will return a string with the presentation of V, and string::format will (and any other predicate that uses format strings) can also use "%p" for presentation.

As mentioned below presenters works on (deeply) nested terms regardless of whether the surrounding term has attached presenters or not.

Example This code uses presenters for the writing T:
clauses
    run() :-
        M = mapP_redBlack::new():insertList([tuple(7, "q"), tuple(1, "a")]),
        S = setP_redBlack::new():insertList(["A", "fox", "jumped", "over" , "the", "fence"]),
        T = tuple(M, S),
        stdio::writef("T = %p\n", T).

The tuple domain does not have a presenter attached, so it is written like an ordinary functor term. Maps and sets on the other hand do have presenters attached so they will be written using them. The output looks like this:

T = tuple([1 -> "a", 7 -> "q"], ["A", "fence", "fox", "jumped", "over", "the"])

The presentation of a set looks like a list, and the presentation of a string is quoted.

Attaching presenters

If a presenter predicate has not been attached to a domain, then the domain will use a default presenter predicate instead. The default presenter predicate will present the value in the standard way (i.e. like write) however subterms will be presented using their presenter. So presenters works on deeply nested in values (regardless of whether the outer terms have presenters or not).

The presenter for a domain is a predicate which is attached to the domain using a presenter/1 attribute in the domain definition:

Example The predicate present_tree is attached as presenter to the tree domain like this:
domains
    tree =
        empty;
        node(tree L, string Value, tree R) [presenter(present_tree)].

The presenter predicate is (i.e. must be) a function from a Value in the domain (i.e. Type) to a Presentation, as specified by the PFC domain presenter::presenter:

domains
    presenter{Type} = (Type Value) -> presentation Presentation.
Example So the present_tree predicate must be declared like this:
class predicates
    present_tree : presenter::presenter{tree}.

Objects can also be presented. A presenter is attached by using the presenter/0 attribute in the domain definition:

Example To attach a presenter to myInterface you supply the presenter/0 attribute:
interface myInterface 
    [presenter]
...
end interface myInterface

For objects the presenter predicate is always named presenter corresponding to this declaration (in the interface):

predicates  
    presenter : () -> presenter::presentation Presentation.

Notice however that the predicate should not be declared; stating the attribute implicitly implies the predicate declaration.

Cautions

Since presenters are used in the debugging process it is very important that they are kept simple, stateless and efficient. Especially:

  • They should be efficient; long running calculations will affect debug performance.
  • They shoud not have or depend on side-effects; during debugging the presenters may be calculated any number of times in any order.
  • They may not acquire exclusive access to resources (critical sections, mutexes, etc): that may end up blocking the entrire debugging process.
  • They should not start or depend on other threads

Presenter Domain

The presentation domain (also defined in the presenter class) looks like this:

domains
    presentation =
        noExpand(string Presentation, any Term);
        expand(string Presentation, any Term);
        structured(string Delimiter, subPresenter SubPresenter);
        enclose(string Opening, string Closing, presentation Presentation);
        functor(functorDescr FunctorDescr, any* SubTerms).

noExpand

domains
    presentation =
        noExpand(string Presentation, any Term);
        ...


The noExpand alternative is used to provide a string Presentation for an "atomic" value. The value will be presented as Presentation and the debugger will not show any children of the value. The Term is used by the debugger to obtain a type of the term. The noExpand alternative could for example be used for a number that represent a time; the Presentation would be a readable format of the time and the Term would be the "raw" time value. The presentation will be the readable format, and the debugger will not provide any expansion of the term (but Native View can be used to see the actual value).

expand

domains
    presentation =
        expand(string Presentation, any Term);
        ...

The expand alternative it used to provide a string Presentation for an "non-atomic" value. The value will be presented as Presentation and will expand it as it would natively expand the Term. The debugger also in some cases obtain the type from Term. The expand alternative could for example be used for a functor term containing date and time fields like year, month, date, hour and minute; the Presentation would be a readable format of the date/time and the Term would be the functor term. The presentation will be the readable format, and the debugger will provide an expansion that contains the individual fields (year, etc).

enclose

domains
    presentation =
        ...
        enclose(string Opening, string Closing, presentation Presentation);
        ...


The enclose is used to surround a presentation by Opening and Closing strings. This is for example used to surround the presentation of a set with "[" and "]" to result in a list-like presentation. The debugger simply consider the nested Presentation.

functor

domains
    presentation =
        ...
        functor(functorDescr FunctorDescr, any* SubTerms).

The functor alternative is specially designed to be used internally by the compiler runtime system, because the FunctorDescr's are terms that are layed-out as contants by the compiler. The functor alternative will create a presentation that corresponds to a functor term which has SubTerms as sub-terms. The alternative is partly redundant, because the same thing could have been expressed using structured and enclose alternatives.

structured

domains
    presentation =
        ...
        structured(string Delimiter, subPresenter SubPresenter);
        ...

The structured alternative is used for structured terms. The SubPresenter will produce a number of sub-presentations. When writing the presentation each of the sub-presentations will be written delimited by Delimiter. Typically the delimiter is ", " so that the resulting presentations are written comma separated. This is for example used when presenting sets.

To avoid calculating very large/deep presentations during debugging, the structured presenters uses lazy evaluation of the sub-presenters. This is achieved by letting the presentation contain functions that can calculate the presentation rarther than the presentation itself. So during debugging the presentation of a term will only expand to a certain level. Inside that level there will be functions the debugger can choose to call if further expansion is required. But if further expansion is not required the calculation will be skipped. For this reason the debugger can deal efficiently with very large terms. On the downside this complicates the coding of presenters somewhat.

There are two kinds of subPresenter's:

domains
    subPresenter =
        fixed(fieldPresentation* FieldPresentations);
        sequence(sequencePresenter Sequence).

fixed (structured)

domains
    subPresenter =
        fixed(fieldPresentation* FieldPresentations);
        ...

used for presenting values that have a fixed number of "Field" sub-presentations. This could for example be the presentation of a certain functor alternative, since such a functor term has a certain (fixed) number of sub-terms. Which result in a fixed number of sub-presentations.

When the debugger expands a fixed presentation all the sub-presentations will be shown, because they are considered all part of the expanded term.

A fixed sub-presenter consist of a list of fieldPresentation's each describing one of the fields.

There are three kinds of fieldPresentation's:

domains
    fieldPresentation =
        fp(string Name, subLazy Sub);
        fp_string(string Presentation);
        fp_childOnly(string Name, subLazy Sub).

The fp alternative has a field Name and a lazy sub-presentation Sub of the field itself. The field Name is used by the debugger for labeling the presentation, but it is not used when writing the presentation. Sub will be used by the debugger for the corresponding value and it will also be used in when writing the presentation.

The fp_string will only appear in the writing of the presentation, the debugger will not include an fp_string when expanding the presentation.

The fp_childOnly does not take part in the writing of the presentation, it will only be used as a child by the debugger.

So to summarize, the fp_string alternative only show-up in the writing, the fp_childOnly only show-up as a child when the debugger expands the presentation and finally the fp will both show up in the writing and as a child.

The subLazy domain is a little complex:

domains
    subLazy = subPresentation{fieldPresenter}.
 
domains
    fieldPresenter = function{presentation}.
 
domains
    subPresentation{Presentation} =
        pres(Presentation Presentation);
        term(any Term).

To simplify the explanation it is worth expanding the fieldPresenter and subPresentation definitions into the subLazy domain:

domains
    subLazy = 
        pres(function{presentation} Presentation);
        term(any Term).

So effectively a subLazy term is either (pres) a function returning a presentation or a (term) term. The function need only be called if the presentation is required and likewise the term need only be presented if its presentation is required.

Example Consider the following functor domain and a constant of that domain:
domains
    pair = p(string Name, integer Value).
 
constants
    t : pair = p("a", 1).

This term would be a sensible presentation of t (we assume that the presenter scope is open):

constants
    t_presentation : presentation =
        enclose("p(", ")",
            structured(", ",
                fixed(
                    [
                        fp("Name", term(toAny("a"))),
                        fp("Value", term(toAny(1)))
                    ]
                )
            )
        ).

Basically there are two named fields which are terms (a "Name" field which has the value "a" and a "Value" field which have the value 1), when written these fields are comma separated ", " and finally they are enclosed in "p(" and ")". Basically there are two named fields which are terms (a "Name" field which has the value "a" and a "Value" field which have the value 1), when written these fields are comma separated ", " and finally they are enclosed in "p(" and ")".

The writing will look like a normal functor term p("a", 1) and the debugger is able expand it to the two named fields.

It is worth noticing that this presentation will give the same result (both when writing and in the debugger):

constants
    t_presentation2 : presentation = functor(functorDescr("p", ["Name", "Value"]), [toAny("a"), toAny(1)]).

sequence (structured)

domains
    subPresenter =
        ...;
        sequence(sequencePresenter Sequence).

A sequence sub-presenter is used for presenting values have a varying number of sub-presentations, for example a set can have from any number of sub-presentations (including none at all). When the debugger expands a sequence presentation it will at first only show a limited number of the sub-presentations. That way the debugger will not suddenly be expanding a set that contains millions of elements. Expanding such a large set is rarely interesting, since it will both take a very long time to do and since it will also give result that is too large to comprehend anyway.

The sequencePresenter is defined like this:

domains
    sequencePresenter = function_nd{sub Presentation}.
 
domains
    sub = subPresentation{presentation}.
 
domains
    subPresentation{Presentation} =
        pres(Presentation Presentation);
        term(any Term).

Here it is worth expanding subPresentation into the sub domain:

domains
    sequencePresenter = function_nd{sub Presentation}.
 
domains
    sub =
        pres(presentation Presentation);
        term(any Term).

So a sequencePresenter is a nondeterministic predicate that returns presentations (pres) or terms (term). The nondeterministic predicate can be used to achieve the step-wise expansion of the children (though it is necessary to start from scratch to obtain more children).

Example Consider a list term:
constants
    l : integer* = [1, 2, 3,  4, 5, 6, 7].

This term would be a sensible presentation of that l:

class facts
    l_presentation : presentation :=
        enclose("[", "]",
            structured(", ",
                sequence(
                    { () = term(toAny(V)) :-
                        V in [1, 2, 3, 4, 5, 6, 7]
                    }))).

Basically the presentation is a sequence presentation with a nondeterministic predicate that returns the terms in the list, these sub presentations are comma separated ", " and finally enclosed in "[" and "]".

Support predicates

The predicate outputStreamSupport::writePresentation can be used to test (or use) presenters which are not attached to a domain.

Example This predicate will write a presentation to stdio::outputStream:
class predicates
    test : (presenter::presentation P).
clauses
    test(P) :-
        outputStreamSupport::writePresentation(stdio::outputStream, P),
        stdio::nl.

These predicates (in the presenter class):

predicates
    present : presenter{any Any}.
    present_native : presenter{any Any}.
 
predicates
    present_term : presenter{Type}.
    present_term_native : presenter{Type}.

Will return the presentation of terms.

The presenter class contains a number of support predicates that can be used to make presenters.

The predicate presenter::mkPresenter_set will create a "set" presentation (i.e. comma separated and enclosed in "[" and "]") of the elements returned by a nondeterministic predicate.

Example This would be a sensible presenter for the list domain:
class predicates
    presenter_list : presenter{list{A}}.
clauses
    presenter_list(List) = mkPresenter_set({  = V :- V in List }).

The list domain does however already have such a presenter, and besides that it would be impossible to state an attribute for the list domain, because it is predefined.

Should you need/want to enclose a comma separated sequence in something else than "[" and "]", then you can use the predicate presenter::mkPresenter_setElements instead.

Example This would enclose a list in "{" and "}":
class predicates
    presenter_list_brace : presenter{list{A}}.
clauses
    presenter_list_brace(List) = enclose("{", "}", mkPresenter_setElements({  = V :- V in List })).

The list domain does however already have such a presenter, and besides that it would be impossible to state an attribute for the list domain, because it is predefined.

Similar predicates exist for map-like constructions:

predicates
    mkPresenter_map : (function_nd{tuple{Key, Value}} GetAll_nd) -> presentation Map.
    mkPresenter_mapElements : (string MapSymbol, function_nd{tuple{Key, Value}} GetAll_nd) -> presentation Map.

The MapSymbol is inserted between the key and value; in mkPresenter_map it is " -> ".