BookmarkSubscribeRSS Feed

CASL: It’s like Base SAS with the MACRO Built-In

Started ‎05-27-2020 by
Modified ‎05-27-2020 by
Views 4,298

One of the great features of SAS Viya is CASL ("CAS Language"). CASL is certainly not the only way to instruct CAS. CAS also supports DATA Step, SAS Procedures, and various open APIs. However, CASL is certainly a powerful option.

 

If I had to describe CASL to a SAS programmer, I'd say that it's like base SAS with the MACRO built-in.

How CASL is like Base SAS

Base SAS programs combine DATA Step with SAS Procedures. A simple one might look like this:

 

DATA public.aggregatedTable ;
SET public.detailedTable ;
      by groupingVar;   retain GroupSalesAmt;
      if first.groupingVar then GroupSalesAmt = 0;
      GroupSalesAmt = GroupSalesAmt + SaleAmt;
      if last.groupingVar then output;
run;

Proc Print data=public.aggregatedTable;   run;

 

The equivalent code would look like this in CASL:

 

Proc CAS;
  datastep.runCode submit / code="
                DATA public.aggregatedTable (replace=yes) ;
                SET public.detailedTable ;
                     by groupingVar;   retain GroupSalesAmt;
                     if first.groupingVar then GroupSalesAmt = 0;
                     GroupSalesAmt = GroupSalesAmt + SaleAmt;
                     if last.groupingVar then output;
                run;";
 
  table.fetch / to=5000 table={caslib="public" name="aggregatedTable"};
quit;

 

While the syntax is different, both approaches have two steps -- a DATA Step program followed by a printing mechanism. CASL is limited to running CAS Actions (no PROCS) but, since CAS PROCs are based on CAS actions, we should be able to do anything with CAS actions that we can do with CAS enabled PROCs.

How CASL has the "MACRO Built-In"

To see the "built-in MACRO," let's add some conditional logic to our simple example. Let's choose between printing the detailed data and the summarized data. Just change the assignment of aggreg8 between N and Y for each option respectively.

 

Now our Base SAS example looks like this. It uses SAS/MACRO for the conditional logic.

 

%macro conLogic;
   %let aggreg8=N;

   %if &aggreg8=Y %then %do;
      DATA public.aggregatedTable ;
      SET public.detailedTable ;
            by groupingVar;  retain GroupSalesAmt;
            if first.groupingVar then GroupSalesAmt = 0;
            GroupSalesAmt = GroupSalesAmt + SaleAmt;
            if last.groupingVar then output;
      run;

      Proc Print data=public.aggregatedTable;  run;
    %end;
    %else %do;
      Proc Print data=public.detailedTable;  run;
    %end;
%mend;

%conLogic;

 

The equivalent code would look like this in CASL.

 

Proc CAS;
  aggreg8='N';

  if aggreg8='Y' then do; 
     datastep.runCode submit / code="
                   DATA public.aggregatedTable (replace=yes) ;
                   SET public.detailedTable ;
                        by groupingVar;   retain GroupSalesAmt;
                        if first.groupingVar then GroupSalesAmt = 0;
                        GroupSalesAmt = GroupSalesAmt + SaleAmt;
                        if last.groupingVar then output;
                   run;";

     table.fetch / to=5000 table={caslib="public" name="aggregatedTable"};
  end;
  else do;
     table.fetch / to=5000 table={caslib="public" name="detailedTable"};
  end;
quit;

 

Again, both approaches are more or less equivalent but CASL requires no MACRO coding to accommodate the conditional processing. CASL variables (aggreg8 in the example) take the place of macro variables. CASL expressions (aggreg8='Y') take the place of MACRO expressions. CASL statements (IF, THEN, ELSE, DO) take the place of MACRO statements.

Data-Driven Code Generation / Processing

