Beginner

Variables and Types in REXX

Learn about variables, typeless values, decimal arithmetic, and stem variables in REXX with practical Docker-ready examples

REXX takes a radically different approach to variables and types than most modern languages. There are no type declarations, no reserved keywords for allocating memory, and no “int” vs “string” vs “float” distinctions to memorize. In REXX, every value is a character string, and the language decides how to interpret that string based on the surrounding context.

This design choice, championed by Mike Cowlishaw at IBM in 1979, makes REXX remarkably beginner-friendly while still supporting serious numerical work through arbitrary-precision decimal arithmetic. The same variable can hold a product code one moment, a monetary amount the next, and a customer name after that — no casting, no conversion calls, no compile errors.

REXX is an imperative, procedural, structured language, so the flow of assignment and reassignment will look familiar to anyone coming from Python, Ruby, or Perl. What will surprise you is how the typeless model and the decimal numeric system interact. This tutorial shows how variables come into existence, how REXX decides whether a value is “numeric”, how NUMERIC DIGITS controls precision, how strict (==) and non-strict (=) comparisons differ, and how REXX’s unique stem variables provide associative-array-like storage without ever leaving the typeless model.

Declaring and Using Variables

Variables in REXX spring into existence the moment you assign to them. There is no var, let, dim, or type keyword — just a name, an equals sign, and a value. Every value is stored internally as a character string; REXX converts it to a number only when an arithmetic operator or numeric built-in demands it.

The example below mixes assignments with different apparent types, then interrogates the values using the DATATYPE() built-in, exercises REXX’s arbitrary-precision arithmetic, and contrasts the two flavours of comparison operator.

Create a file named variables.rexx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/* Variables and Types in REXX */

/* Section 1: Basic assignment -- no declarations required */
say "=== Basic Variables ==="
message = "Hello, REXX!"
count   = 42
pi      = 3.14159
flag    = 1                 /* REXX uses 1/0 for true/false */

say "message:" message
say "count:" count
say "pi:" pi
say "flag:" flag
say ""

/* Section 2: Every value is a string until used as a number */
say "=== Typeless Data ==="
x = 100                     /* stored as the 3-char string "100" */
y = "100"                   /* stored as the 3-char string "100" -- identical */
say "x + 5 =" x + 5         /* arithmetic context: treats as number */
say "y + 5 =" y + 5         /* same result */
say "x || 'abc' =" x || "abc"   /* string context: concatenates */
say ""

/* Section 3: DATATYPE() inspects how a value would be interpreted */
say "=== DATATYPE Checks ==="
a = 42
b = "hello"
c = 3.14
d = "2.5e3"                 /* scientific notation is valid numeric */
say "datatype(" || a || "):" datatype(a)
say "datatype(" || b || "):" datatype(b)
say "datatype(" || c || "):" datatype(c)
say "datatype(" || d || "):" datatype(d)
say "datatype(" || a || ",'W'):" datatype(a, 'W')   /* W = whole number? */
say "datatype(" || c || ",'W'):" datatype(c, 'W')
say ""

/* Section 4: Arbitrary-precision decimal arithmetic */
say "=== Decimal Arithmetic ==="
numeric digits 9
say "1/3 (9 digits):" 1/3
numeric digits 30
say "1/3 (30 digits):" 1/3
numeric digits 50
say "2**100 (50 digits):" 2**100
say ""

/* Section 5: Non-strict (=) vs strict (==) comparison */
say "=== Comparison Types ==="
say "(5 = '5.0'):" (5 = "5.0")
say "(5 == '5.0'):" (5 == "5.0")
say "('abc' = 'abc '):" ("abc" = "abc ")
say "('abc' == 'abc '):" ("abc" == "abc ")

A few points worth noting in this example:

  • x = 100 and y = "100" produce identical internal storage. The quotes are a source-code convenience, not a type annotation.
  • DATATYPE(v) with no second argument returns NUM if the value could be treated as a number and CHAR otherwise. A one-letter option (N, W, A, and so on) narrows the check and returns 1 or 0.
  • NUMERIC DIGITS adjusts the global precision used for arithmetic results. REXX performs decimal math, not binary floating-point, so 1/3 is a genuine repeating-decimal approximation rather than a misleading 0.3333333333333333.
  • The plain = operator pads shorter strings with blanks and does numeric comparison when both operands look like numbers. The strict == operator demands exact character-for-character equality, including length.

Stem Variables — REXX’s Built-in Associative Arrays

REXX has no array, list, map, or hash keyword. Instead, every variable whose name contains a period behaves as a compound variable, and the part before the first period is the stem. A bare stem followed by a period (e.g. score.) sets the default value for every tail that is not explicitly assigned — including tails you haven’t thought of yet. Integer tails give you array-like behaviour; symbol tails give you dictionaries. The convention for “array length” is to store the count in the .0 tail.

