100 Languages Speedrun: Episode 98: Rexx
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.