Montura Consulting   Research & Development
Control Relationship

What is a Control?

For computers, control is simple. The SAS system compiles the code into a stream of CPU instructions. The stream is executed sequentially starting with the first instruction, ending with the last instruction. Speed of execution is the only significant difference between batch of compiled instructions and the next.

For SAS programmers, control is simple to code and easy to understand.

  • 00% is the definition of a logical step.
  • 50% is executing each logical step in sequence
  • 50% is evaluating positive and negative result messages created within each logical step.

Three software structures implement the Control Relationship.

(1) Application Driver

  • Base/SAS is interpreted as a single program from top to the bottom.
  • SAS Object applications must have a main driver that indicates which program is first, second, third, last.

(2) Component Driver

  • Base/SAS is interpreted as a single program from top to the bottom.
  • SAS Objects may evaluate the decision to execute as a precondition, may evaluate the decision to continue after each logical step, and may roll back the results following a post-result evaluation.
  • SAS Objects may self-enable or self-disable in response to signals from decision-logic and business-rule logic located in other objects.

(3) Driver Status Monitor: application driver and every instance driver are tied to the same monitor.

  • Feature not available in Base/SAS.
  • Any instance (program) may evaluate positive and negative result messages, errors, and exceptions from any other instance (program).

Standard Naming Convention

Apparently, the physical name that a programmer assigns to a block of logic in a SAS script has exactly ZERO effect on the computational speed of the application when executing in memory. This means exactly two things for SAS programers.

  1. The name of each Source code should indicate the function, meaning, step sequence, or feature implementation.
  2. When multiple independent functions are in a single source code, it's time to split that program into smaller pieces. Create one program for each independent function.

The following structure defines four physical steps within one logical step. For presentation context, each SAS object represents on logical step. This code shows that four steps MUST execute, without error, for this logical step to be considered "complete". Any error indicates complete failure for the program.

public list interface / (initialValue={'interface1',
                                       'interface2',
                                       'interface3',
                                       'interface4'});

A simple system utility coded in SCL can populate this type of property at runtime before execution.
SAS programmers need only define the property.


Application Main Driver

By default, SAS programs never have a main driver. The SAS System is designed to read scripts, compile, and execute. There is no need for driver logic because SAS knows which instruction to execute first, second, and last.

Object programs are different because the code is already compiled when submitted for execution. The main difference is that The SAS System does not execute the program starting with the first line of code, the programmer has to indicate the starting point. The first major problem is that every programmer adopts a different naming convention for the main driver.

The simple solution is to adopt a universal naming convention. The code example below shows how to execute a SAS object program where every program in the application has one method named runInterface. Also, take note of the complete lack of decision logic. No program is specifically named and executed like you see in Base/SAS.

Every program is executed unconditionally.

A main driver works on the same principal as your car engine.
The decision to (A) continue or (B) stop is NOT made inside the engine.

public list component / (sendEvent='N');
public list gMessage  / (sendEvent='N');
runInterface: method;                                                
dcl num xObject;
dcl object thisObject;

do xObject=1 to listlen(component) while (listlen(gMessage)=0);
thisObject=getitemo(component, xObject);
put 'Executing Program.....' thisObject.description;
thisObject.runInterface();
end;
endmethod;

Most object programs are simple enough to execute until one of the components issue a message that indicates an error. That message will be detected by the while statement, causing the application to terminate. The decision to stop can also come from a SAS/Frame GUI or another program

The code below demonstrates a second global vector that is evaluated for content. Click the "STOP" button on a SAS/Frame GUI and the command to stop is inserted into the global data vector named command. That command is detected inside the main driver through the local property named gCommand and the application will terminate normally.

public list component / (sendEvent='N');
public list gMessage  / (sendEvent='N');
public list gCommand  / (sendEvent='N');
runInterface: method;                                                
dcl num xObject;
dcl object thisObject;

do xObject=1 to listlen(component) while (listlen(gMessage)=0 or nameditem(gCommand, 'stop'));
thisObject=getitemo(component, xObject);
put 'Executing Program.....' thisObject.description;
thisObject.runInterface();
end;
endmethod;

Component Main Driver

Base/SAS programmers may consider every data step and SAS procedure to be an independent logical step.

proc sql;
    create table retail as
select *
from sashelp.retail;
quit;

