Symbolic Logic:Programming:Meta Programming

The goal of meta programming is to apply general solution to give an efficient solution to a specific problem. A general solution is a discription of a problem that applies to many specific situations. Specific solutions are generated from general solutions by specififying values and types for parameters.

The process of generating a specific solution to a problem from a general solution is the same as for any logic programming evaluation. Specified values and types are used to calculate unknown values and types from know values and types. Evaluation procedes in the order determined by the known values.

At some point the evaluation must stop if there is insufficent data to evaluate all requested variables. At this point the partially evaluated program is converted back into a meta program, ready to be evaluated when all the necessary data for calculation is provided.

The specific solution may then be reviewed by the programmers, to help them understand how the general logic is applied to the specific problem. This helps the programmer understand and check the program.

A general description of a program will be more abstract. It may be simpler than a specific solution but it may be hard to understand how it applies in a particular situation. A general solution will execute slower than a specific solution because the general solution must interpret more data in applying the specific solution.

Meta programming allows the execution to be broken down into phases, called Meta Phases. Each phase consists of the partial execution of the program based on what specific information is available. The result of the partial execution is a more specific version of the program, which may then be executed.

This gives the speed and simplicity of a specific solution, while keeping the generality of the source logic.

Ideally Meta Programming should have no effect on the way we describe the functionality. Meta Programming should effect only how the code is executed. In particular we should be able to control the application of general logic to a specific situation.

Execution of code with partial information is possible in a Logic Programming language because Logic Programming supports unknown values for variables. Partial evaluation fills in some values while leaving other values unknown. The known values are then used to simplify the code.

History
Historically there have been strictly typed languages (C++, Java, ...) where you must declare a type for each variable and scripting languages (Ruby, Python, Perl, ...) where the type is determined at run time. The scripting languages had slower execution.

The primary problem with scripting languages was that the type checking happens at run time. Their advantages are that generic code may be written, and the developer does not need to specify the types.

Another approach is to use Type Inference. Using this approach the compiler solves type equations to infer types.

The solving of type equations is a natural activity for a Logic Programming language. So we might consider solving these type equations as another "phase" of program execution.

Meta Programming
C++ template classes may be used for template meta programming. This is an example of a separate "phase" of execution. Template meta programming techniques may sometimes be used to provide extra compile time checks.

Code Generation
Programs may be written that write the text of other programs. This approaches allows the intermediate stage of program to be read by the programmer. But this approach adds a overhead in the description of the problem. The Meta Programmer must understand the task of reading in some source description of the problem and using it to write text which must then be compiled before it may be executed.

This approach does achieve the goals of Meta Programming but with a considerable cost. Meta Programming directly in the language is simpler.

Modern Scripting Languages
Modern scripting languages like Ruby have strong support for Meta Programming built into the language. In this case the Meta Programming executes as part of the run time execution of the program. There is no division of the execution into Meta Phases.

Meta Programming in Ruby allows the creating methods and code at run time.

Meta Phases
A Meta Phase is phase of execution of a program, which contributes to the creation of the program for a specific purpose and to the implementation of functionality. The standard Meta Phases are,


 * Inheritance resolution
 * Type and Characteristic resolution
 * Partial Evaluation
 * Run regression tests
 * Database Upgrade
 * Deployment
 * Execution

Inheritance resolution
Determine the inheritance structure of the program. In Dynamic Class Inheritance Statements statements are described that define the class structure as executable statements.

The body of every class or function is executed once, with no input values, and no requested values. However for any inheritance statement found the truth of the statement is requested. Depending on the result,


 * true - the dynamic inheritance statement is converted into a fixed inheritance statement.
 * false - the dynamic inheritance statement is ignored.
 * unknown - Data and functions for inheritance are set up but truth of the statement is a precondition on the inherited stataments.

The last option is where some class instance will inherit and others wont. This is a logical possibility which can readily be supported. However I am not sure if there will be an actual requirment for it.

Type and Characteristic resolution
Type Inference may be used to deduce the types and characteristics of variables. To perform type inference as a meta phase the instantiated type must be inherited by (be a subset of) the variable type.

The type of the variable constrains the instantiated type of the object. The instantiated is the type of the object that is created. For a variable of a particular type, the instantiated type must inherit from (be a subset of) variable.

The set of instantiated types that a variable may have may be implemented as a Value Set. The value set system will then work out the Value Set of instantiated types that a variable may hold. From that class with the smallest number of inherited classes that includes all the classes in the value set is the type of the variable.

Partial Evaluation
Partial evaluation consists of running a program, without full knowledge of the input parameters and data. This divides the execution of the program up into stages which called Meta Phases.

Partial evaluation is designed to do as much calculation as possible prior to running the program for the user. It simulates the execution of the program,
 * To decide the types of variables.
 * To evaluate constant variables.
 * To remove unused code.
 * To work out how many different ways functions are called.
 * Determine the most likely order of execution.

Execution of Partial Evaluation
Each function that is external in the project is called. External functions are functions that may called from other projects or the run time environment. The variables actual values are not known, so they are replaced by placeholder objects that record information about the object,
 * Type
 * Duplicity characteristic (unique, multiple).
 * Value
 * Member variables accessed (see Object Simplification).

During the partial evaluation type information will be set by like,
 * x.IsA(Animal)

narrow down the type of the variable.

The built in functions will simulate there behaviour as if they had data. If they have enough values "given" they will execute and set other values to given. They will set the type and characteristics of the calculated value. If the call is not ready to be executed then it will be placed on the call queue.

Tabling is used. Tabling means that a table is kept of each function call recording its input values and characteristics. In the first metaphase all unknown but given values are regarded as equal. Before each call is executed a check is made to see if an existing function call with the same values and type characteristics. A new call is only executed if the call is not found in the table.

Call Contexts
During partial evaluation each call has an associated call context. Each call also has a parent call context, which is the call from which this call was made.

Each call context records the parameters used for the call. Type and value information will have been resolved by the partial evaluation.

When a call is executed that has unknown values the call is appended to the list of calls from its parent call context. Any call that can be completely executed in the Partial Evaluation need not be added as its results are already calculated and recorded in the parameters recorded against the call context.

The results of partial evaluation are recorded in the Call Contexts. Each function may have a number of call contexts. Each call context describes a specific version of the function taylored for a particular purpose.

The developer may browse these call contexts in order to better understand the execution of the program.

Code generation is based on the call contexts. Code generation may include debug information. The debug information is the Debug call, passing the parent call context and the index of the call in the call list, along with the parameter values.

The Debug Call
The developer may record breakpoint conditions against the call context. The Debug call checks the breakpoint condition and displays the call context on break.

Break point conditions may access the attributes of parameters using the run time attribute discovery. Each parameter to the call context appears as a variant, which is either a base type or an object. The method,
 * Variant GetAttribute(String name)

function may be implemented by the object to allow access to the attributes of the object. This function is implemented by renaming inheritance.

Object Contexts
During partial evaluation a list of the member variables actually accessed for each object is made. This list may be used as a basis for simplifying the object created for a particular variable in a call context.

The object created for a particular variable in a call context is called the object context. If an object context is not accessible from outside the project then it needs only the member variables actually requested in the partial evaluation run.

If an object context is available after the execution of the call then every other function public in the project needs to be considered, to find a complete list of member variables that may be accessed.

So for any object context the full list of member variables actually accessible may be determined.

The compiler may create a specific class instance for each object context.

Object Simplification for Database Persistence
If an attribute is not requested then it will not be read from the result set returned by a query. Demand driven evaluation says that a function is not called unless a value it can calculate is requested. This will be detected in the partial evaluation run, and the call to read a value that is not requested will not be added to the call context.

This means that the SELECT statement that is used to request the result set need not include a column unless it is requested. This may be implemented using the IsRequested function on the attribute value.

IsRequested is a service function that would not normally be called. Services allow us to perform actions that are only safe in a particular context.

However saving an object to the database would normally request all the database attributes of the table. If the save is for an update only this is not required. The Save may be optimised to only request those values for which there are Set calls. Set function calls AddSaveAttribute to add to the list of attributes that need to be saved. If doing an update the Save call retrieves this list and if doing an update, only created an UPDATE statement for those values.

AddSaveAttribute needs to be a special kind of statement function that does not depend on its result. Normally a statement is only executed if its result is true. The result being true then triggers the calculation. But in this case we wish to generate the list in the partial evaluation. So by adding to the list even if the Set statement is not required in all cases the UPDATE SQL for the object context is known in the partial evaluation.

The UPDATE statement will normally be created in a stored procedure by UpgradeDB (see Meta Phase 2 - Database Upgrade).

Select and save optimisation may or may not have much effect on the actual performance of the code, depending on the situation. The main reason for implementing it is to disuade developers from performing this optimisation themselves, outside the context of a proper logical model.

Run regression tests
Automated regression and unit tests are coded examples of the expected behaviour of a program. Each test is designed to stop a particular piece of expected functionality being broken by an enhancement to other functionality.

Application logic may be shared by many users. The most important thing for the users is that they can rely on the functionality.

If all tests pass the code is ready for deployment (maybe to a user acceptance testing environment).

Database Upgrade
Object Relational Mapping may be implemented with the code knowing what the structure of the database should be. Meta Phase 2 is the upgrade of the database to match the code.


 * class Class
 * bool UpgradeDB
 * AllClasses.ForEach(c : if (c.IsA(Persist)) c.CreateObject.UpgradeDB);
 * AllClasseContexts.ForEach(c : if (c.IsA(Persist)) c.CreateObject.GenerateStoredProcs);
 * }
 * }
 * }
 * }

Deployment
Deployment means installing the code on an application server where it may be used.

Execution
Execution of the code with actual parameters and other input data for the end user.

Links

 * Symbolic Logic:Programming:Logic Application
 * Symbolic Logic:Programming
 * Intelligence and Reasoning