What about something more complex, like generating code from data? This can get fairly complicated with MACRO, requiring multiple levels of macro resolution (lots of ampersands strung together, e.g. "DATA p&&dsn&q..inputTab;" (see this paper for more info). A simple, single level (only one ampersand required) example is found in this article.

 

CASL can also do iterative data-driven processing using DO loops. However, you don't need MACRO resolution to reference the right table name, field name, library name, file name, directory name or whatever. CASL's dictionary and result table data types, which can be returned from previously-run CAS actions, can be used instead. (CASL is like Base SAS with the SCL Built-In?)

 

For really complex generated code, you can build the code in pieces and store/append it in/to CASL variables and execute it with the runCasl CAS action. For more info on the runcasl action, see this article by Deva Kumar.

 

Here is a simple example of iterating through and loading the SASHDAT files in a CASLib's data source location. The fileinfo action is used to store the data source file list in the filesToLoad CASL variable. It is a CASL dictionary field that contains a table in its sole FileInfo member. We then spin through the rows of the FileInfo table returning the file name to use in the loadTable action.

 

proc cas;
  table.fileinfo result=filesToLoad/ caslib="casuser";

  do i = 1 to dim(filesToLoad.FileInfo);
      fname = filesToLoad.FileInfo[i,'Name'];
      table.loadtable /
         caslib="casuser"
          path=fname;
  end;
run;
quit;

 

Comments

Hi, 

 

First, a disclaimer, it is very interesting article and it gave me new knowledge about CASL whence below text is not a criticism, I'm just sharing my observation. 🙂

 

I can't agree with the main thought/title of the article, which is "CASL: It’s like Base SAS with the MACRO Built-In".

After the lecture my thought was: "Shouldn't it be more like >>CASL: It’s like Base SAS with the Call Execute()<<?"

In the example presented a macro's conditional logic altering the 4GL code is compared with a CASL's conditional logic altering the text string containing 4GL code.

So in fact something like this datastep:

data _null_
  aggreg8='N';

  if aggreg8='Y' then do; 
     code="DATA public.aggregatedTable (replace=yes) ;
             SET public.detailedTable ;
                by groupingVar;   retain GroupSalesAmt;
                if first.groupingVar then GroupSalesAmt = 0;
                GroupSalesAmt = GroupSalesAmt + SaleAmt;
                if last.groupingVar then output;
           run;
           Proc Print data=public.aggregatedTable;  run;
          ";
  end;
  else do;
     code="Proc Print data=public.detailedTable;  run;";
  end;

  call execute(code);
quit;

would be more representative, wouldn't it?

 

For the very simple example with only one parameter, like above, it is easy and "readable". But when we add some more "parameters" to the code, e.g. input dataset name, output dataset name, etc. in case of the macro language we still will be able to keep straight forward and fairly "readable" code:

%macro conLogic(
  aggreg8=N
 ,in=public.detailedTable
 ,out=public.aggregatedTable
);

   %if &aggreg8=Y %then %do;
      DATA &out. ;
      SET &in. ;
            by groupingVar;  retain GroupSalesAmt;
            if first.groupingVar then GroupSalesAmt = 0;
            GroupSalesAmt = GroupSalesAmt + SaleAmt;
            if last.groupingVar then output;
      run;

      Proc Print data=&out.;  run;
    %end;
    %else %do;
      Proc Print data=&in.;  run;
    %end;
%mend;

%conLogic;

while in case both CASL and `call execute()` it would be text strings manipulation:

Proc CAS;
  aggreg8='N';
  in="public.detailedTable";
  out="public.aggregatedTable";

  if aggreg8='Y' then do; 
     datastep.runCode submit / code="
                   DATA" || out || "(replace=yes) ;
                   SET " || in || " ;
                        by groupingVar;   retain GroupSalesAmt;
                        if first.groupingVar then GroupSalesAmt = 0;
                        GroupSalesAmt = GroupSalesAmt + SaleAmt;
                        if last.groupingVar then output;
                   run;";

     table.fetch / to=5000 table={caslib="public" name="aggregatedTable"};
  end;
  else do;
     table.fetch / to=5000 table={caslib="public" name="detailedTable"};
  end;
quit;

which isn't that convenient in my opinion (and we don't have any single or double quotes situations to handle here).

 

All the best

Bart

 

 

