Académique Documents
Professionnel Documents
Culture Documents
Contents
1. Introduction
1. Changes made to the FAQ at this release
2. To Do
2. Using Rexx.
1. How do I Run My Rexx Exec.
2. How do I allocate Exec libraries and other datasets to my TSO or
ISPF session.
3. Where can I find Rexx manuals and documentation.
4. How do I find or access the current level of a GDG.
5. How do I access data in control blocks such as jobname.
6. How can I access a calling execs variables.
7. How do I implement job control with (and for) Rexx
8. Where are the Standard Rexx I/O functions.
9. How do I replicate CLIST's WRITENR functionality.
10. Why does Outtrap() not trap all output.
11. EXECIO, why does it require 'Enter' more than once.
12. How do I List Datasets and PDS members.
13. How do I access data held on the JES spool.
14. What do unusual return codes such as -3 and 0196 mean.
15. How do I pass parms to my ISPF Edit macro.
16. Why are my procedure arguments in upper case.
3. Rexx and Different environments.
1. OS/390 Unix Systems Services
2. ISPF Services
3. ISPF Services in Batch
4. Load Modules (I.e. Assembled programs)
5. SQL
6. Console Command Environment
7. Rexx aware editors
8. Rexx Compilers
9. Using Rexx as CGI programs with the Web Server.
4. Style.
1. What are the most effective ways to use comments?
2. EXECIO.
3. What case should I program in, upper or lower?
4. Functions and procedures
5. Logical (Boolean) variables and functions.
6. Symbolic names.
7. Internal and external calls
8. How can I improve performance of my exec?
9. How can my systems programmer improve performance?
10. What obscure coding tricks are there?
11. Using Non-Alphanumeric characters.
12. Are there any other style hints?
5. Appendices.
1. Code Examples
2. Other Sources of Information
3. Subscribing to the TSO-REXX List
4. Glossary
Introduction
This FAQ is for REXX/MVS, that is, REXX for IBM mainframes (MVS, OS/390
and VM). It is unofficial and based on questions asked in the TSO-REXX listserv
group based at LISTSERV@VM.MARIST.EDU.
As is the case with all FAQs, this document is a work in progress. Additions,
corrections, and comments are very welcome. Please send any correspondence to
webspace@neilhancock.co.uk
If this FAQ or the List Archivedoes not answer your Rexx questions, do not email
me, but instead subscribe to the TSO-REXX List where your questions will be
usually be answered promptly by many knowlegable people.
• 1.4q - Update links and whatnot, add EXECIO tip, add PARSE ARG.
• 1.4p - New coding trick. Assorted submissions and updates (Thanks!).
• 1.4o - Assorted minor things from the list, edit macro parms.
• 1.4n - R.I.P. Deja. ReMarQ too. Code examples moved external to FAQ.
• 1.4m - Performance updated. Coding tricks added.
• 1.4l - LIBDEF/ALLOC/ALTLIB question and sample.
• 1.4k - Subscription Info added.
• 1.4j - Rexx in Batch added. More info added to. RXSQL noted. Docs
question added.
• 1.4i - More reformatting, Some answers reviewed and amended.
• 1.4h - Minor formatting.
• 1.4g - Corrections submitted by Peter Tillier. Reinstated rexx information
sources
To Do
Using Rexx.
Most of this is described in the User Guide, or in the guide for the appropriate
environment.
Use the Storage() function to extract the data from control blocks.
Rexx doesn't allow an exec to access the symbols used by another exec. A classic
example of this is creating an exec to sort the contents of a stemmed symbol.
The obvious answer is to use JCL condition code processing, possibly running a
Rexx exec at the end of each job to perform error recovery, message issuing, etc.
See the ISPF in batch discussion for more information.
SDSF offers a batch interface. I don't have the documentation here at home, but it
can be found as a whole chapter in the SDSF Users Guide (Chapter 15 seems to
stick out in my head). I can't swear that you can "interrogate" sysout data or
condition codes from this interface, so you will need to validate this also. If you
can, then you can build a set of REXX to call this interface from within a TSO
environment.
HOWEVER, you must still consider how you will implement this facility. Let's say
that you want to monitor the majority of your batch work. You must "schedule"
this routine to run after every single batch task, inputting the jobname and JES
number for the SDd. This is not an easy task from within CA-7...
So, to make a long answer even longer, I would recommend that you get away
from looking at the SDSF data, and possibly try to exploit one of the following:
- Implement the SHARE sample of an IEFACTRT exit that generates WTOs with
step condition code results, and use your local automation package to intercept the
WTO and perform an automation process.
- See if you can exploit CA-11 or CA-7 as far as a user exit point or extracting
their database info, and take actions based on that
- Implement your own IEFACTRT exit (there are a lot of samples out there) to
perform the actions you need (yeah, I know, I'd rather do it in REXX also)
- Get with your CA rep and see if CA-7 will be incorporating any of the REXX
technology that CA-JOBTRAC currently uses and possibly a time frame on the
availability
Linein(), Lineout(), Stream() etc. have not been implemented in MVS/Rexx *as far
as I am aware*. I/O can be done using the functionality of EXECIO.
A function package giving I/O functions for the OE environment only is available
from IBM's Download website.
The Rexx say command places a carriage return and line feed at the end of each
say command execution.
Some suggestions for creating a say command that behaves in the same way as
CLIST's WRITENR are:
The free utility XWRITENR (and also XPROC) are available from
ftp.mackinney.com and the CBT tape
The Outtrap() function does not trap output written directly to the screen by the
TPUT macro, but only traps output from the PUTLINE macro. Use PUTLINE
when writing assembler programs, and your program will be of more use in a Rexx
environment.
TSO Session Manager can be used to trap and manage all output that is normally
written to the screen. Session Manager may require some setting up by your
Systems Programer, who will have to look things up in the TSO manuals.
When using "EXECIO * DISKW DDNAME", Rexx will continue pulling input
from the stack and from terminal input (depending on the users TSO PROMPT()
value) until it gets a null line. So a users will have to hit enter once to generate a
null line.
Solutions are:
There are various methods for listing datasets and PDS members.
JES3 sites can extract data off of the JES3 spool using E-JES.
The TSO STATUS command gives limited information about jobs on the spool.
The TSO OUTPUT command can reteive some data from the spool. The TSO
CANCEL command can be used to stop jobs, and TSO SUBMIT to them.
Brave souls can try the JES interface macros. Try looking at the CBTTape for
examples.
JES2 users can brave the perils of SDSF in batch or other third party products
Basically they are either decimalised abend codes or indicate a problem with the
environment.
E.g. 0196 is decimal for hex 0C4, -3 indicates external environment not found.
This is explained further in the Rexx manuals.
Also note that the command line on most panels has CAPS(ON) set. Notable
execptions (where case is preserved)
are ISPF edit and ISPF 6.
Rexx and Different environments.
address SYSCALL and address SH give you the ability to run execs that use
OpenEdition MVS services. From outside of the shell use the Syscall() function to
activate the environment.
See Manual 'Using REXX to Access Unix System Services.' (or whatever its called
this week).
• Command return codes are not exactly passed via the usual rc symbol, but
through new symbols retval and errno.
• The syscall environment initialises __argv. and __environment. stems.
ISPF Services
address ISPEXEC is detailed in the REXX and ISPF manuals. Examples can
be found using the MODELcommand in the ISPF editor.
There is a known problem in ISPF table services, where a freed table may not be
truly free (its ISPF trying to be eficient), causing the TSO FREE to fail. To avoid
the problem, open a fake table, e.g. ADDRESS ISPEXEC "TBOPEN FAKETAB
LIBRARY(FAKELIB) NOWRITE"
ISPF services can be used by a batch job, although panel display and user
interaction may be a little limited. Use ISPSTART running under IKJEFT01 to
initiate the ISPF environment, see the manuals for this.
There can be some difficulties when ISPF return codes >=8 cause ISPF to
terminate. Firstly, the termination will pass a zero RC back to the batch job, most
terminations can be controlled using ISPEXEC CONTROL, see below for an
example. Secondly, the ISPF RC has to be passed back via profile variable
ZISPFRC, as the Rexx RC is ignored by ISPF.
"LMFREE DATAID(EEYORE)"
"EDIT DATASET('NWH.GENERAL.EXEC(ISPFTEST)')
MACRO(MCGYVER)"
say 'EDIT rc='rc
/* Pass the current value of rc back to job control */
zispfrc = rc
exit 12
Creating and accessing Rexx external functions using compilers and assembler is
also documented in the Rexx manuals. Examples of using the IRXEXCOM
interface are on the CBTTape.
It is well worth understanding APF authorisation for load modules. Read the
manuals for full details, but briefly, APF authority abends are usually a result of
the loss of an authorised environment caused by 'dirty' programs in the user
address space or non APF authorised libraries in the STEPLIB concatenation.
Some items to look out for are:
SQL
DB2 for zOS has a Rexx interface, introduced with DB2 version 5, and described
in the latest IBM DB2 manuals. The interface supplies the DSNREXX
environment (as in ADDRESS DSNREXX "EXECSQL SELECT FROM
yadda.yadda").
ISPF edit supplies a color hilite facility that is a must if it is available. ISPF edit
also supplies models for ISPEXEC services, although you may have to adjust the
style to your own. Many *nix editors understand Rexx, and Kate (part of KDE)
uses a simple XML markup that allows highlighting of any language.
Rexx Compilers
There are Rexx compilers available, including one from IBM. Ask on the
newsgroup for Information on what is currently available.
A freeware utility that converts Rexx source into a tokenised form (as is used by
LLA / VLF) is rumoured to exist.
Style.
These style hints are mostly aimed at programmers from other MVS and TSO
languages, especially CLIST. Your site may have its own conventions and these
should take precedence over anything said here. You may also prefer to work in
your own style, and that too is perfectly acceptable.
• You must have REXX as part of the first line of Rexx, which must also be a
comment. It seems sensible to take the opportunity to say something about
what the program is about in this opening comment.
• Only open and close a comment that goes across many lines once. There are
no real performance benefits in this, only your code will contain fewer
extraneous characters, and this will help you see the wood for the trees.
• It makes little sense to comment every line no matter how trivial. Rexx isn't
assembler and most commands are self evident.
• Consider commenting what is being terminated at an END statement,
particularly select statements and do loops that cover more than one
screenfull of program.
• Don't squeeze comments down the right hand side of the code.
This restricts the insertion of maintenance code, and more importantly, the
restricted space causes the comments to become abbreviated past the point
of becoming cryptic.
EXECIO.
If a call returns a value that is not a return code, it may be better to call it as a
function rather than a procedure.
Functions may return logical values, but don't confuse functions returning logical
values with those that return numeric values.
Symbolic names.
Symbolic (or variable) names are not limited to eight characters as in some other
languages, this allows more scope for meaningful names. The names used will
depend largely on the programmers preferences and style of code writing, but here
are some suggestions to consider.
Use i,j,k,etc as loop control symbols. Execs that do not hide symbols using the
procedure command may suffer from reuse of loop control symbols.
E.g. do i = 1 to 10
Prefix variable names with specific characters to show the type of data the variable
contains, such as logical, numeric, flags, etc.
I often prefix variable names with '?' to stop variable substitution occurring if
stemmed variables.
E.g.
count = 2 ; year = '1945' ; stem.count.?year = year /*
STEM.2.?YEAR = '1945' */
Bboolean = 1; if Bboolean then say 'hello'
Fflag = 'A'; if Fflag = 'A' then say 'hello'
@address = STORAGE(something)
#numeric = 10 + 12
Some aspects of an exec may be relevant to other execs, and you may wish to
supply a routine that is usable by other execs performing similar tasks. Consider
creating external routines that supply a robust and generic service (e.g. catalogue
listing, mail sending, interfaces to data, etc.) so that other developers may benefit
from reduced development time.
Exit external routines with RETURN rather than EXIT. This facilitates the copying
of the routine into an exec for performance benefits.
Don't use an external call to a routine that is called recursively, the resulting I/O
will drop the exec performance.
The inability to share variables across external procedures may lead you to choose
to use internal procedures over external ones .
Address external environments directly using ADDRESS. For multiple calls to the
same environment set the environment before entering into the multiple calls.
E.g. address TSO "ALLOC FI(...) DA(....) SHR"
Internalise external routines that are called repeatedly. Then they won't have to be
loaded and tokenised by the interpreter every time they are called.
Use single letter vars for loop control. TSO/Rexx has some fast-path optimisation
for code such as DO I = 1 to 1000.
Using the string functions ( POS(), LEFT(), etc ) on a single very large string,
rather than searching through many stemmed vars.
Embed function calls within each other rather than build up a variable over several
lines of code.
E.g. junk = Left(Overlay('A',Userid()||
Random(999),1,1),8)
Use a compiler. Expect 100% improvement for number maniplulation (adding and
whatnot), 30% for string manipulation (using the string functions), and no real
improvement when interfacing to external environments.
Performance test your code using the SYSVAR('SYSCPU') function and not the
TIME() function.
Use VLF. See IKJEXEC in Init and Tuning manual. Note that VLF is not used for
external procedure and function calls, only the EXEC interface, like what is used
when you TSO EXEC MYLIB(MYEXEC).
Use the environment tables in SAMPLIB if you have environments that are not
part of the distribution tables.
Supply well written external routines for non trivial tasks performed by user code.
This may deter users from writing inefficient code to perform the task themselves.
These tricks are not necessarily recommended, as they can reduce the readability of
the code. They can also speed things up, particularly those tricks using PARSE.
Use functions such as DATE() to check if some data conforms to a type. Use ON
SYNTAX to trap the error.
junk = DATE(,testDate,'S') /* Will give a syntax error
is testDate is not a standard date */
The many variations of DO. Read the Rexx Reference and Rexx Guide for
inspiration.
Use PARSE to increment a stem counter and assign a value to the new last stem
(Attributed to Les Koehler @ IBM Tampa).
stem.0 = 1 ; stem.1 = 'cheese'
• Leave space at the end of line for continuations and short 'date changed'
comments.
• Avoid ambiguous or confusing abbreviations in symbol names and
comments.
E.g. 'lst' and '1st' appear similar, and will a casual reader know whether a
symbol 'lstPointer' is referring to a 'list pointer' or a 'last pointer'
• See the Style Guide for a more detailed style discussion
Appendices.
Code Examples
or TSO in batch:
//IRXTSO EXEC PGM=IKJEFT01
//* You could use PARM='%execname testparms' on exec
statement
//SYSEXEC DD DISP=SHR,DSN=your.exec.library
//SYSTSPRT DD SYSOUT = *
//SYSTSIN DD *
%execname testparms
/*
//* You could run more than one exec sequentially from
SYSTSIN
//* You can also run straight TSO commands like TSO
PROF PREF(FRED)
This Exec shows how to temporarily LIBDEF ISPF datasets to your session,
ALTLIB an Exec library to your session, call an ISPF application (under a new
application Id to avoid name collisions with other ISPF profile var. names), and
then unstack the LIBDEFs, and deactivate the alternative Exec library.
ISPF has its own active listserv at ISPF-L@listserv.nd.edu, Send message text
'SUBSCRIBE ISPF-L Your Name' to LISTSERV@listserv.nd.edu and await
further instructions.
Go to the TSO-Rexx list archive, scroll to the bottom of the page, and follow the
instructions there.
Glossary
Introduction.
This is a style guide for the Rexx programming language, primarily aimed at Rexx
in the OS/390 environment, but application to Rexx on other platforms, and to
variants of Rexx such as NetRexx. Many of the points here have been raised at
some time on the TSO-REXX listserv mailing list.
Using Comments.
Correctly commenting code is one of the most useful things you can do when
writing code. Most style problems can be repaired with a formatter, but useful
comments cannot be conjured out of thin air.
/**************************************\
* This is a *
* boxed comment *
\**************************************/
The use of asterisks (*) as the box border can make the comment too heavy.
Consider using minus (-) or another lighter character instead. Also, don't worry
about closing the right hand vertical, as realigning the border after text insertion is
a tedious affair. You may have a Rexx reformatter that deals with this problem.
/* Rexx -----------------------------------------
AUTOTOOL
<A one line description for use by an indexing tool>
-------------------------------------------------------
Copyright: <You may want a copyright notice>
Change History:
<yy/mmm/dd Userid VersId Description>
Description:
<A long description of the purpose of the exec.
Include invocation arguments, examples of call
syntax, returned values, etc.>
---------------------------------------------- MEMNAME
*/
It is extremely useful to have a tool to handle the creation of a standard header, and
to handle change history information. Note the marker in the top right of the box
that may be used by a tool to spot whether this is a standard header or not.
An indexing tool that reads the short description and change history is also useful,
so make sure the header format supplies information that would be useful to an
indexing tool.
In the change history, put the most recent changes at the top, so they are visible
when the exec is opened. It may also be useful to have a version id to mark
changed code in small comments later on.
It may not be worth putting some types of information in the header, particularly
data that ages poorly. Dataset names and pointers to external information are
particularly prone to becoming incorrect.
The Rexx ANSI standard talks of Rexx 'symbols'. Here I shall use the more
familiar terms 'variable', 'variable name' and 'variable value'.
Compound variables, stems, and tails refer to the whole and various parts of
compound variables.
Consider using i, j, k, etc as loop control variables. There are advantages and
disadvantages to this:
Faster execution speed. Single character variable names show a performance
improvment (My simple test measured nearly 10% improvement).
Compound variables names are shorter and less likely to make long
statements cross onto multiple lines.
You have to rely on the stem name to indicate the meaning of the data in the
compound variable. E.g. Consider compound var names record.i and
score.teamIndex.eventIndex . It is obvious that i is the record number, but would
it be obvious from score.i.j that i is the team and j is the event?
Global variables can be handled in the following way:
First, in the opening section of your Exec, set a variable named global whose
contents are the stem names of your global variables:
Then, at each procedure start, expose this global, and its contents:
Do use the procedure keyword with every defined procedure. This hides the
procedure's internal variables from the caller, and allows the use of i,j and k as
loop control variables throughout your program without any side effects, as in this
example:
do i = 5 to 10
say 'Number:'i 'Factorial:'Calculate_Factorial(2,i)
end
exit
/* --------------------------------------
Return the multiple (factorial) of all
numbers between arg1 and arg2.
-------------------------------------- */
Calculate_Factorial: procedure
parse arg startNum,endNum
factorial = 1
do i = startNum to endNum
factorial = factorial * i
end
return factorial
This common problem occurs when a variable is assigned a value, and the
variable is then used in a compound variable:
If you don't want this effect, you must use unassigned variable names in the
compound variable. You could use non-alphanumeric characters such as ! or ? to
prefix the variable name when its used in the compund tail, or use a numeric
prefix, as symbols starting with numerics are, by definition, constants.
testVar = 'HELLO'
stem.0testVar = testVar
stem.!testVar = testVar
Large blocks of variable assignations can be split into columns, with variable
names, variable values, and winged comments:
Here we discuss the case we could use for syntactic elements, what indentation is
appropriate, and how we could break blocks of statements up.
But first, What are we trying to achieve, and how do we go about achieving our
aim.
The target audience should be anyone likely to need to change your code in the
future, including yourself. Don't assume any knowledge of the inner workings of
your code, it may be some years before you revisit it. A reasonable knowledge of
Rexx may be assumed, even though the reader may be new to the language.
Everyone has to learn something sometime.
We are also concerned with breaking up the code into manageable and
reusable units. We can do this by using procedures, and by surrounding blocks of
statements that perform a task with white space.
There are some facts about the programming environment that influence what
techniques may be most effective.
External function names are related to file names in a file system, or member
names in partitioned datasets. MYFUNC() may refer to PDS
'user.EXEC(MYFUNC)' or file name MYFUNC.REXX, depending on your Rexx
environment. By using upper case for external function names, you avoid having to
quote function names to maintain lower case in the file name. You then, of course,
must use upper case in the file names of external functions.
Screen size influences how much code can be on the screen at one time.
Using too many blank lines or splitting statements over multiple lines can move
code out of the viewable area. Techniques that disperse statements over several
lines work well on window based systems that allow the viewable area to be
adjusted, but are restricted by the fixed terminal size of 3270 systems. The same is
true for boxed comments.
Some editors are Rexx aware and can hilite Rexx code to show keywords,
quoted strings and other elements. A Rexx aware editor such as ISPF EDIT with
the HILITE utility, can for instance turn all keywords red, and all comments blue.
There's no point in using upper case to highlight keywords, when the editor can
pick them out with colour.
Keywords appear in isolation in the text, and also tend to appear at the start
of statements, so they stand out without any help from the programmer.
Function and variable names tend to appear close together in code. We need
to be able to differentiate variable names from function names, and also
differentiate between the various flavours of function; internal, external, builtin,
etc. For example:
datasetName = STRIP(TRANSLATE(GetDsn(ddName)),,"'")
or
datasetName =
STRIP(TRANSLATE(Get_Dataset_Name(ddName)),,"'")
Literals should be quoted, unless you can be sure that the literal hasn't been
used as a symbol elsewhere.
Here is programming style that meets the above criteria in varying degrees. It tries
to keep the code dense for 3270 displays, and relies heavily on a highlighting
editor to differentiate between keywords, quoted text, variable names, and other
statement elements.
• Keywords in lower case. Let the editor highlight them. E.g. select do if end
• Variable names starting in lower case with the initial letters of concatenated
words in upper case. This differentiates them from function names, which
will all start with an upper case character. E.g. myName counter fishType
• Internal function names with the initial letters of individual words in upper
case. Optionally, you can also use underscore separators E.g. MyFunction()
MyOtherFunction() Your_Function()
• External function names in upper case. This saves having to quote them. E.g.
MYFUNC() EXTFUNC()
• Builtin functions in upper case. This rule is pretty arbitrary, as the
highlighting editor will mark them in a different colour to user supplied
functions anyway. E.g. SUBSTR() D2B() MVSVAR()
• External environment names in upper case. Note that address treats the
environment name as a constant, and does not perform symbolic
substitution. E.g. address TSO "X", address ISPEXEC "FTCLOSE",
address SH "cat myfile"
• Commands for external environments preferably in upper case, unless the
environment is case sensitive. E.g. address TSO "ALLOC FI("ddName")
SHR REUSE DA("dsName")"
• Use i, k, j, etc. as loop control variables. This can make your code run faster,
and helps keep statements short..
• Indent do, select, and other similar structures with between 1 and 3 spaces.
Style Example
/* REXX
--------------------------------------------------------
--
SYNOPSIS : LZ78 compressor/decompressor demo
VERSION : 1.0
CREATED : April 1999
NOTES : From the uberFish Rexx Style Guide.
--------------------------------------------------------
------- */
/* Demo normal text compress/decompress */
say 'Normal text demo.'
inString = 'If_I_were_a_Linesman,_' ,
||
'I_would_execute_the_players_who_applauded_my_offsides.'
compString = Compress_LZ78(inString)
say 'Compressed as.....'
say compString
decompString = Uncompress_LZ78(compString)
say 'Decompressed as.....'
say decompString
compString = Compress_LZ78(inString)
say 'Compressed as.....'
say compString
decompString = Uncompress_LZ78(compString)
say 'Decompressed as.....'
say decompString
exit 0
/**
========================================================
========
Demo LZ78 compressor. In reality, the output would be
formatted
to take up less space then the original, E,g, in 9bit
bytes,
with the high order bit set on when the char is a
dictionary
reference, or something.
Oh, and it doesnt like spaces, cos I use WORDPOS() to
search
the dictionary.
--------------------------------------------------------
--------- */
Compress_LZ78: procedure
parse arg inString
/* Initialise stuff */
outString = ''
d = ''
w = ''
Spot the line that is longer than 80 characters. In ISPF it should be wrapped in
whatever way is consistent with your treatment if IF THEN DO constructs:
There's little point in setting guidelines for writing programs if they're going to be
ignored. You have to make people want to follow your guidelines.
A tools led approach can be combined with other approaches. There is some
development effort to get tools into place, and the tools must be flexible and fit for
their purpose. Generally, a handful of ISPF EDIT macros to perform various
formatting tasks will suffice.
Concatenating Strings.
There are two schools of thought on whether to explicitly use the string
concatenation operator ( || ) or not.
There are some good reasons to use concatenation operators, and some good
reasons not to.
Do make sure abstracted procedures allow full access to the underlying data.
For instance, say you put a nice shell exec around TSO LISTCAT. Your shell
program should be able to control all arguments for LISTCAT, and return any
information returned by LISTCAT to the caller. Otherwise you may have to create
a new interface for your old shell if new LISTCAT data is needed. Adding new
functionality to old code creates backwards compatibility problems, and
maintenance problems as the code becomes 'hairy' from unnecessary revisions.
Don't hide information in variables set far away from where they are used.
Its one thing to have a nice block of commented assignations near the top of an
exec or procedure, but its another thing entirely to place assignation statements in
obscure parts of the exec, far away from where they will actually be used.
Send Feedback
Please drop me a line, and tell me about any improvements you think could
be made to this page.
• Was the information useful?
• Was the information complete and concise?
• Did you understand the information?