Presenters
Visual Prolog 11 preliminary documentation. This article contains preliminary documentation for the upcoming release |
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.
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).
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.
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:
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.
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:
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 expand the Term. The debuger also obtains 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.
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).
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.
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.
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.
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 " -> ".