Late Transcend
Reasoning at runtime

Transcend is a powerful tool and as a consequence it's computationally inefficient. Well, expressiveness has its cost. That's the reason why it's mostly suited only for a compile time where it shines unequivocally. Obviously, at this stage compiler lacks dynamic part - the data that is known for sure only during actual program execution. In other words, Early Transcend reasons over weak data - set of facts reflecting possible states and conditions the program could go through during execution. Before that, there is no way to be sure what exactly is going to happen, only set of possibilities is available. Nevertheless, in many cases it's not enough and that's why Late Transcend is introduced - reasoning and making decisions based on data available at runtime.

Late Transcend approach can be described as having three phases:

  • Input: at compile time Transcend working with weak data produces decisions for all possible alternatives. Generated set is used as an input for Late Transcend. As it's unknown which exactly decision turns out to be true, every decision has a guards - set of pairs (variable, value) that describe exact state for which it's possible to reach given decision.
  • Validation: A decision is considered to be valid only if all guards are met, i.e. variables from guard pairs actually have required values. In a sense Late Transcend decisions are deferred until correspondent preconditions proved to be true.
  • Execution: DIfferent guarded decisions may produce different code branches. All correspondent code branches are compiled and present in the code in order to be used when needed. At branching point guards are tested and appropriate branch is selected to be executed next.

Late Annotations and ASP Representation

Late annotations is a particular type of annotations that depend on parameters known only at runtime. Example:

tests/latetranscend.cpp: LateReasoning.Doc_SwitchLateOperation
mul(a, b)::float; arithmetic(ArithmX)

Suppose we have different specializations of mul function each implementing different multiplication precision and it controlled by using arithmetic(fast), arithmetic(accurate) and similar annotations. In this example arithmetic(ArithmX) is a late annotation, if ArithmX is a parameter known only during program execution.

Currently, late annotations do not differ syntactically from other annotations. Only if annotation is recognized to have late arguments it's marked internally as a late and special rules of conversion to ASP form apply.

Late annotation in ASP form is presented as follows:

SYNTAX: late(target, (guard-identifiers-list), (guard-values-list), body):- parameter-types-list.
  • target References entity for which late annotation is applicable
  • guard-identifiers-list List of guard identifiers
  • guard-values-list List of guard values
  • body Decision deemed valid if guards are met
  • parameter-types-list List of types of late parameters

Meaning that a fact body wrapped by modifier late() alongside with two lists: guards' identifiers and guards' values.

For an example above, arithmetic(ArithmX) translated into form below where exact references depend on a program:

late(s(0,-2,2), (s(1,-2,2)), (ArithmX), arithmetic(ArithmX)):- arithmetic(ArithmX).

This rule generates different facts for each possible x alternative. At runtime only those facts are selected whose guards are met, i.e. referenced variables actually have specified values.

Switch Late Operation

SYNTAX: switch late ( condition [-> alias ] [:: condition-type [; annotations-list] ] ) :: type [; annotations-list] switch-block
  • condition switch's condition
  • alias optional alias to denote condition's result within internal code block annotations. If there is no alias and condition is just one identifier it's accessible by its own name
  • condition-type Condition must have slave type

Switch Late operation allows to designate alias as a late parameter to use in late annotation. It can be conceptualised as lifting Brute value to Transcend level. During compilation it generates different switch-block branches for each possible condition value and only branch is executed whose guard correspond condition. Example:

tests/latetranscend.cpp: LateReasoning.Doc_SwitchLateOperation
ArithmeticT = type slave arithmetic.

test = function(a::float, b::float, ArithmX::ArithmeticT):: float; entry
{
    switch late (ArithmX)::   float
    {
        mul(a, b)::float; arithmetic(ArithmX)
    }
}

Compiler generates several branches for all possible arithmetic variants. Only one branch executed depending on arithmX.