FCMP with VARARGS not working as expected? - sas

While researching FCMP to help answer another question on here, I was left a bit perplexed by how proc fcmp works when using the VARARGS option in order to be able to call that function with a variable number of arguments. The sas support page for the FUNCTION statement provides the following example and clearly states that "the example implies that the summation function can be called as follows: sum = summation(1, 2, 3, 4, 5);."
options cmplib=sasuser.funcs;
proc fcmp outlib=sasuser.funcs.temp;
function summation (b[*]) varargs;
total = 0;
do i = 1 to dim(b);
total = total + b[i];
end;
return(total);
endsub;
sum=summation(1,2,3,4,5);
put sum=;
run;
Running this seems to work well and produces an output report showing sum=15 which seems to indicate that calling the function as summation(1,2,3,4,5) works as expected.
However, if I then try to use that function in the same way in a data step
data _null_;
test=summation(1,2,3,4,5);
run;
I get errors in the log
ERROR 72-185: The summation function call has too many arguments.
ERROR 707-185: Expecting array for argument 1 of the summation subroutine call.
This has me confused. Am I missing something obvious?
The second error message says that the function is expecting an array as argument 1. Forgetting the fact that calling the function that way in the fcmp proc seemed to work and that the SAS support seems to indicate that this is the whole point of this; expecting an array, which can indeed be of varying length, is really not the same as accepting a variable number of arguments, an array being one argument

If you specify VARARGS, then the last argument in the function must be an array.
And later on:
Note: When calling this function from a DATA step, you must provide the VARARGS as an array.
http://documentation.sas.com/?docsetId=proc&docsetTarget=n10vesidziklh1n1l7kidq4dqd0r.htm&docsetVersion=9.4&locale=en
Please make sure to use the latest version of the documentation, in this case 9.4. Unless you happen to unfortunately be stuck on version 9.2.
This works for me - note that it's not how I expected it to work as well...but it does :).
data demo;
array test(4) (1, 2, 3, 4);
check = summation(test);
put check=;
run;

Just clarifying based on your comment "What's the point of providing a way to define a function that takes a variable number of arguments but that can only be used as such within the very proc fcmp where it is defined?".
FCMP can load the function either using the inlib= proc option or option CMPLIB. Since FCMP knows how to call a varargs function you can still use the option, just from other proc fcmp steps or fcmp functions.

Related

how to get macro variable to evaluate math?

I have the following sas marco snippet:
%macro processLink(uuid=, name=, cluster_external_ipaddress=);
%let unix_starttime = 1000000*(&starttime - '01JAN1970:00:00'dt);
%let unix_endtime = 1000000*(&endtime - '01JAN1970:00:00'dt);
...
when this runs it just creates the variable as a string ie
=1000000*(dhms(today()-1,0,0,0) - '01JAN1970:00:00'dt)
instead of the unix timestamp in usecs.
using unix_starttime = 1000000*(&starttime - '01JAN1970:00:00'dt); outside the macro in a data step works
do i need a null datastep in the macro for this to work as intended ?
Thanks
In general if you want to work with DATA you are better off using SAS code and not MACRO code. You can use CALL SYMPUTX() to generate a macro variable if you need it later.
data _null_;
call symputx('unix_starttime',1000000*(&starttime - '01JAN1970:00:00'dt));
...
run;
You can use %eval() to do simple integer arithmetic and comparisons. If you need to use floating point numbers (or date/time/datetime literals) then you need to use %sysevalf().
%let unix_starttime=%sysevalf(1000000*(&starttime - '01JAN1970:00:00'dt));
In general, anything after a %let statement is treated as pure text. However, there are functions available to wrap around the text which tell SAS to perform a mathematical operation.
These are %eval, used for integer calculations, or %sysevalf where calculations involving decimals are required.
So you could put %let unix_starttime = %eval(1000000*(&starttime - '01JAN1970:00:00'dt));
It's not applicable here, but if you ever need to include a function in a %let statement, then precede the function name with %sysfunc

SUBSTR(SCAN(...)) and its contents

I was asked to find the length of First defined in the program below and I think it should be 15 but the answer says it is 200.
My thought is that SCAN(Author,1,',') has value Agatha, but since I did not define its length the length is as same as Author, which is 15. Again, First is valued as A since its the first letter of Agatha, but because of not specifying the length is still 15.
data test;
Author='Agatha Christie';
First=substr(scan(Author,1,','),1,1);
run;
proc contents;
run;
I have no clue where 200 comes from... can I get some help?
Before SAS 9.4, the variable length returned from Scan function used to be 200.
But SAS 9.4 variable length give in the scan function is same as the variable it operates on. hence new variable length will be 15 not 200 as said by #Craig. below is information for SAS 9.4 documentation.
In a DATA step, if the SCAN function returns a value to a variable that has not yet been given a length, that variable is given the length of the first argument. This behavior is different from the behavior in previous releases of SAS. In previous releases, code that created a variable with a length of 200 might have produced a variable with a length that was greater than expected. If you need the SCAN function to assign a variable with a value that is different from the length of the first argument, use a LENGTH statement.
The answer is wrong. It's 15.
x

Passing hash object as FCMP parameter