You can eliminate some of the inconvenience of your last solution, or at least exchange it for a different kind of inconvenience, with the resolve function, something like (untested):

 

 

data _null_
  aggreg8='N';

  if aggreg8='Y' then do; 
     code='DATA &OUT.;
             SET &IN.;
                by groupingVar;   retain GroupSalesAmt;
                if first.groupingVar then GroupSalesAmt = 0;
                GroupSalesAmt = GroupSalesAmt + SaleAmt;
                if last.groupingVar then output;
           run;
           Proc Print data=public.aggregatedTable;  run;
          ';
  end;
  else do;
     code='Proc Print data=&IN.;  run;';
  end;

call symput('in', 'public.detailedTable');
call symput('out', 'public.aggregatedTable'); call execute(resolve(code)); quit;

 

Quoted strings would make this more difficult.  I really wish SAS would upgrade the quote function to  "quote(StringToQuote, Quotemarks)", where Quotemarks defaults to the double quote, but could also be "'" for single quotes, or '«»', or '[]', and the syntax could easily be extended to support escape characters.

 

@JackHamilton 

My main point here was that comparing macro and CASL wasn't the way I would go.

 

And about quote() function I 100% agree. This would be a great improvement. What is even funnier - I was thinking about implementing something like this with FCMP myself this morning 🙂

 

Bart

Thanks for the input guys.  I'm glad that the post is generating some conversation.  -Steve

If you create that quote function, please post the code.  Thanks.

Tom

@JackHamilton  The SAS QUOTE() function currently supports using either double quotes (the default)

x=quote('He said "hello" to me','"');

or single quotes

x=quote("Don't walk on the grass","'");

on the outside of the generated quote string.

Thank you!  I wonder when that was added.

 

It is documented for the data step, but not for DS2 or FedSQL.  It also works in PROC SQL.

 

I didn't see it in the documentation because I looked at the first result, which was for FedSQL.  The second result is for DS2.  I falsely assumd that the quote function works the same way in all environments.  The second argument is also not documented for the %QUOTE function.

 

 

 

This example still involves putting code into text strings, but for anybody using Viya (July 2021 or later), here's an additional option using PROC PYTHON:

%let aggreg8 = Y;
%let in = public.detailedTable;
%let out = public.aggregatedTable;
%let region = East;

proc python;
submit;

aggreg8 = SAS.symget('aggreg8')
in_ds = SAS.symget('in')
out_ds = SAS.symget('out')

if aggreg8 == 'Y':
    SAS.submit(f'''    data {out_ds};                                           ''') 
    SAS.submit(f'''       set {in_ds} end=end;                                  ''')
    SAS.submit(f'''       by groupingVar;  retain GroupSalesAmt;                ''')
    SAS.submit(f'''       if first.groupingVar then GroupSalesAmt = 0;          ''')
    SAS.submit(f'''       GroupSalesAmt = GroupSalesAmt + SaleAmt;              ''')
    SAS.submit(f'''       if last.groupingVar then output;                      ''')
    SAS.submit(f'''       if groupingVar="&region" then put 'Sales=' SaleAmt;   ''')
    SAS.submit(f'''    run;                                                     ''')
    SAS.submit(f'''                                                             ''')
    SAS.submit(f'''    proc print data={out_ds};  run;                          ''')
else:
    SAS.submit(f'''    proc print data={in_ds};  run;                           ''')

endsubmit;
run;

I'm still experimenting with this approach, but I like how "readable" the code is.  I could have typed less and put that first DATA step and proc print into one string, but I prefer to submit line-by-line....the submitted code is formatted nicer in the log.

 

I'm using Python's f-strings for the substitutions, and the triple quotes are helpful for embedded quotes.

Version history
Last update:
‎05-27-2020 03:58 PM
Updated by:
Contributors

Ready to join fellow brilliant minds for the SAS Hackathon?

Build your skills. Make connections. Enjoy creative freedom. Maybe change the world. Registration is now open through August 30th. Visit the SAS Hackathon homepage.

Register today!

Free course: Data Literacy Essentials

Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning  and boost your career prospects.

Get Started

Article Labels
Article Tags