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).
Related
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
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.
I've got a table with DataSet names in, some of which contain macro references in the name.
e.g. Monthly_Data_&YYMM (where YYMM is the latest month)
I want to keep the Table with this string, but then have a new variable with the evaluated DataSet name.
e.g. Monthly_Data_&YYMM, Monthly_Data_1612
I can't work out a way to do this. If I read the dataset as a macro variable it returns as the required name, but I can't then join it on the same row as the non evaluated reference.
I'm sure this must be possible, and probably quite easy, but I just can't get my head around how to do this.
Many thanks
You can use the resolve function to do this, e.g.
%let YYMM = 1601;
data mydata;
dsname = 'Monthly_Data_&YYMM';
dsname_resolved = resolve(dsname);
run;
N.B. all macro variables used in your column of names must be defined in your session with the correct values at the point when the resolve function executes. If two different data sets used the same macro variable in their name, but it took different values at different times, you will need to redefine the macro variable and run your logic separately, possibly via separate data steps or call symput + symget.
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 ;
I am defining a macro variable inside a macro. Then, I am feeding it into a second macro. Inside macro2 counter changes value to 200. However, when I check what is inside the macro variable that I put in after macro 2 runs, it still says 0. I would like it to store the value 200? is this possible?
%macro macro1();
%let variable1= 0;
macro2(counter=&variable1)
%put &variable1;
%mend macro1;
%macro1;
You have a couple of issues here. First of all, you are missing the % before your call to macro2, but I suspect that's just a typo. The main issue is that you are trying to do what is referred to in other languages as call-by-reference. You can do this in SAS macro by passing the name of your variable rather than the value of your variable, and then use some funky & syntax to set the variable of that name to a new value.
Here is some sample code that does this:
%macro macro2(counter_name);
/* The following code translates to:
"Let the variable whose name is stored in counter_name equal
the value of the variable whose name is stored in counter_name
plus 1." */
%LET &counter_name = %EVAL (&&&counter_name + 1);
%mend;
%macro macro1();
%let variable1= 0;
/* Try it once - see a 1 */
/* Notice how we're passing 'variable1', not '&variable1' */
%macro2(counter_name = variable1)
%put &variable1;
/* Try it twice - see a 2 */
/* Notice how we're passing 'variable1', not '&variable1' */
%macro2(counter_name = variable1)
%put &variable1;
%mend macro1;
%macro1;
I actually have another post on StackOverflow that has an explanation of the &&& syntax; you can have a look at it here. Note that the %EVAL call has nothing to do with call-by-reference, it is just there to do the addition.
Sparc_Spread explains how to "call by reference" in the SAS macro language, which may solve your problem.
In this particular case though, it's not necessarily crucial to use call by reference, and I'd argue it's not idiomatic to SAS macro language to use it (though certainly nothing wrong with it - it just looks a bit odd, and is a bit harder since it's not really a native concept, though certainly intentionally supported to be used that way if desired). There are two ways to get around this that both are very easy to use.
First of all, let's say you know the variable name you want to increment, and the starting value is the only interesting thing. Thanks to how SAS macro language handles scoping, with something not exactly lexical scoping and not exactly functional, it automatically will use the variable that already exists in the most local scope, when it does already exist (with some minor caveats, such as macros using DOSUBL).
So this works as expected:
%macro macro2(counter=);
%do variable1 =&counter. %to 200;
%if %sysfunc(mod(&variable1.,50))=0 %then %put &=variable1;
%end;
%mend macro2;
%macro macro1();
%let variable1= 0;
%macro2(counter=&variable1.);
%put &=variable1;
%mend macro1;
%macro1;
(Of course, that is if you expect &variable1 to have the value of 201 - because %do loops, like do loops, always get incremented one higher than their ending value. I assume your real procedure works differently.)
That's because the &variable1. referred to in %macro2 automatically is the one present in the most local scope - which in this case is the scope of %macro1.
Alternatively, if you're using this %macro2 for the purpose of incrementing a counter, I would use a function-style macro method.
A function-style macro by definition is one that returns only a single value - and by returns I mean has a single value at the end of the macro's code that is presented in plain text (since a macro is, after all, only intended to create text that will then be parsed by the normal SAS language parser).
This can then be used on the right side of an equal sign in an assignment statement. The key is that it uses only macro language elements - %do loops and such - and no data step, proc, etc., language that would prevent it from being on the right side of an equal sign in an assignment statement (ie, x=%macrostuff(); cannot be x=proc sql(select...)).
So the following accomplishes the goal: increment a counter some amount, return the value (201, in this case, just like before), and then that can be assigned to a macro variable.
%macro macro2(counter=);
%do internal_counter =&counter. %to 200;
%if %sysfunc(mod(&internal_counter.,50))=0 %then %put &=internal_counter.;
%end;
&internal_counter.
%mend macro2;
%macro macro1();
%let variable1= %macro2(counter=0);
%put &=variable1;
%mend macro1;
%macro1;
I would suggest that this is the most idiomatic way to accomplish this, and the most simple: you pass the value you want as input, function operates on it, returns value, which you then assign to a variable in your macro however you want.