Create a file named stems.rexx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* Stem variables: REXX's compound variables */

/* Section 1: Integer tails simulate a traditional array */
say "=== Integer-tail Stem ==="
fruit.1 = "apple"
fruit.2 = "banana"
fruit.3 = "cherry"
fruit.0 = 3                 /* convention: store the count in .0 */

do i = 1 to fruit.0
    say "fruit." || i || ":" fruit.i
end
say ""

/* Section 2: Bare-stem assignment provides a default for every tail */
say "=== Default Tail Value ==="
score. = 0                  /* every tail now reads as 0 unless overridden */
score.1 = 95
score.2 = 87
say "score.1:" score.1
say "score.2:" score.2
say "score.99 (never assigned):" score.99
say ""

/* Section 3: Symbol tails behave like associative-array keys */
say "=== Symbol Tails ==="
capital.usa    = "Washington"
capital.france = "Paris"
capital.japan  = "Tokyo"
say "capital.usa:" capital.usa
say "capital.france:" capital.france
say "capital.japan:" capital.japan

Notice three things here:

  1. The DO i = 1 TO fruit.0 loop uses the stored count. Nothing stops you from overrunning a stem — it just returns the default value (or the uppercased tail name, if no default was set).
  2. Assigning to the bare stem score. does not create a single “array” object; it sets a fallback for every possible tail. This is how REXX programmers emulate sparse arrays and initialised-to-zero counters.
  3. In capital.usa, the symbol usa is uninitialised, so REXX uses the uppercased literal USA as the internal tail. Reading back via capital.usa performs the same lookup, so the round-trip works transparently.

Running with Docker

1
2
3
4
5
6
7
8
# Pull the Regina REXX image (skip if you already have it)
docker pull rzuckerm/rexx:3.6-5.00-1

# Run the variables example
docker run --rm -v $(pwd):/app -w /app rzuckerm/rexx:3.6-5.00-1 rexx /app/variables.rexx

# Run the stems example
docker run --rm -v $(pwd):/app -w /app rzuckerm/rexx:3.6-5.00-1 rexx /app/stems.rexx

Expected Output

Running variables.rexx:

=== Basic Variables ===
message: Hello, REXX!
count: 42
pi: 3.14159
flag: 1

=== Typeless Data ===
x + 5 = 105
y + 5 = 105
x || 'abc' = 100abc

=== DATATYPE Checks ===
datatype(42): NUM
datatype(hello): CHAR
datatype(3.14): NUM
datatype(2.5e3): NUM
datatype(42,'W'): 1
datatype(3.14,'W'): 0

=== Decimal Arithmetic ===
1/3 (9 digits): 0.333333333
1/3 (30 digits): 0.333333333333333333333333333333
2**100 (50 digits): 1267650600228229401496703205376

=== Comparison Types ===
(5 = '5.0'): 1
(5 == '5.0'): 0
('abc' = 'abc '): 1
('abc' == 'abc '): 0

Running stems.rexx:

=== Integer-tail Stem ===
fruit.1: apple
fruit.2: banana
fruit.3: cherry

=== Default Tail Value ===
score.1: 95
score.2: 87
score.99 (never assigned): 0

=== Symbol Tails ===
capital.usa: Washington
capital.france: Paris
capital.japan: Tokyo

Key Concepts

  • Typeless by design. Every REXX value is a character string; numeric, boolean, and textual interpretations are decided by the surrounding operators and built-ins, never by a type declaration.
  • Assignment creates variables. There is no declare, no var, and no initialiser syntax. The first assignment is the declaration, and subsequent assignments may store values that look like entirely different “types”.
  • Variable and keyword names are case-insensitive. Count, COUNT, and count all refer to the same variable; SAY and say are the same instruction.
  • DATATYPE() is your type inspector. Use DATATYPE(v) for a quick NUM/CHAR answer, or DATATYPE(v, 'W'), DATATYPE(v, 'N'), DATATYPE(v, 'A'), etc., for specific categorical checks.
  • Arithmetic is decimal and arbitrary-precision. NUMERIC DIGITS n sets the number of significant digits used in results — perfect for financial calculations, awkward for nanosecond-sensitive floating-point work.
  • = and == are different operators. Plain = does numeric comparison when both sides look numeric and otherwise pads strings with blanks; == demands exact, byte-for-byte string equality.
  • Stem variables replace arrays and dictionaries. Any variable name containing a period is a compound variable; a bare stem assignment (stem. = value) sets a default for every tail, giving you sparse arrays and initialised dictionaries in one construct.
  • REXX has no true constants. The convention is to assign once and treat the variable as read-only by discipline — or use NUMERIC DIGITS / OPTIONS for settings that behave constant-like across a scope.

Running Today

All examples can be run using Docker:

docker pull rzuckerm/rexx:3.6-5.00-1
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining