REXX Primes

Just a quick sanity check on performance.

/** primes.rexx */
/* Created 2019-09-26 */
/* Copyright © 2019 by Mark Damon Hughes. All Rights Reserved. */

PARSE ARG primeCount
IF \ DATATYPE(primeCount, "W") THEN DO
    SAY "Usage: primes.rexx N"
    EXIT 1
END

CALL clearPrimes
CALL sievePrimes
CALL printPrimes
EXIT 0

clearPrimes: PROCEDURE EXPOSE primes. primeCount
    primes. = 1
    primes.0 = primeCount
    primes.1 = 0
RETURN

sievePrimes: PROCEDURE EXPOSE primes. primeCount
    DO i = 2 TO primeCount
        DO j = (i * i) TO primeCount BY i
            primes.j = 0
        END
    END
RETURN

printPrimes: PROCEDURE EXPOSE primes. primeCount
    DO i = 1 TO primeCount
        IF primes.i THEN CALL CHAROUT , i || " "
    END
RETURN
# REXX: 0.8% C
% time rexx primes.rexx 1000000 >~/tmp/primes-rexx.txt
rexx primes.rexx 1000000 > ~/tmp/primes-rexx.txt  6.43s user 0.36s system 99% cpu 6.831 total

# Regina: 1.53% C
% time regina primes.rexx 1000000 >~/tmp/primes-regina.txt
regina primes.rexx 1000000 > ~/tmp/primes-regina.txt  3.25s user 0.26s system 99% cpu 3.521 total

# Python: 4.8% C
% time ./primes.py 1000000 >~/tmp/primes-python.txt
./primes.py 1000000 > ~/tmp/primes-python.txt  0.75s user 0.02s system 68% cpu 1.123 total

# Julia: 1.4% C
% time ./primes.jl 1000000 >~/tmp/primes-julia.txt
./primes.jl 1000000 > ~/tmp/primes-julia.txt  0.45s user 0.35s system 21% cpu 3.797 total

Most of REXX's bad time can be attributed to using stem variables in a tight loop, effectively string-keyed hashtables, so I'm sure an ooRexx Array implementation would be significantly faster. But stems are what you'd use in "real code", so caveat coder. In a long real-world program I don't think it's as big an issue, but it's definitely not received the kind of optimization love that newer languages have. Aesthetically, the REXX source is a little wordier and more explicit about globals access, but not hard to write or read.

I went ahead and grabbed Regina, and it doubles the speed of ooRexx. I'm still wary of it, but that's a big win.

Python's not terrible at anything; it's within a stone's throw of a compiled language at this point. Competent mediocrity has really made Python the new dynamic Java of our time. But you still can't do multithreading in it.

Julia's slow because the startup time is just atrocious; if I timed it internally after startup it'd be as fast as a compiled native program, but as a scripting language Julia's bad news.

(I do have real work to do, but I'll keep playing with REXX more over the next few days)

REXX Monarchial Madness is Hereditary

So, I was trying to figure out how to manage global vs. local state, and made the dumbest program possible:

/** array.rexx */

a. = 0
a.0 = 10
DO i = 1 TO a.0; a.i = i; END

SAY array_string("a.") -- prints 1 to 10
SAY alen -- prints "ALEN" because it doesn't exist in global scope

EXIT 0

/* Returns string representation of an array, stem. passed as first arg. */
array_string:
    PARSE ARG _arrayvar .
RETURN _array_string()

_array_string: PROCEDURE EXPOSE (_arrayvar)
    alen = VALUE(_arrayvar || 0)
    str = ""
    DO i = 1 TO alen
        IF i > 1 THEN str = str || ", "
        str = str || VALUE(_arrayvar || i)
    END
RETURN str

So… I've made a global variable "a." full of numbers. PROCEDURE makes a local variable context until you hit RETURN; you should never work at global scope if you can avoid it. So to pass a. in, I have to bounce through a global function array_string(), set a global var _arrayvar to whatever arg was given, which calls a local function _array_string and the paren in EXPOSE (_arrayvar) expands that into "a.", and then use VALUE() to dynamically read the variable.

Well, it works. I remember now how crazy complex REXX programs got, with giant global databases being exposed in every function. I bet this is the origin of my "World object" pattern in so many later languages.

This and another dumb example or two ate a couple hours of my brain, but it kinda makes sense again, you know? Interesting for a language I haven't written in anger in 20 years. I went fully back to SHOUTING KEYWORDS, it just makes sense.

The Python version of this is:

#!/usr/bin/env python3

def array_string(arrayvar):
    return ", ".join(map(str, arrayvar))

a = []
for i in range(1, 11): a.append(i)
print(array_string(a))

Which I'd argue is just as stupid, because Python thinks ranges end 1 before the last number, doesn't know how to join numbers with strings, on a large array that map will consume all memory, and is a single-pass interpreter so I have to put the function above the script code; but it is much, much shorter.

REXX is Still the King

Random REXX mention on Fediverse reminded me of good times with it. REXX was originally a scripting language for the IBM /360 series, later OS/2 where I encountered it. There's three main branches: REXX (original, simple procedural language), ooRexx (strict superset adding OOP tools to REXX), and NetRexx (almost-REXX implemented in Java, so it can interact with the entire Java API).

The things it does well are: Simple notation, like BASIC, but it's structured and dynamic: Variables have their NAME as default value, and it's trivial to evaluate strings as code. Variables can also be "stems", either indexed or associative arrays depending on key. a.=0; a.0=10 and you have an 10-element array of 0's (using .0 as length is common). Any command it doesn't recognize is passed to the outside environment, which might be a command-line shell, another program you're scripting, whatever, no special syntax needed. REXX is preposterously fast for a scripting language, and has perfect decimal math (Cowlishaw's main academic area of interest), though it's still not compiled language fast.

OS/2 made great use of it. The E and E/PM editors written in REXX are all about scripting; like Emacs but far more so, there's almost no program except the E script.

Back in the '90s, I wrote a ton of programs for clients (all dreadfully dull line-of-business stuff) which were thousands of lines of REXX with VT100 UI or a little teeny GUI shell in C it controlled and got messages from; my turnaround on a business change could be hours instead of days or weeks for a C-only program. There's somewhere on FTP sites or in my old code archive my QUEST game, which was just a random D&D wilderness crawl hack-and-slash, written the same way, and a huge script for writing a nice config file to generate the impossibly fussy bit-twiddling world files for DikuMUD and CircleMUD.

Down side, by the late '90s OS/2 was shut down, Mike Cowlishaw started doing everything in NetRexx, so the core language languished, and there weren't good ports to Linux or Mac (either System 9 or early OS X). The open-source "Regina" interpreter was almost but not quite compatible, and was very frustrating to debug around. I completely gave up on it around 1999, when Python got usable.

Seems in the last decade they've somewhat recovered, REXX Language Association, aka ooRexx.org, and NetRexx all have maintained sites, the old RexxInfo site is as hideous as it was in the '90s, and unmaintained now, but does have a free copy of the REXX Programmer's Reference book which is nicely complete.

The RexxLA symposium is going on as we speak. No livestreams, sadly, but last year's presentation decks are up. You might want to start with Rexx Tutorial for Beginners part 1, part 2, and ooRexx Tutorial.

I'd really like to hear "Rexx from OS/2 to macOS - a travel in time & space"!

% sudo port install oorexx
…
% cat hello.rexx
/** hello.rexx */

name = input("What is your name? ")
say "Hello," reverse(name) || "!"
exit

input: procedure
    parse arg prompt
    call charout ,prompt
    return linein()

% rexx hello.rexx
What is your name? Mark
Hello, kraM!

Works fine. The default I/O commands are a little weird so I rewrote that input function almost from muscle memory. PULL var and PUSH x operate on a stack and get uppercased, PARSE PULL var is very powerful and lets you interpolate strings for parsing input (you can also use it on function args, as seen here), but here all I want is a complete line as a function. SAY is a cutesy form of CALL LINEOUT ,whatever, (the missing first arg to CHAROUT or LINEOUT is the output file name/handle). Note the two ways to call a function: y=f(x) or CALL f x depending on if it has a return value. I write everything lowercase, modern REXX is case-insensitive, but historically it was an uppercase-only language, so I always SHOUT THE KEYWORDS when talking about them.

I didn't get far into ooRexx back in the day; I don't think most of its "enhancements" are needed to a nice simple language, but there's places where stems aren't a sufficient abstract data structure and the like.

This looks like fun to play with again, though I wouldn't expect to ship anything useful in it. The one problem is there's no REPL. You can actually make a simple REPL with just DO FOREVER; PARSE PULL line; INTERPRET line; END but there's a lot of error-handling and structured stuff that won't work in that. I think I have a real REPL in my code? I'll go looking in a bit.