Here’s more dabbling in programming languages to re-create my venerable interpretation of Conway’s Game of Life, this time using three stereotypical languages of the IBM galaxy: the Rexx scripting language, good old COBOL, and Fortran 95.
Rexx
You probably never heard of Rexx before; that’s all right, here’s what Wikipedia has to say about it:
Rexx (Restructured Extended Executor) is a programming language that can be interpreted or compiled. It was developed at IBM by Mike Cowlishaw. It is a structured, high-level programming language designed for ease of learning and reading. Proprietary and open source Rexx interpreters exist for a wide range of computing platforms; compilers exist for IBM mainframe computers.
What can you use it for?
Rexx is a full language that can be used as a scripting, macro language, and application development language. It is often used for processing data and text and generating reports; this means that Rexx works well in Common Gateway Interface (CGI) programming and is used for this purpose, like later languages such as Perl. Rexx is the primary scripting language in some operating systems, e.g. OS/2, MVS, VM, AmigaOS, and is also used as an internal macro language in some other software, such as SPF/PC, KEDIT, THE and the ZOC terminal emulator. Additionally, the Rexx language can be used for scripting and macros in any program that uses Windows Scripting Host ActiveX scripting engines languages (e.g. VBScript and JScript) if one of the Rexx engines is installed.
The first time I got in contact with Rexx was in 1994, when I installed OS/2 Warp 3.0 on my PC, almost 30 years ago. I found it to be a simple, easy to understand language, with some interesting features. It was touted, and rightfully so, as a powerful alternative to standard DOS batch files.
These days, the easiest way to run Rexx programs in Linux or other operating systems is through the Regina interpreter, available in Fedora 38 with a simple sudo dnf install regina-rexx
.
The Rexx language itself is quite simple, and it looks at first glance like a hybrid between BASIC and Python, with some outstanding features:
- Rexx is a fully dynamic language, without declarations of any kind.
- All arithmetic number types are floating-point (like Minimal BASIC and JavaScript)
- Rexx does not have arrays or hashes as other languages, but has “compound variables” that work exactly the same way. Array indexes are directly specified next to the variable name, separated with a dot.
- Regina allows Rexx scripts to use a shebang.
- The
say
command always includes a line feed, that’s why we usecall charout ,variable
to write to stdout without one. - Subroutines are simply specified using their name and a colon at the end, like
Evolve:
on like 58, and must have areturn
statement at the end (otherwise the execution continues towards the next subroutine below!) They can have arguments, specified with thearg
statement at the beginning. - Although not used on this example, Rexx has an
interpret
instruction that works exactly like JavaScript’seval()
function.
There’s of course a Visual Studio Code extension for Rexx, which was very helpful while writing this code. Vim has syntax highlighting for Rexx scripts out of the box.
The Rexx version of my Conway project is available on GitLab, and here are the first lines transcribed to satisfy your curiosity:
#!/usr/bin/regina
/* Initialization */
size = 29
separator = "|"
generation = 0
do i = 0 to size
do j = 0 to size
world.i.j = 0
end
end
signal on halt
call Blinker 0, 1
call Beacon 10, 10
call Glider 4, 5
call Block 1, 10
call Block 18, 3
call Tub 6, 1
/* Print grid in an endless loop */
do forever
'clear'
say ""
do a = 0 to size
if a = 0 then call FirstLine
call charout ,format(a, 3)
call charout ,separator
do b = 0 to size
if world.b.a = 1 then call charout ," x |"
else call charout ," |"
end
say ""
end
generation = generation + 1
say ""
say "Generation " generation
call Evolve
sleep(0.5)
end
halt:
'clear'
return
/* First line with coordinates */
FirstLine:
do b = 0 to size
if b = 0 then call charout ," "
call charout ,format(b, 3)
call charout ,separator
end
say ""
return
/* More: https://gitlab.com/akosma/Conway/-/tree/master/Rexx */
COBOL
Do I need to introduce it? Says Wikipedia:
COBOL (/ˈkoʊbɒl, -bɔːl/; an acronym for “common business-oriented language”) is a compiled English-like computer programming language designed for business use. It is an imperative, procedural and, since 2002, object-oriented language. COBOL is primarily used in business, finance, and administrative systems for companies and governments. COBOL is still widely used in applications deployed on mainframe computers, such as large-scale batch and transaction processing jobs. However, due to its declining popularity and the retirement of experienced COBOL programmers, programs are being migrated to new platforms, rewritten in modern languages or replaced with software packages. Most programming in COBOL is now purely to maintain existing applications; however, many large financial institutions were still developing new systems in COBOL as late as 2006.
This is one of the oldest programming languages still in use:
COBOL was designed in 1959 by CODASYL and was partly based on the programming language FLOW-MATIC designed by Grace Hopper. It was created as part of a US Department of Defense effort to create a portable programming language for data processing. It was originally seen as a stopgap, but the Department of Defense promptly forced computer manufacturers to provide it, resulting in its widespread adoption. It was standardized in 1968 and has since been revised four times. Expansions include support for structured and object-oriented programming. The current standard is ISO/IEC 1989:2014.
This is not my first COBOL code; I first got exposure to the language in April 2020, during the pandemic, when news broke that the governor of New Jersey was desperately looking for COBOL programmers. I got curious and then I enrolled in the Master the Mainframe program by IBM, where I learnt a bit about COBOL and JCL.
The Micro Focus COBOL and COBOL debugger Visual Studio Extensions were very useful during the writing of this code. Again, Vim has COBOL syntax highlighting support off-the-box.
The Micro Focus extension fulfills a very important task: it provides visual vertical guides to properly format COBOL code; these tabulation limits were mandatory in old COBOL code, as it replicated the layout of code lines on punched cards. This extension makes it trivial and convenient to quickly navigate those tab stops using the tabulation key on your keyboard:
What else can I say about COBOL?
- Variable types are confusing at first, as they are specified by providing examples of what you want to store inside, leaving the language to decide the best allocation format for them.
- For example, the statement
X PIC 99 VALUE ZERO
asks to create a variable calledX
that stores positive integers of up to two digits (00 to 99) initialized to zero. TheS
prefix is used to specify that a “sign” is required (hence it can store negative numbers) and theZ
prefix prevents leading zeros from being printed on the console.
- For example, the statement
- You don’t assign values using the
variable = value
syntax but theMOVE value TO variable
one. - You don’t
variable += value
butADD value TO variable
. - You don’t
variable -= value
butSUBTRACT value FROM variable
. - The two-dimensional array used to store the “world” of cells is literally defined with the
OCCURS … TIMES
statement. The compiler very conveniently checks whether there are out-of-bound indexes in your code. - This program has no I/O, which means that there’s no
ENVIRONMENT DIVISION
in this program. TheDATA DIVISION
has only aWORKING-STORAGE
section, with all the variables used in the program (all global, of course.) - The code of the program is all contained within the
PROCEDURE DIVISION
, as it should be. - The
PERFORM
keyword is used to call subroutines by name, and also to loop over variables (PERFORM VARYING
) or to create infinite loops (PERFORM FOREVER
). - The
DISPLAY variable WITH NO ADVANCING
statement just outputs the variable without a line feed at the end. - The call
CALL 'SYSTEM' USING 'clear'
is obviously non-standard cheating, and should only work on Linux or macOS.
To compile this code I used GnuCOBOL, easily installed on Fedora 38 using the classic sudo dnf install gnucobol
thingy.
The most complicated part of writing this version of Conway was that the documentation is, at best, scattered and hard to find:
- I have a copy of a very good book about COBOL: “Beginning COBOL for Programmers” by Michael Coughlan (2014), but it features code examples written using the Micro Focus dialect of COBOL, which isn’t entirely compatible with GnuCOBOL. The book in general is, however, excellent, and I can gladly recommend it.
- By the way, the source code of the book is available on GitHub.
- Although the GnuCOBOL project has very good and decent documentation in PDF and HTML format, it works more like a big manual rather than a quick getting started guide, which is what I would have needed.
- There aren’t many questions on Stack Overflow or blog posts to refer to (for obvious reasons!)
I would love to see if this code can run on a mainframe, punching the required cards. A few months ago, on the Vidéothèque section of my magazine De Programmatica Ipsum, I featured a video describing the operation of the wildly successful IBM 1401 computer, which was of course used to execute COBOL programs back in the 1960s.
Here’s then the first lines of the COBOL version of Conway, available on GitLab:
* Conway Game of Life in COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. Conway.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SizeValue PIC 9(2) VALUE 30.
01 X PIC 99 VALUE ZERO.
01 Y PIC 99 VALUE ZERO.
01 A PIC S99 VALUE ZERO.
01 B PIC S99 VALUE ZERO.
01 Temp PIC 99 VALUE ZERO.
01 MinA PIC S99 VALUE ZERO.
01 MinB PIC S99 VALUE ZERO.
01 MaxA PIC S99 VALUE ZERO.
01 MaxB PIC S99 VALUE ZERO.
01 Counter PIC 9 VALUE ZERO.
01 DisplayX PIC Z(2)9 VALUE ZERO.
01 DisplayY PIC Z(2)9 VALUE ZERO.
01 Generation PIC 999 VALUE ZERO.
01 Gen PIC Z(3)9 VALUE ZERO.
01 World.
02 Row OCCURS 30 TIMES.
03 Cell PIC 9 VALUE ZERO OCCURS 30 TIMES.
01 WorldCopy.
02 RowCopy OCCURS 30 TIMES.
03 CellCopy PIC 9 VALUE ZERO OCCURS 30 TIMES.
PROCEDURE DIVISION.
BEGIN.
PERFORM INIT-WORLD
PERFORM FOREVER
CALL 'SYSTEM' USING 'clear'
PERFORM DISPLAY-TABLE
PERFORM DISPLAY-GENERATION
PERFORM EVOLVE
CONTINUE AFTER 0.5 SECONDS
END-PERFORM
EXIT SECTION.
* More: https://gitlab.com/akosma/Conway/-/tree/master/COBOL
Fortran 95
Finally, a version in Fortran, the first time I’ve ever written any Fortran code:
Fortran (/ˈfɔːrtræn/; formerly FORTRAN) is a general-purpose, compiled imperative programming language that is especially suited to numeric computation and scientific computing.
Who created it and what for?
Fortran was originally developed by IBM in the 1950s for scientific and engineering applications, and subsequently came to dominate scientific computing. It has been in use for over seven decades in computationally intensive areas such as numerical weather prediction, finite element analysis, computational fluid dynamics, geophysics, computational physics, crystallography and computational chemistry. It is a popular language for high-performance computing and is used for programs that benchmark and rank the world’s fastest supercomputers.
I used Fortran 95 for my code, compiled with GNU Fortran on Fedora 38.
I found it to be a beautiful language to work with; it’s quite obvious how the designers of BASIC took inspiration from it. It sadly lost a bit of its edge in scientific computing during the past few decades, in favor of Python and its ecosystem. It’s hard to compete against behemoths such as NumPy, Project Jupyter, and SciPy.
But Fortran is still there, still going strong. There’s plenty of very good documentation about the language, lots of blog posts, and quite a thriving community of passionate users.
The Modern Fortran Visual Studio Code extension was extremely useful while writing this code, just like Vim and its built-in Fortran support.
Here’s a fragment of the full application:
program conway
use world
implicit none
integer, dimension(30, 30) :: array
integer :: generation
generation = 0
array = 0
call blinker(array, 0, 1)
call beacon(array, 10, 10)
call glider(array, 4, 5)
call block(array, 1, 10)
call block(array, 18, 3)
call tub(array, 6, 1)
do
generation = generation + 1
call execute_command_line("clear")
call print(array)
write(*, '(A)', advance="no") 'Generation '
write(*, '(I3)') generation
print *, ""
array = evolve(array)
call sleep(1)
end do
end program conway
! More: https://gitlab.com/akosma/Conway/-/tree/master/Fortran