Date: Sat, 10 Dec 2005 19:50:24 +0000
Reply-To: iw1junk@COMCAST.NET
Sender: "SAS(r) Discussion" <SAS-L@LISTSERV.UGA.EDU>
From: Ian Whitlock <iw1junk@COMCAST.NET>
Subject: Re: MAcro Design was (Re: Macro quoting essentials)
Subject: Re: MAcro Design was (Re: Macro quoting essentials)
Summary: Discussion of design issues in context of help suggestion
and the macro PRINTZ
Respondent: Ian Whitlock iw1junk@comcast.net
Lei,
You have an interesting idea to add the PARMBUFF option to all macro
calls so that a call such as
%printz; or %printz()
will produce help.
For the uninitiated, I note that adding the PARMBUFF option will preserve
positional and keyword parameters. For example,
%macro q ( x, y , z=stuff ) / parmbuff;
%put x=&x y=&y z=&z :&syspbuff ;
%mend q ;
%q(a,b)
produces
x=a y=b z=stuff :(a,b)
Design issues fall into two categories - system issues across macros and
issues within a macro.
Your help suggestion is a system issue. It certainly makes sense to have
a unified approach to help, but there are several possibilities.
1) Empty argument list
Disadvantages: User cannot call with all defaults
Ugly %PUT statements
Macro must use PARMBUFF option
One level of help
Closed to very complex macros
(e.g. macro with 20 interacting parameters)
Advantages: Immediate interactive or batch use
Intuitive for user used to UNIX commands
2) HELP parm (values ?,yes,y,1...)
Disadvantages: User must remember and type the key word HELP=
Ugly %PUT statements
One level of help
Closed to very complex macros
(e.g. macro with 20 interacting parameters)
Advantages: Immediate interactive or batch use
Intuitive once learns to type "HELP=?"
3) External documentation:
Disadvantages: User must learn to use the help facility
Complex to set up
Long macros if documentation stored with macro
Advantages: Open to multiple levels of help
Immediate interactive or batch use when controlled
by macro or interactive when some help facility is
used
Open to very complex macros particularly when an
external help system is used
In the long run I think #3 has to win because it is open ended. For
simplicity and keeping it within SAS, I liked a system where we structured
the macros.
1) Single line comment before macro stating general purpose.
2) One parameter per line with short clarifying comment.
3) Closing MACRO statement with ";" on a separate line.
4) Immediately followed by PL1 comment block with closing */ on a
separate line.
The user called a macro %DOC and could specify LIBRARY (with default
company autolib), MACRO (member in library), level of documentation (short
- one line comment plus %MACRO statement, or long - added initial comment
block.
If I had been writing for UNIX users I might have called the macro MAN
instead of DOC.
Now let's turn to your specific example of %PRINTZ. The only advantages I
see to your
%macro printz(x)/parmbuff;
are
1) It meets your help philosophy.
2) It allows the user to write a comma separated list without hiding
the commas from the macro facility.
It is not clear to me why a comma separate list should be important. SAS
is generally not a comma separated language with the exceptions of function
and macro calls, SQL. Perhaps it is a question of view, but I see the list
as one argument, and particularly do not want to hide this fact by making
appear as a set of arguments.
Something can be said for simplicity, but I think the main design goals
should be
1) functionality
2) expandability
3) global readability - how does the macro accomplish it purpose
4) local readability - what does this line mean?
5) usability
I tend to think the order given is the order of priority, but in fact these
are always competing goals and in any particular case one should be willing
to give up something from one goal to obtain something considered more
important in another because of the particular situation.
In a one positional parameter macro you don't have to remember anything
except what kind of a parameter value is expected. But what happens when
you want to expand the functionality of the macro? For example, it would
be desirable to have an OBS limit for PRINTZ. Do you want to lock the user
into requiring OBS come first or last? Since I see macro development as a
growth of functionality which usually requires adding parameters for more
detailed specification, I would want OBS to be a keyword parameter. That
is largely why I see positional parameters as play. Consider
%Q(A,B,C,D)
Is that a phony list using PARMBUFF? Is B the output data set name or
input ID variable? Is D a keyword for deleting a certain type of record
or something else? I consider it neither easy to remember the order nor
to read this code once I have looked up what goes where. Consequently, I
see positional parameters as buying simplicity and locking you into stunted
growth for the life of the macro. I cannot say
proc print w ;
why should I not have to name the data parameter in macro call?
Well we could decide that all subsequent parameters for %PRINTZ must be
keyword parameters. So what is the consequence? Now we have to identify
the end of the list by anything containing an =-sign. But I would like
the ability to drop or keep variables for a particular data set and W(DROP=
X Y) will cause problems. This causes problems with the way we decided
to end the list, and the use of space as a separator, and the use of
parentheses as separators. In fact if we plan on having any significant
growth for this macro we need parameters, SEP= to provide the list
separator, and ENDER= to provide the list ender. The problem was that we
tried to have a list without adequate machinery to handle a list in the
face of complexity.
Here is my suggestion.
/* print list of data sets */
%macro printz( x /* list of data sets */
, obs=20 /* obs limit null is max */
, sep=%str(, ) /* list of list separators */
, ender== /* end list indicator */
) /parmbuff
;
/* printz - print list of data sets
parameters:
x - positional required unless help requested
name and type inherited from Lei
obs - default 20, null means no limit
sep - default space and comma inherited from Lei
ender = default = inherited when OBS added
usage:
%printz(lib.w1 w2) - print two data sets 20 obs
%printz(lib.w(keep=id obs =5)/w/#/,sep=/,ender=#)
note need for final separator after ender
*/
%local num dsname ;
%if %length(&syspbuff)= 0 or %length(&syspbuff)=2 %then %do;
/* Print help */
%put;
%put Help on Macro %nrstr(%%Printz):;
%put ...;
%put End of Help;
%end; %else %do;
/* Main */
%let num=1;
%let syspbuff = %substr(%superq(syspbuff),2,%length(&syspbuff)-2) ;
%let dsname=%scan(%superq(syspbuff),&num, &sep);
%do %while(%length(&dsname) and not(%index(&dsname,&ender)));
%if %length(&obs) > 0 and %index(&dsname, %str(%()) = 0 %then
%let dsname = &dsname(obs=&obs) ;
proc print data=&dsname width=minimum;
run;
%let num=%eval(&num+1);
%let dsname=%scan(%superq(syspbuff),&num, &sep);
%end;
%end;
%mend;
data w1 w2 w3 ; retain x y 3 ; do obs = 1 to 50 ; output ; end ; run ;
%printz()
%printz( w1 , work.w2 )
%printz(w1, w2 ,obs=2)
%printz(w1, work.w3(keep=x obs=4),*,sep=%str(,),ender=*,obs=)
%printz(work.w1/w2/w3/#/,obs=4,sep=/,ender=#)
Quoting quiz: Lei did not use quoting on &SYSPBUFF when scanning it, while
my version required it. Why? What obstacle removal caused
the need for this quoting?
In conclusion, I have managed to add my desired features without losing
the macros original philosophy. However, I think it would have been
easier and in the long run better to have simply started with the list
concept and admited that lists need a separator and ender. Moreover, for
flexability, those elements should be in the hands of the user.
Is this version of the macro more difficult to use? Well, yes, if you need
its features, but, no, if don't. On the other hand, part of that
difficulty in handling new features comes from the need to unlearn the
original simplicity. So maybe simplicity shouldn't be desired for complex
task requirements.
Hopefully this exercise has helped someone to see how early design
decisions may lead one to build machinery that cannot last over a long
period of time and active growth. In general, be very careful to get real
value for simplicty when limiting usage by stunting growth. Saving a few
keystrokes or looking familiar when it isn't, doesn't qualify.
Ian Whitlock
================
Date: Fri, 9 Dec 2005 13:20:36 -0800
Reply-To: lzhang9830@YAHOO.COM
Sender: "SAS(r) Discussion"
From: Lei Zhang <lzhang9830@YAHOO.COM>
Organization: http://groups.google.com
Subject: Re: MAcro Design was (Re: Macro quoting essentials)
Comments: To: sas-l
In-Reply-To: <BAY101-F10DEF612E041FFF7F7E28ADE450@phx.gbl>
Content-Type: text/plain; charset="iso-8859-1"
Hi Toby,
Attached is the example macro with online help functionality that
I purpose.
The macro is modified from your %printz by adding pbuff option. To get
onlne help,
a macro user can simply type the macro name and run it. He/she don't
have to
remenber or type any other extra parameters for help. For example,
type
%Printz;
the macro show the usage as follows:
Help on Macro %Printz:
...
End of Help
A user can also print one or more datasets with its normal parameters:
%Printz(sashelp.class sashelp.vstable);
I will appreciate if you have other approach without using vararg
feature. Thanks.
The source code is listed as follows for your quick review:
%macro printz(x)/parmbuff;
%if %length(&syspbuff)= 0 or %length(&syspbuff)=2 %then %do;
/* Print help */
%put;
%put Help on Macro %nrstr(%%Printz):;
%put ...;
%put End of Help;
%end; %else %do;
/* Main */
%let num=1;
%let dsname=%scan(&syspbuff,&num, %str(%(%), ));
%do %while(%length(&dsname));
proc print data=&dsname width=minimum;
run;
%let num=%eval(&num+1);
%let dsname=%scan(&syspbuff,&num, %str(%(%), ));
%end;
%end;
%mend;
|