Standard functions in SAS are plentiful and incredibly useful. Not having to write your own algorithms every time reduces development time, decreases the number of typos in your code, and ensures that everyone performs their calculations in the same, agreed upon way. Sometimes you have a business problem that there is no standard function. Luckily, you can create your own!
In this article, I'll show you how to create three different types of functions,
Macro functions,
FCMP functions, and
CASL functions.
Business problem
Every Swedish citizen has a unique personal number on the form YYYYMMDD-XXXX, where YYYYMMDD is the person's birthdate and XXXX are four digits. Many times, I've had to extract the birthdate from such a personal number to use in my calculations. Let's create three different functions for this, to be used in different contexts. Let's start with a macro function.
Macro function
First off, a definition. By macro function I mean a macro definition which only adds a value to the input stack and no SAS code. We can express it as if the macro definition returns a value. A macro function can be used practically anywhere in our programs, but don't forget that the macro processor does it's thing before the program is compiled! Below is a version of how the macro function solving our business problem might look.
%macro birthdateFromPnr(pnr); /* 1 */
%sysfunc(inputn(%substr(&pnr, 1, 8), yymmdd8.)) /* 2 */
%mend;
%put NOTE: Macro version: %birthdateFromPnr(19121212-1212); /* 3 */
Begin the definition of the macro. It takes a personal number as a positional parameter.
Use %SYSFUNC to invoke the INPUTN function. It interprets the first eight characters of the personal number using the YYMMDD8 informat.
Invoke the macro function.
In order to really make the function reusable, we should either save the source code and use the Autocall Facility or compile the macro and use the Stored Compiled Macro Facility.
FCMP function
Now let's implement the same logic in a function created by PROC FCMP.
proc fcmp outlib=work.functions.identity; /* 1 */
function birthdateFromPnr(pnr $); /* 2 */
return (input(substr(pnr, 1, 8), yymmdd8.)); /* 3 */
endfunc; /* 4 */
run;
options cmplib=work.functions; /* 5 */
data _null_;
birthdate = birthdateFromPnr('191212121212'); /* 5 */
putlog 'NOTE: FCMP version: ' birthdate;
run;
Begin the FCMP procedure. Specify that any function created in the step should be stored in the identity package in the FUNCTIONS dataset in the WORK library.
Begin the definition of the function. It takes a personal number as a character parameter.
Use the INPUT function to interpret the first 8 characters of the using the YYMMDD8 informat. Return the result.
End the definition of the function.
Add the WORK.FUNCTIONS dataset to the locations where SAS looks for function definitions.
Invoke the function.
Just like with the macro function, we should of course share the function with our colleagues. Simply store the function in a dataset in a permanent library that they have access to, instead of saving it in WORK.
CASL function
Last but certainly not least, let's create a CASL function!
cas; /* 1 */
proc cas; /* 2 */
function birthdateFromPnr(pnr); /* 3 */
return (inputn(substr(pnr, 1, 8), 'yymmdd8.')); /* 4 */
end; /* 5 */
print (NOTE) 'CASL version: ' birthdateFromPnr('19121212-1212'); /* 6 */
run;
quit;
Start a CAS session.
Begin the CAS procedure, allowing us to write CASL code.
Begin the definition of the function. It takes a personal number as a parameter.
Use the INPUTN function to interpret the first 8 characters of the personal number using the YYMMDD8 informat. Return the result.
End the definition of the function.
Invoke the function.
Just like with the other two function types, we wouldn't be our generous selves if we didn't share the function with our colleagues! Use the CASLstore functions for this.
When to use what function type?
Now that we know how to create these types of functions, the question is in what scenario should/could we use what function type? The short answer is:
Macro functions can be used before the compilation of your program,
FCMP functions can be used during the execution of the program in SAS 9 or on the Compute Server in SAS Viya,
CASL functions can be used during the execution of your CASL code.
What makes the longer answer more interesting is that there are techniques we can use to:
Use FCMP functions before the compilation of our programs (%SYSFUNC/%QSYSFUNC macro functions),
Use FCMP functions in our CASL code (FCMP CAS Action Set),
Use macro functions during the execution of our programs (DOSUBL function and RUN_MACRO function for use in FCMP functions)
to name a few. So, it also comes down to personal preference.
One thing that we can only do with our CASL functions and not our FCMP functions is to use CAS Actions. Below is an example, including how we can store the function for later use.
cas;
proc cas;
source myFunction; /* 1 */
function nObs(dsIn); /* 2 */
simple.numrows r=nr / table=dsIn; /* 3 */
return nr.numrows; /* 4 */
end func; /* 5 */
endsource; /* 6 */
upload_caslstore({caslib='casuser', name='functions', replace=true}, myFunction); /* 7 */
table.promote / caslib='casuser', name='functions'; /* 8 */
run;
quit;
Begin a source block so that we can write our code without having to quote it as a string.
Begin the definition of the function. It takes a dictionary with keys CASLIB and NAME as a parameter.
Use the NUMROWS action in the SIMPLE action set to get the number of rows in the specified table. Store the result in the dictionary NR.
Return the NUMROWS entry from the NR dictionary.
End the definition of the function.
End the source block.
Store the function in the FUNCTIONS table in CASUSER, referring to the above source block. Replace the table if it exists.
Promote the table to global scope so it persists in memory when the current CAS session is terminated.
We can then use the function in other CAS sessions:
proc cas;
caslstore({caslib='casuser', name='functions'}); /* 1 */
print (NOTE) 'Table myTable in CASLIB myCaslib has ' nObs({caslib='myCaslib', name='myTable'}) 'observations!'; /* 2 */
run;
quit;
Specify to look in the FUNCTIONS table in the CASUSER CASLIB for function definitions.
Invoke the function.
If we stored it in a CASLIB accessible by our colleagues, they could also use it!
Conclusion
Creating custom functions and sharing them with colleagues is easy and can be very useful. Different types of functions serve different purposes, and the application areas are infinite. Of course, functions can be shared with others than just your colleagues! Check out @yabwon's GitHub repository for the SAS Packages framework for some inspiration.
... View more