Rexx is a language from the olden days, vaguely similar to Python, or Tcl, but doing a lot of things in a weird way because back then people didn't know better.
Rexx saw modest use back in the 1980s, and while it never got big, it still survives somewhat in the IBM mainframe world. You can install an Open Source Rexx interpreter with brew install regina-rexx
.
Hello, World!
I'm a bit surprised it uses say
for print
, I thought it's a new thing Raku did, but apparently it has much deeper roots.
#!/usr/bin/env rexx say "Hello, World!"
$ ./hello.rexx Hello, World!
Variables
Rexx is case-insensitive. Variables by default contain their name, in upper case, so variable world
starts with WORLD
.
#!/usr/bin/env rexx a = 40 b = 19 say a + b * 20 say A B C say "Hello, " || world || "!"
./variables.rexx 420 40 19 C Hello, WORLD!
This is definitely not the direction where the world of programming ended up going. ||
is string concatenation.
Types
Everything is a string. If we use a string in numeric context, and it looks like a number, it's treated as a number. Otherwise, it raises exception:
#!/usr/bin/env rexx say "2" + "67" say "hello" + "world"
$ ./types.rexx 69 4 +++ say "hello" + "world" Error 41 running "types.rexx", line 4: Bad arithmetic conversion
FizzBuzz
In a baffling reversal of the usual convention, /
is float division, %
is integer division, and //
is modulo.
#!/usr/bin/env rexx do i = 1 to 100 if i // 15 == 0 then say "FizzBuzz" else if i // 5 == 0 then say "Buzz" else if i // 3 == 0 then say "Fizz" else say i end
Fibonacci
Let's do an easy Fibonacci sequence:
#!/usr/bin/env rexx do i = 1 to 20 say "fib(" || i || ")=" || fib(i) end fib: parse arg n if n <= 2 then return 1 else return fib(n - 1) + fib(n - 2)
Something's not right here...
$ ./fib.rexx fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=4 fib(6)=5 fib(7)=6 fib(8)=7 fib(9)=8 fib(10)=9 fib(11)=10 fib(12)=11 fib(13)=12 fib(14)=13 fib(15)=14 fib(16)=15 fib(17)=16 fib(18)=17 fib(19)=18 fib(20)=19
Rexx by default makes everything global, so that n
is getting overwritten by recursive calls, and crazy things happen.
Let's do a second try:
$ ./fib2.rexx fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=5 fib(6)=8 fib(7)=13 fib(8)=21 fib(9)=34 fib(10)=55 fib(11)=89 fib(12)=144 fib(13)=233 fib(14)=377 fib(15)=610 fib(16)=987 fib(17)=1597 fib(18)=2584 fib(19)=4181 fib(20)=6765 7 +++ procedure Error 17 running "fib2.rexx", line 7: Unexpected PROCEDURE Error 17.1: PROCEDURE is valid only when it is the first instruction executed after an internal CALL or function invocation
That was closer, but why did it suddenly crash? Well, what if we add an exit
?
#!/usr/bin/env rexx do i = 1 to 20 say "fib(" || i || ")=" || fib(i) end exit fib: procedure parse arg n if n <= 2 then return 1 else return fib(n - 1) + fib(n - 2)
$ ./fib3.rexx fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=5 fib(6)=8 fib(7)=13 fib(8)=21 fib(9)=34 fib(10)=55 fib(11)=89 fib(12)=144 fib(13)=233 fib(14)=377 fib(15)=610 fib(16)=987 fib(17)=1597 fib(18)=2584 fib(19)=4181 fib(20)=6765
Now it works. There's also a few different versions of parse arg
with somewhat different semantics.
Of course, we can work with all this, but it's annoying such a basic thing is already causing issues.
Arrays and Hashes
There aren't any. But we get a weird alternative instead - composite variables.
Let's try to implement very simple records this way:
#!/usr/bin/env rexx a.name = "Alice" a.surname = "Smith" a.age = 25 b.name = "Bob" b.surname = "Nilson" b.age = 30 c.name = "Charlie" call print_person a call print_person "B" call print_person c exit print_person: person = arg(1) name_ = value(person || ".name") surname_ = value(person || ".surname") age_ = value(person || ".age") say name_ || " " || surname_ || " is " || age_ || " years old"
$ ./person.rexx Alice Smith is 25 years old Bob Nilson is 30 years old Charlie C.SURNAME is C.AGE years old
There's a lot going on here:
- there's separate syntax for function calls which must return values (
foo(a,b)
) - and procedure calls which don't (call foo a b
) - we cannot actually pass composite variable as argument, what we're passing is their names
A
,B
,C
- value of
a
(by defaultA
) is completely unrelated to what's ina.name
etc. -
call print_person b
is justcall print_person "B"
- as that's the default value - we cannot mark
print_person
asprocedure
, because it needs full access to variables in the caller - to get actual values we need
value
orinterpret
- that is essentially aneval
- default fields are uppercase of the whole thing so Charlie's default surname is
C.SURNAME
not even justSURNAME
- because
print_person
doesn't have its own local variables (name_
etc. are all global) we need to be really careful here
Compared to languages which came after, this is all unbelievably primitive. I don't know if any other language tries to take this "composite variables" concept somewhere good, this definitely isn't it.
Composite Arrays
OK, so given all the limitations of composite arrays, how do we even do anything with them?
The first problem is that we cannot even know the length, and we cannot iterate to see when array ends as accessing beyond its end will give us prefilled ARRAY.7
, ARRAY.8
etc.
So by convention we put the length of the array in ARRAY.0
, and then its elements in ARRAY.1
, ARRAY.2
, etc. You can program this way, but it is quite ugly:
#!/usr/bin/env rexx a.0 = 20 do i = 1 to 20 a.i = 2 * i end b.0 = 3 do i = 1 to 3 b.i = 21 + i end say sum(a) say sum(b) exit sum: array_name = arg(1) array_size = value(array_name || ".0") result = 0 do i = 1 to array_size result = result + value(array_name || "." || i) end return result
$ ./sum.rexx 420 69
Should you use Rexx?
Not unless you're a time traveler who needs to go back to the 1980s to code on an IBM mainframe. Even then, I'm not sure it was such a great choice.
Rexx seems like a dead-end language. I don't think it had any influence on the languages which followed it.
Code
All code examples for the series will be in this repository.
Top comments (1)
As a junior dev, I was tasked with working with a mainframe codebase where I needed to write some code to process some data and I could choose the language to use. The choices were: a COBOL-variant, JCL, Eastytrieve, and Rexx; Rexx was the easiest to use. I am glad that I do not have to use any of those now, but given the circumstances, it was better than I anticipated