Dragging REXX Into The 21st Century?
Автор: Martin Packer
Источник: IBM developerWorks
I like REXX but sometimes it leaves a little to be desired. This post is about a technique for dealing with some of the issues. I present it in the hope some of you will find it worth building on, or using directly.
Note: I’m talking about Classic REXX and not Open Object REXX.
List Comprehensions are widespread in modern programming languages - because they express concisely otherwise verbose concepts - such as looping.
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
/* roots is now [1, 2, 3], numbers is still [1, 4, 9] */
Essentially it applies the square root function (
Math.sqrt) to each element of the array
numbers, using the
map method. Even though it processes every element there’s no loop in sight. This, to me, is quite elegant and very maintainable. It gets rid of a lot of looping cruft that adds no value.
I have a lot of REXX code - essential to fetch data from the performance databases I build and turn it into graphs and tabular reports. Much of this code iterates over stem variables (similar to arrays - for the non-REXX reader) or character strings that are tokens separated by spaces (blanks).
An example of a blank-delimited token string is:
address_spaces="CICSIP01 CICSIP02 CICSPA CICXYZ DB1ADBM1 MQ1AMSTR MQ1ACHIN"
It would be really nice when processing such a string - perhaps to pick up all the tokens beginning “CICS” - to be able to do it simply. Perhaps an incantation like:
In this example the
filter routine applies the
find routine to each token in the string, with a parameter
"CICS" (the search argument).
And not a loop in sight.
I implemented versions of map, filter and reduce. I’ll talk about how but first here’s what they do:
|map||Applies a routine to each element|
|filter||Creates a subset of the string with each element being kept or discarded based on the routine’s return value (1 to keep and 0 to throw the item away)|
|reduce||Produce a result based on an initial value and applying a routine to each element.|
Here’s a simple version of filter:
parse arg f,p1,p2,p3,list
if list="" then do
parse arg f,p1,p2,list
if list="" then do
parse arg f,p1,list
if list="" then do
parse value list with item list
if keepit=1 then do
if outlist="" then do
if list="" then leave
Variable “list” is the input space-separated list. “outlist” is the output list that filter builds - in the same space-separated list format.
Much of this is in fact parameter handling: The p1, p2, p3 optional parameters need checking for. But the “heavy lifting” comes in three parts:
Breaking the string into tokens (or items, if you prefer).
interpretto invoke the filter function (named in variable f) against each token.
Checking the value of the keepit variable on return from the filter function:
If it’s 1 then keep the item. If not then remove it from the list.
I also wrote a filter called “grepFilter” (amongst others). Recall the example above where I wanted to find the string “CICS” at the beginning of a token. That could’ve been done with a filter that checked for
pos("CICS",item)=1. That’s obviously a very simple case. grepFilter, as the name suggests, uses grep against each token. It worked nicely (though I suggest it fails my long-standing “minimise the transitions between REXX and Unix through BPXWUNIX” test).
And then I got playing with examples, including “pipelining” - from, say, map to filter to reduce - such as:
say reduce("sum",0,filter("gt",8,map("timesit",2,"1 2 3 4 5 6")))
There are a number of issues with this approach:
You’ll notice the function name (first parameter in the
filterexample above) is in fact a character string.
It’s not a function reference as other languages would see it. REXX doesn’t have a first class function data type. Suppose you didn’t have a procedure of that name in your code: You’d get some weird error messages at run time. And while you can pass around character strings all you want the semantics are different from passing around function references.
The vital piece of REXX that makes this technique possible is the
It’s very powerful but comes at a bit of a cost: When the REXX interpreter starts it tokenises the REXX exec - for performance reasons. It can’t tokenise the string passed to
interpret. So performance could suffer. For my use cases most of the time (and CPU time) is spent in commands (scripted by REXX) rather than in the REXX code itself. (I also think the process of mapping a function to a list suffers less than the average REXX instruction if run through
The requirement to write, for examplesay reduce("sum",0,filter("gt",8,map("timesit",2,"1 2 3 4 5 6")))
say "1 2 3 4 5 6".map("timesit",2).filter("gt",8).reduce("sum",0)
is inelegant. Fixing this would require subverting a major portion of what REXX is. And that’s not what I’m trying to do.
The need to apply a function to each item - particularly in the filter case - can be overkill.
In my Production code I can write
filter("item>8","1 2 4 8 16 32")
as I check the first parameter for characters such as “>” and “=”. So no filtering function required.
REXX doesn’t have anonymous functions and I can’t think of a way to simulate them. Can you? If you look at the linked Wikipedia entry it shows how expressive they can be.
These are worth thinking about but not - I would submit - show stoppers. They just require care in using these techniques and sensible expectations.
It’s perfectly possible to do some modern things in REXX - if you work at it. And this post has been the result of experimentation. Experimentation which I’m going to use directly in some of my programs. (In fact I’ve taken the prototype code and extended it for Production. I’ve kept it “simple” here.)
I’d note that “CMS Pipelines” would do some of this - but not all. And in any case most people don’t have CMS Pipelines - whether on VM or ported to TSO. (TSO is my case, but mostly in batch.)
I don’t believe “Classic” REXX to be under active development so asking for new features is probably a waste of time. Hence my tack of simulating them, and living with the limitations of the simulation: It still makes for clearer, more maintainable code.
Care to try to simulate other modern language features? Lambda or Currying would be pretty similar.
Of course if I had kept my blinkers on then I wouldn’t know about all these programming concepts and wouldn’t be trying to apply them to REXX. But where’s the fun in that?