Implementing Methods with SCL

Introduction

You can implement methods for your components with SCL code. The SCL implementation for a method can be stored in three different places:
  • the SCL entry that is identified as the Source Entry for the method in the Class Editor
  • the SCL entry that is identified in the SCL method declaration statement of a CLASS block. In the following example, the SCL implementation for the m1 method is stored in the entry named sasuser.myclasses.oneCode.scl.
    Class One extends sashelp.fsp.object.class;
        m1: public method 
            n:num 
            return=num
            / (SCL='sasuser.myclasses.oneCode.scl');
    EndClass;
  • the SCL class definition itself, where the method is both defined and implemented. For example:
    Class One extends sashelp.fsp.object.class;
      m2: public method
          n:num
          return=num;
        dcl num total;
        total=n * 2;
        return total;
       endmethod;
    
      /* ...insert additional methods here... */
    EndClass;
When writing methods, keep the following tips in mind:
  • Method implementations always start with the METHOD statement and end with ENDMETHOD.
  • Method names are not case-sensitive and can contain underscores, but not embedded blanks or other special characters. Methods that are supplied by SAS are named using a leading underscore, a lowercase first letter, and subsequent uppercasing of any joined word (such as _setBackgroundColor) to promote readability, but your methods do not have to conform to the SAS convention.
    Note: Methods supplied by SAS include a leading underscore so that they can be differentiated from user methods. If you name a new method using a leading underscore, a warning message appears.
  • Method calls must include all arguments that you have specified in the method signature.
  • You must either declare any variable that you use within a METHOD block, or store the variable as a private attribute on the class. For example:
    addToList: public method
       item:input:char
       aList:update:list;
       dcl num rc;           /* rc must be declared */
       rc=insertc(aList, item, -1);
       /* ...insert additional SCL statements here... */
    endmethod;
    You can use either the DECLARE statement or its abbreviated form DCL to declare local variables in a method.

SCL and Overridden Methods

You can write methods to override existing methods, but you should execute the method on the parent class with the _super() routine. The _super() routine determines the current method context and then executes the method of the same name on the parent class. The point at which you execute the parent method can significantly affect the behavior of the overriding method. For example, if the _super() call occurs before any other statements in the METHOD section, the inherited behavior is executed before the overridden code executes. In order to use _super(), you must be inside a CLASS or USECLASS block.
Some methods require you to invoke the inherited method in a particular order. Each of the following examples is assumed to exist inside an appropriate CLASS or USECLASS block:
  • If you override an object's _init method, you must invoke the super _init method before any other processing:
    init: public method;
       _super();   /* call super */
       /* ...insert additional code here... */
    endmethod;
  • If you override an object's _bInit method, you must invoke the super _bInit method before other processing:
    bInit: public method;
       _super();   /* call super */
       /* ...insert additional code here... */
    endmethod;
  • If you override an object's _term method, you must invoke the super _term method as the last operation:
    term: public method;
       /* ...insert additional code here... */
       _super();   /* call super */
    endmethod;
  • If you override an object's _bTerm method, you must invoke the super _bTerm method as the last operation:
    bTerm: public method;
       /* ...insert additional code here... */
       _super();   /* call super */
    endmethod;
Note: When overriding a method, you may not change its signature. If you require a different signature, you should overload the method.

Using USECLASS Statement Blocks with Methods

By using USECLASS/ENDUSECLASS statements around an SCL method block, you can use methods and attributes for the specified class without repeating the class identifier. Due to compile-time binding, the SCL compiler can distinguish between local variables and class attributes. Refer to the SAS Component Language: Reference for complete details on the USECLASS statement.
You can also use the _super() routine in the implementation of a method that is contained in a USECLASS statement block without having to specify the object identifier.
  • Your method can execute the _super() routine without specifying a super or parent method. The SCL compiler assumes that you want to execute the method of the same name on the parent. For example, to override an _init method:
    init: public method;
       _super();   /* run _init method on parent */
       /* ...insert additional code here... */
    endmethod;
Note: So that any new properties you have added are saved with a class, be sure to save a CLASS entry before compiling the SCL entry that contains its method implementation(s).
If you override a method that has a signature of '(None)' and implement the method inside a USECLASS/ENDUSECLASS block, you must include a signature designation in the METHOD statement. For example, if you override the _foo method and _foo has a signature of '(None)', your SCL code could include
USECLASS mylib.mycat.myclass.class;
foo: method/(signature='N');
  /* ...insert additional code here... */
endmethod;
enduseclass;
Any method that is implemented within a USECLASS/ENDUSECLASS block must designate a signature.

Improving the Performance of SCL Code in Methods

Here are several tips to make the SCL code in your methods work more efficiently:
Be careful when you use recursion in methods.
You can use recursive calls such that a method can invoke itself. However, be sure to provide an exit case so that you do not recurse infinitely. Also be sure to invoke the inherited method with _super() and not with the form object.method(). Attempting to use a direct call to the method when you should use _super() results either in an infinite loop or in an out-of-memory condition.
Use the _term() method to delete objects.
If you programmatically create instances of objects in your SCL, you should always invoke the _term() method when your application no longer needs those objects. Objects that are no longer used are not automatically deleted while the application is running. Invoking the _term() method deletes the object and frees the memory that it occupies.
DCL sasuser.myclasses.myObj.class demoObj = _new_ myObj(); 
/* ...insert additional SCL statements here... */ 
demoObj._term();
Delete SCL lists that have been created by a method.
If any of your object's methods create new SCL lists, delete these lists with the DELLIST function. SCL lists take up memory and are not always automatically deleted. Leaving too many SCL lists open in memory can cause your application to run out of available memory.
Do not bypass SCL method-calling functions.
Always invoke the methods in the same manner, and do not bypass the method-calling functions that SCL provides. For example, consider a banking account class that has two methods, deposit() and update(). The deposit() method records a deposit, and the update() method updates a data set with the new account balance. You can implement these methods together in a single SCL entry, ACCOUNT.SCL:
  /* ACCOUNT.SCL: methods for the ACCOUNT class */ 
update: public method;   
  /* ...SCL code to update a data set with the account information... */ 
endmethod;  

deposit: public method
    amount:update:num;
  /* ...code to process a deposit goes here... */ 
endmethod;
Since you want to perform an update after each deposit, you may need to invoke the update operation from the DEPOSIT code. You may be tempted to call this operation directly, either with a LINK statement or by using CALL METHOD:
/* ACCOUNT.SCL: methods for the ACCOUNT class */ 
deposit: method
         amount:update:num;    
  /* ...code to process a deposit goes here... */     

/* this is the wrong way to do an update! */   
link update; 
endmethod;
or
/* ACCOUNT.SCL: methods for the ACCOUNT class */ 
deposit: method 
         amount:update:num;    
   /* ...code to process a deposit goes here... */     

   /* this is also the wrong way to do an update! */    
   call method('account.scl', 'update'); 
endmethod;
Both of these mechanisms may work when you first develop your application, but they violate basic principles of object-oriented programming. To understand why, consider what happens when someone decides to use your ACCOUNT class. They decide that your account class provides most of the functionality that they want, except that they want to record transactions in an audit trail. To do so, they override the update() method to also update the audit trail. If your deposit() method is implemented using either of the techniques presented above, then the new update() method is never called when a deposit is made.
The proper way to perform the update is to call the update() method:
/* NEW ACCOUNT.SCL: methods for the NEW ACCOUNT class */  

USECLASS newaccount.class;  
  deposit: method     
           amount 8;    
  /* ...code to process a deposit goes here... */        

    /* This is the correct way to do an update! */   
    update();  
endmethod;
Then, if someone overrides the update() method, your deposit() method automatically invokes that new update() method. Of course, the developers of the new update() method that adds an audit trail should also invoke your original update() code using _super().
   update: method;
     _super();      
     /* ...SCL code to update a data set */      
     /* with the account information...  */    
   endmethod; 
ENDUSECLASS;