Help using Base SAS procedures

%macroname1(%(macroname2(value2));

Reply
Occasional Contributor
Posts: 5

%macroname1(%(macroname2(value2));

Hi to whom it may concern, ´firstly thank you for your time.

 

  How do I execute two %macros in within the same statement line, i.e.  the nested per se macro is executed first and the result of this first macro is then used by the second macro? i.e.

 

  %let response = %macroname_left(%(macroname_right(value,12),8);

 

 

  For example you have the value '???????12345678.dat' but you only want the numeric part '12345678', the '????' section could be any length, hence %subtr() doesn't because we can't determin where the starting postion could be, but we do know that the string value always ends with '.dat', hence if we take the right 12 first and the from that returned value, '12345678.dat', take the left 8, we'd be left with '12345678', correct? Correct.

 

  In Excel or Access VBA I would have used the combination of the LEFT and RIGHT fuctions like LEFT(RIGHT("???????12345678.dat",12),8) and the returned result would be '12345678'.

 

Hence who I can I achieve this macro combination within same commend statement line?

 

 

I would have like to have attached a file but can't due to security issues, hence see the code below.

Although this is what I'd like to be able to do which does not work yet...

%fncSTR_LEFT(%fncSTR_RIGHT('???????????????_12345678.dat', 12), 8);

 

 

... but currently in order to get around this limitation I've put the statement in two command lines a refer to a global paramter return.

%fncSTR_RIGHT('???????????????_12345678.dat', 12);

shows 12345678.dat

%fncSTR_LEFT("&return.", 8);

shows 12345678

 

 

%global return;

%macro fncSTR_LEFT(str, len);

  data _null_;

  call symputx('return', substr(&str.,1,&len.), 'g');

 

  run;

 %put &return.;

%mend fncSTR_LEFT;

 

%macro fncSTR_MID(str, start, len);

  data _null_;

  call symputx('return', substr(&str.,&start.,&len.),'g');

 

  run;

 %put &return.;

%mend fncSTR_MID;

 

%macro fncSTR_REVERSE(str);

  data _null_;

  call symputx('return', reverse(&str.),'g');

 

  run;

 %put &return.;

%mend fncSTR_REVERSE;

 

%macro fncSTR_RIGHT(str, len);

  data _null_;

  call symputx('return', reverse(substr(reverse(&str.),1,&len.)),'g');

 

  run;

 %put &return.;

%mend fncSTR_RIGHT;

 

/*

%fncSTR_RIGHT('???????????????_12345678.dat', 12);

%fncSTR_LEFT('???????????????_12345678.dat', 4);

%fncSTR_REVERSE('???????????????_12345678.dat');

%fncSTR_MID('???????????????_12345678.dat',6,6);

*/

 

%fncSTR_LEFT(%fncSTR_RIGHT('???????????????_12345678.dat', 12), 8);

%fncSTR_RIGHT('???????????????_12345678.dat', 12);

%fncSTR_LEFT("&return.", 8);

 

Super User
Super User
Posts: 7,988

Re: %macroname1(%(macroname2(value2));

Hi,

 

That problem is very simple, what you are finding is your overcomplicating, and confusing yourself with macro language.  Don't never start with macro, write simple Base SAS programs - which is the language.  You problem can simplified down to one simple procedure:

data _null_;
  new_file=substr("???????12345678.dat",index("???????12345678.dat",".")-8,8);
  put _all_;
run;

It look slike your trying to recreate Excel functions in SAS, again, really not a good idea.  Base SAS is a fully functioning language which has been around longer than Excel.  Anything you can do there already has been implemented in SAS so do not a) create more work for yourself, and b) obfuscate code.

 

Occasional Contributor
Posts: 5

Re: %macroname1(%(macroname2(value2));

Hi RW9,

  Thank you for your repy, I understand what you are proposing but I can't yet grasp how such a solution can be generic and ultimately be reused over and over again, in that your proposal I'd have to write a four line 'data step' repeatedly each and every time I would like to perform the action within a %macro, rather than just refering to a user defind function and passing to it the required parameters.

  Anyway, I'll take your proposal as it is quite useful, but I've also investigated and found references to the usage of, 'PROC FCMP', which will also allows for the concatenation of functions like:

proc fcmp outlib=work.fcmpfuncs.func print;

  function fncSTR_LEFT(str $,len) $;

  return(substr(str,1,len));

  endsub;

run;

proc fcmp outlib=work.fcmpfuncs.func print;

  function fncSTR_RIGHT(str $,len) $;

  return(reverse(substr(reverse(str),1,len)));

  endsub;

run;

 

%let str = "???????12345678.dat";

%put fncSTR_LEFT(fncSTR_RIGHT(str,12),8)

 

Thanks again and take care!

Super User
Super User
Posts: 7,988

Re: %macroname1(%(macroname2(value2));

Please don't start creating fcmp procedures.  This is a prime example of why I have such a problem with macro programming.  Lets take your example with string left/right.  Every SAS programmer who uses SAS is very familiar with a function called substr (in fact other langauge programmers would be also).  This function is fully functioning and can substr out a string into many different ways.  It was programmed specifically for this purpose and is fast and well described.  Now you come along and create some code and hide it behind two macro calls str_left, str_right.  You haven't (i presume as 99% don't) provided any user manual, functional design specs, testing etc. which would be required for library tools.  So, Mr New Guy joins, and looks at a bit of code containing substr() calls and knows straight away, yep string manipulation.  He then looks at %str_left(...);  What does this mean, what does it do?  Sure to youo the developer it might be self exaplanatory, but to those of us who are not you this means we then have to go and look at what that code does.  This is called obfuscation.  Also, that macro does not add anything to the already present substr() function, so you lose on code readability, and on gaining anything by the code.

 

Now lets add another level here called FCMP - compiled (in proprietary binary file) procedures.  As above we still have the issue where the user who is familiar with Base SAS, is not familiar with your thinking/coding methods.  But to add to this we also restrict the user from looking at the code by hiding it further in a compiled format.  This is something we have really seen with the move from 32bit to 64bit catalogs, our code providers always used to hide their proprietary code in these compiled libraries so we couldn't see it.  Great you thinkas it still runs, but no, on 64bit these do not work, we have no access to source, and now idea what they are doing - hence multi-million dollar code deliveries are worthless.  To my mind, hiding code in formats with no source is possible the biggest no-no you can possibly do in programming.

 

Now then on your question specifically.  You can of course have code which is repeated over and over and this is where macro comes in, it is there to save you typing the same code several times, but to do this type of coding is different to standard Base SAS coding.  If the code is to be used by anyone but yourself - say in a library - then there are processes to follow.  A functional design specification should be drawn up, this will show what you want the code to do, all the inputs/outputs to the procedure, what the benefits are, what testing etc.  This is detailed and makes coding almost negligible.  You would then have a testing plan, and a user manual - very important as you want your code to work and for users to understand it and how to use it.  At teh level of FDS I would look at the requirement: create a function which returns a substring of a given string.  At this point most programmers would just ask why you don't use substr?  If the reposnse is because I need to process the data several times then that indicates the data does not support your programming, and the simple solution to the problem would be to include a further column in the data which is processed to get the 8 characters, and then that column is used throughout the program - hence no need for any macro calls.

 

Now even if you have to use macro for some reason, there is still no need to create functions, again SAS provides a mechanism to use datastep functions outside datasteps, your example can simply be written:

%let str=???????12345678.dat;
%put %sysfunc(substr(&str.,%index(&str,.)-8,8));

So again, what value in creating functions?

 

To be honest, 99% of the macro code I have seen over 15 years is both obfuscating, and of little if no value with not planning or documentation.  Don't fall into the midset of creating code just for the sake of creating code.  Base SAS can do everything, and then there is data modelling and other techniques which will make your code streamlined and simple to maintain (and programmers coming into the job after you will not sit there deleting it all muttering under their breath). 

 

/rant Smiley Happy

Occasional Contributor
Posts: 5

Re: %macroname1(%(macroname2(value2));

Hi RW9 (easy tiger, chill it's Friday the weekend is upon us Smiley Wink ),

 

  yes I can perfectly understand from where you're coming from in regard to 'Please don't start creating fcmp procedures' or 'start with %macros' etc. etc. and with all of the expected standard development protocol concerning the correct relevant documentation that would need to be kept up to date and distribute with such 'user defined functions', as you correctly write, Base SAS coding is sufficient for all 'data management' needs .... but ... and I'm note trying to wind you up in anyway, the LEFT/RIGHT was just an 'example' in order to get the question over in regard to how to get a %macro to use the derived value from another %macro returns in the same statement line, that quite rightly as you have demonstrated with '%put %sysfunc(substr(&str.,%index(&str,.)-8,8));' allows you do to with 'SAS defined functions'. What I would like to be able to do though is to write my own user defined functions and use them in exactly the same way, combined/nested.

 

  Probably using the simulated LEFT/RIGHT was a bad example, yes it comes across if I'm try to 'reinvent the wheel' of functionality that clearly already exists, so point taken, but there are plenty of examples where shared '%macros (user defined functions)' held within a SAS reference file and referring to them via the %include statement at the start of a SAS program are a convenient way to segregate/reuse code over and over again, for example like being able to import an intra-day FX rates table, I obviously don't won't to copy the complete code into each every SAS program that I may need to have an up to date fx rates table, hence I've a separate SAS program Load_Fx_Rates.sas that is %included and then I refer to it when it is necessary and I pass through quote start/end times and if need be a list of ccy codes.

 

  Similarly with regard to displaying error trap messages, or just messages in general, again I just refer to a user defined function %fncMSG_BOX and pass the message, title, button value + message icon value + default button value, yes just like a Windows message because the %fncMSG_BOX uses the SAS ability to call Windows DLL via a SASCBTBL reference to the Windows DLL MessageBoxA, or if I don't want to import all the files within a specific folder located on a local/network drive, I call another user defined function %fncGET_FOLDER_FILE that uses the GetOpenFileNameA DLL that allows me depending on what parameters I pass to it, to either select a folder(and included all sub directories) or either single/multiple selected files limited to a certain file type start with a certain file naming convention, again I wouldn't want to have to copy the extensive SAS code repeatedly into each and every SAS program that I write, so yes %macros (user defined functions) do have their place, this is why I see that the PC SAS development environment provides such functionality, I just want to be able to use to its full potential that's all.

 

  Yes, its clera that I'm a noob naive beginner (2 weeks in) and yes I’ll no doubt heed to your persuasive guidance and leadership, eventually!

 

  Thanks again and have a 'relaxing' weekend and take care!

 

Super User
Posts: 11,343

Re: %macroname1(%(macroname2(value2));

Perhaps you are looking for something more like this:

%macro fncSTR_LEFT(str, len);
  %substr(&str.,1,&len.)

%mend fncSTR_LEFT;


%macro dummy (arg);
%put the value of the argument was &arg;
%mend;

%dummy(%fncstr_left(something,5));

Not so much the specific %substr but to get a macro to resolve to a single value you need something that references the desired value and note that the value (of the %substr in this case) does not have an ending ;

 

 

There are some serious design issues about what kind of values you allow the fncstr_left macro to resolve to though if it is to be used as another macro parameter due to the need for masking special characters.

Super User
Super User
Posts: 7,076

Re: %macroname1(%(macroname2(value2));

[ Edited ]

You can define macros at three levels. Steps, Statements and Functions.

 

Most people write macros that generate one or more steps.  It might run a data step or two and a proc or two.  Typically parameters are input and output dataset names and values to drive the code generation or set procedure options.

 

Occasionally you will write a macro that generates one or more statements.  It might be used to generate global statements like FILENAME or LIBNAME.  Or more often data step code.

 

Rarely you will want your macro to work like a function so that the generated value can be included as part of a statement or the input to another function.  But to do this you need to write it so that the only text it generates is the result of the function. So you cannot include DATA steps or PROC steps.  You have to be very careful not to add any extra semi-colons.

 

This sounds like what you want with your example:

 

%let response = %macroname_left(%(macroname_right(value,12),8);

For this to work both of these macros would need to written as "function-style" macros.  The basic structure of type of a macro might look like this.

%macro divide(numerator,denominator);
%local return ;
%if %sysevalf(denominator=0) %then %let return=##DIVIDE BY ZERO##;
%else %let return=%sysevalf(&numerator/&dnominator);
&return
%mend divide;

So the macro does some purely macro statements that result in the value that is to be returned and then the value is returned. So in trivial DIVIDE macro above the value is calculated into the local macro variable RESULT and then RESULT is expanded as the value (text) that the macro generates.

 

So if I called it with 100 and 2 as the arguments it would return string of characters 50.  

 

So I could use it in the middle of data step.

data want ;
   base_value = %divide(100,2);
run;

And the result would be as if I had written this code instead.

data want ;
   base_value = 50;
run;

 

Now it might be possible with a lot of complexity and the use of new features like DOSUBL() to try to run a more complex set of statements as function.  But I suspect that you will get better results by redesigning how you process the data instead. 

Super User
Super User
Posts: 7,988

Re: %macroname1(%(macroname2(value2));

Just to add Tom, where you use the term "return" and "function", its not exactly that.  What happens is the code is pre-processed in the macro pre-processor, which is basically a find and replace system.  At this stage every macro part is resolved and the full Base SAS code program with no macro is then fed into the compiler.   The reason I point this out is that after that pre-processor step the "return" value is placed in the code, it is not dynamic at run time.  This can lead to some confusion when combining techniques or trying to influence the values when they have already been replaced.

 

Ask a Question
Discussion stats
  • 7 replies
  • 179 views
  • 1 like
  • 4 in conversation