SAS object programmers have the same view, at first.

runInterface: method;                                                            
dcl num xMethod;
do xMethod=1 to listlen(interface) while (listlen(gMessage)=0);
call send(_self_, getitemc(interface, xMethod));
end;
endmethod;

interface1: method / (description='Report Data');
submit continue sql;
create table retail as
select *
from sashelp.retail;
quit;
endsubmit;
endmethod;

SAS object programming offers more options when it comes to controlling application response to errors and unexpected conditions in the data. For example, a large program may have a large number of logical steps. SQL generates two automatic variables that are easily collected with a SAS object.

SQLRC - indicates a syntax error
SQLOBS - indicates the number of rows in the SAS dataset.

Unlike Base/SAS, syntax errors inside a submit block will not cause the entire object application to bomb. Syntax errors must be detected and handled within the application. In this case, if there is any SQLRC value above zero a message will be inserted into the global message vector and the application main driver will stop.

Zero observations need to be handled on a case-by-case basis.
In the code below an absence of data is an error.

interface1: method / (description='Report Data');
submit continue sql;
create table retail as
select *
from sashelp.retail;
quit;
endsubmit;

if symgetn('sqlrc') then
insertc(gMessage, description||' '||_method_, -1, 'SQL Error'); if symgetn('sqlobs') then
insertc(gMessage, description||' '||_method_, -1, 'Zero Observations');
endmethod;

Not all errors that occur inside a submit block are syntax errors. Object programmers have the option of adding additional steps to detect every possible problem. For example, add the following code to the application and when this error is triggered it will instantly pinpoint the problem. No need to hunt through the SAS Log looking for ERROR and WARNING messages. Bulletproofing software may seem useful for everyday SAS programs; however, this becomes absolutely mandatory for any application with GUI.

interface0: method / (description='Verify the dataset is present');
    if exist('sashelp.retail')=0 then                                                     
insertc(gMessage, description||' '||_method_, -1, 'Absent dataset');
endmethod;

SAS objects allow the programmer to easily detect an ABSENCE. The following code throws a message that indicates a SAS dataset does not have an expected column - without saying which column is absent or if multiple columns are absent. Code that indicates which columns are present and absent belongs in a separate program. The purpose here is to detect specific errors and absences, without elaborating

interface3: method / (description='Validate dataset content');
submit continue sql;
create table columns as
select name
from sashelp.vcolumn where libname='SASHELP' and memname='RETAIL' and name in('SALES','YEAR','MONTH','DATE');
quit;
endsubmit; if symgetn('sqlobs') NE 4 then
insertc(gMessage, 'Dataset SASHELP.RETAIL: a required column was not found');
endmethod;

Base/SAS and SAS Object perform exactly the same function. The core code is the same. Even with the limited examples on this page, the difference should be obvious and clear. Object programs can be loaded to the hilt with automatic validations that detect and handle every possible syntax error, data omission, and unexpected data value.

SAS Object were specifically structured for additional automatic validations.

Object programming will translate into better software design, with fewer years of programming experience.

Automation + better design = lower IT budget.


Driver Status Monitor

The "main driver" in Relational Objects show four differences compared to traditional software design.

      1. A status monitor is strictly a receiver of information.
      2. Any number of instances (programs) can connect and contribute to the same status monitor.
      3. Any number of physically separate applications can connect and contribute to the same status monitor.
      4. The number of instances (programs) and applications involved in the status relationship may change each session.

The primary job of the status monitor in the Application Driver is to determine when to terminate the application. The implementation can be as simple as the WHILE clause with a single criteria.

public list gMessage  / (sendEvent='N');

runInterface: method / (description='Application Driver');                                                
dcl num xObject;
dcl object thisObject;

do xObject=1 to listlen(component) while (listlen(gMessage)=0);
thisObject=getitemo(component, xObject);
put 'Executing Program.....' thisObject.description;
thisObject.runInterface();
end;
endmethod;

The primary job of the status monitor in the Component Driver is to determine when to stop executing logical steps. The implementation can be as simple as the WHILE clause with a single criteria.

public list gMessage  / (sendEvent='N');
runInterface: method / (description='Component Driver');
dcl num xMethod;
do xMethod=1 to listlen(interface) while (listlen(gMessage)=0);
call send(_self_, getitemc(interface, xMethod));
end;
endmethod;