proc fcmp functions allow passing hash objects as parameters. The documentation is a little vague and it doesn't mention whether or not this ability is restricted to calls from other fcmp functions or if it's allowed from a data step call as well.
I'm trying to define a hash in a data step and then pass that hash by reference to an fcmp function. When I try the below code however, it's giving me: NOTE: Invalid type conversion and I'm not sure where I'm going wrong (or if this is even possible).
option cmplib=work.funcs;
proc fcmp outlib=work.funcs.funcs;
function test(h hash);
return (0);
endsub;
run;
data _null_;
format pos best.;
if _n_ eq 1 then do;
declare hash h();
rc = h.definekey('pos');
rc = h.definedone();
call missing (pos);
end;
xx = test2(h);
put _all_;
run;
The reason I am trying to do this is because I would like to eventually have several functions that I can pass the same hash table to as a parameter.
I don't believe this is possible from data step (be good to see that documentation link).
However - you CAN create a hash table within an fcmp function (or subroutine), and it will remain there for every subsequent call, until the end of the data step.
If you need to retain the state of that hash table across different fcmp function calls, then create a generic subroutine to contain the hash table, and call that subroutine from your different function calls.
Note that the implementation of hash tables within fcmp is limited (eg no attributes such as 'ordered' and you can't export them to datasets).

SAS Macro Works Standalone, But Not When Looped

I have a large dataset where I am storing macro parameters. The macro is itself used to call a number of other macros, each of which runs a number of operations.
Ideally, I'd like to use another macro to loop over each row of the dataset, construct (using PROC SQL) a macro call, store it in a macro variable :CALL, and call the variable at every iteration of the loop (with a PUT &CALL.;) That is:
%macro OUTER_LOOP(DS);
%let K = ;
%COUNT_ROWS(DS, K); /* This stores the number of rows in DS in K. */
%do i = 1 %to &K.;
proc sql noprint; ...; quit; /* Create the macro call, and store it in :CALL. */
%put &CALL.;
%end;
%mend;
%OUTER_LOOP;
This doesn't work as expected: some of the internal checks that exist in my macro indicate several datasets created by the macro are missing. Curiously, when I don't run this in a macro loop (i.e. I manually create a macro call, row-by-row, and execute it), no error occurs.
Has anyone experienced this issue? If so, is anyone familiar with a solution that would still allow me to loop over macro calls? I know that CALL EXECUTE(); (in the data step) runs different parts of the macro at different times--is that what is occurring in this case, as well?
I would add %put Loop iterating: i=&i k=&k ; inside the DO loop. That will let you see how many times the loop iterates. One possibility is the loop is exiting earlier than you intend it to. If that is the case, the cause could be a collision between the macro variable i you use for the looping in %Outer_Loop and another macro variable i you use in one of the inner macros you call. As a general rule, it's a good idea to define macro variables as %LOCAL to the macro they are defined in. Doing that will prevent such macro variable collisions. But without seeing the inner macros, that's just one possibility.
You could also add %put %superq(Call) ; inside the do loop. That will show you the macro calls that are being generated, so you can check you are getting the expected parameter values in each call.
Most likely a scoping issue. Your sub-macros are likely overwriting the values of your macro variables in your calling-macros.
You can fix this by declaring all your variables as local variables using the %local statement. If there are macro variables that you need to access after the macros have run, explicitly declare them as %global.
So for the macro you have listed above you will need the below line:
%local k i;
Don't forget you need to do this for any sub-macros that are called, and so on...
You can avoid a lot of these types of problems by generating the code yourself. For your example you could move the logic that generates the code from SQL to a data step and then instead of a macro you just need a data step. You don't even need know the number of observations in the dataset in advance.
filename code temp ;
data _null_;
set DS ;
file code ;
put '.... generated code based on values in current data ... ;
run;
%include code / source2 ;

How to run/not run SAS or SQL code based on conditional output?

I have a SAS program with a macro that will output a different list of variables based on the input criteria. For example, with %MACRO(OPTION1), I get three variables, but with %MACRO(OPTION2), I get four variables. The name of all of the variables is fixed, but it's just a matter of if they are created or not (based on the option).
How can I adjust the macro so that any option inputted by the user will still allow the macro to run? In other words, how can I tell it to ignore some variables if they don't exist.
Fortunately, I am not restricted to any specific procedure, but it would probably have to be either in a DATA step (macro language) or a PROC SQL statement (where clause or some other conditional statement).
This is answerable in the general, as an approach to programming.
The first rule:
Use macro parameters explicitly when the amount of code is small.
This means, if you want to (say) do a PROC MEANS on something, but the variable differed, you could do:
%macro run_means(var=);
proc means data=sashelp.class;
var &var.;
run;
%mend run_means;
%run_means(var=height);
%run_means(var=weight);
etc. Don't put some conditional logic in the macro, make them external. This includes lists of variables; make the whole list of variables parameters. Don't write them into your macro. If it's a long list, make it a macro variable in your main program, and pass that macro variable. Your macro itself should strive to accept what's given; today you have two sets of variables, tomorrow you might have three, or a slightly different set of one or the other. It's easier to change what you pass to the macro than to change the macro.
This concept will feel comfortable to folks used to object oriented programming, in particular the modular approach, although the separation of data is a bit different.
The second rule:
When substantial parts of a macro vary based on a parameter, separate that code into multiple macros.
In this case, let's say you have two things you want to do: run a PROC MEANS, or run a PROC FREQ, depending on if it's a character or numeric variable. Here, I suggest a general rule of not putting all of that into one macro. It's possible, but it's generally a bad idea. Adding to the previous macro, if you wanted to do this for sashelp.class, I'd do it like this:
%macro run_freq(var=);
proc freq data=sashelp.class;
tables &var.;
run;
%mend run_freq;
%run_means(var=height);
%run_means(var=weight);
%run_freq (var=sex);
How you create these may be programmatic. A lot depends on what you're doing and how you're generating the code; and sometimes in the middle of your macro, you generate the value that determines which of the two things you do. I would still write the portion that varies as a separate macro, though; you can then add logic to call the appropriate macro, and allow it to be more legible.