Re: %eval in recursive macro #7

  • Follow


Summary: Macro design issues and semicolons
#iw-value=2

Cary,

I agree with Howard that you might learn some interesting things
about macro by reading papers, and with Toby that there are much
better areas in macro to explore than recursion.  However, I do
not ever remember writing a serious macro or a macro for a paper
that involved recursion.  Your question is not really about
recursion or even %EVAL, but it does raise the need for discussing
design issues.

First let's consider a simpler form of the macro.

   %macro factorial ( n ) ;
      %if &n < 0 %then %put ERROR: NO Negative Numbers ;
      %else
      %if &n <= 1 %then 1 ;
      %else %eval (&n * %factorial(%eval(&n-1))) ;
   %mend  factorial ;

This works and protects the macro from calls with negative numbers.
It isn't nice with decimals but at least it doesn't work, and this
lesson is not really about error handling.  But it doesn't answer
your question either.

Your macro:

>   %macro factorial(n);
>    %if &n GT 1 %then %do;
>     %let next = %factorial(%eval(&n-1));
>     %eval(&n * &next);
>    %end; %else
>    1;
>   %mend;

had a problem with a semicolon and you wanted to know why, when this
fix was suggested.  But first let's consider where the variable NEXT
resides?  Is it global, or local and then which local?  Now in your
case it doesn't matter because you always give a new value to NEXT
and never refer to an old one.  However, since your macro is about
recursion it is particularly important to declare NEXT local.  Then
every executing FACTORIAL has its own copy of that variable in its
own environment for its own use.  As you wrote it there is only one
copy of the variable and it is either in the global environment or
in the environment of the first call to FACTORIAL.  It is not wrong
here, but a dangerous habit to develop and particularly dangerous
in any real recursion situation!

From my point of view, your question should have been - why did the
semicolon work for N=1 and 2, but not 3?  Well, to put it simply,
with 1 the offending semicolon was not relevant, and with 2 it
wasn't tested by your %LET statement.  However, consider:

   53   %macro factorial(n);
   54    %if &n GT 1 %then %do;
   55     %let next = %factorial(%eval(&n-1));
   56     %eval(&n * &next) ;
   57    %end; %else
   58    1;
   59   %mend;
   60
   61   %let x = %factorial(2) + 9 ;
   61   %let x = %factorial(2) + 9 ;
                               --
                               180
   ERROR 180-322: Statement is not valid or it is used out of
   proper order.

Now, we see that there was an error for 2; it just wasn't apparent
until one used the value in an expression with text following the
value.  Sometimes when something seems to work and sometimes not,
you can learn more by asking why you thought it worked, because it
makes you more aware of how accidental "working" can be.

Now to answer the question, you have to understand what macros do
in the macro facility.  They generate text to be read by the SAS
compiler.  In its simplest form consider:

62
63   %macro q(n) ;
64      &n ;
65   %mend  q ;
66
67   %let x = %q(2) + 9 ;
67   %let x = %q(2) + 9 ;
                    --
                    180
ERROR 180-322: Statement is not valid or it is used out of
proper order.

Ah, the lesson should be generating semicolons in code won't
work when that semicolon splits two parts of a SAS statement
or macro expression such as

    %eval(&n*%factorial(&next)

So why did my code work?  Didn't I have semicolons at the end of
every %IF or %ELSE statement?  Well yes, but they belong there!
Macro statements require a semicolon and the macro facility owns
that semicolon, i.e. the expression after the %THEN or %ELSE did
not include a semicolon.  In your case there was no statement so
the semicolon was part of the text generated, not an instruction.

Sticking with almost your macro, you might find the style

   %macro factorial(n);
    %local next return ;
    %if &n GT 1 %then %do;
     %let next = %factorial(%eval(&n-1));
     %let return = %eval(&n * &next);
    %end;
    %else
     %let return = 1;

    &return

   %mend factorial ;

This style makes is clear exactly what the macro is generating.
It isn't very good for macros generating SAS statements, but
it is very good for macros generating expressions.  It makes
it very clear what is generated and focuses attention on that
semicolon.

Ian Whitlock
===============

Date:         Mon, 16 Jul 2007 10:56:37 -0400
Reply-To:     Cary Miller <cmiller1@COQIO.SDPS.ORG>
Sender:       "SAS(r) Discussion"
From:         Cary Miller <cmiller1@COQIO.SDPS.ORG>
Subject:      %eval in recursive macro
Hi SAS people,
   I am experimenting with basic recursion and having trouble
   with
the %eval function.  The macro works for n=1,2 but fails for n=3.
I must be missing something about %eval but not sure what.
Can anybody help shed light on this?
Thanks,
  CAM

%macro factorial(n);
 %if &n GT 1 %then %do;
  %let next = %factorial(%eval(&n-1));
  %eval(&n * &next);
 %end; %else
 1;
%mend;


%let x = %factorial(1);   *OK;
%let x = %factorial(2);   *OK;
%let x = %factorial(3);   *ERROR;

/*ERROR: A character operand was found in the %EVAL function or
%IF
condition where a numeric*/
/*       operand is required. The condition was: 3 * 2;*/
/*ERROR: The macro FACTORIAL will stop executing.*/
0
Reply iw1junk (1195) 7/17/2007 8:24:00 PM


0 Replies
41 Views

(page loaded in 0.121 seconds)


Reply: