Intermediate

Functions in REXX

Learn how to define and call functions and subroutines in REXX, including arguments, return values, variable scope with PROCEDURE, recursion, and built-in functions

Functions let you package a piece of logic under a name, give it inputs, and reuse it anywhere in your program. As an imperative, procedural language, REXX organizes reusable code into routines—blocks of code marked by a label. A single routine can be used two ways: as a function that returns a value into an expression, or as a subroutine invoked with the CALL instruction. There is no separate syntax for declaring the two; the difference is only in how you call them.

REXX routines reflect the language’s human-oriented design. There are no type declarations on parameters, no return-type annotations, and no forward declarations—a routine is simply a label followed by instructions and a RETURN. Arguments arrive as strings (because in REXX everything is a string) and you pull them apart with PARSE ARG or read them with the ARG() built-in function.

In this tutorial you’ll learn how to define and call your own routines, pass multiple arguments, return values, control variable scope with the PROCEDURE keyword, write recursive functions, and tap into REXX’s large library of built-in functions.

Defining and Calling Functions

A REXX function is a label (a name followed by a colon) that ends with a RETURN instruction carrying a value. You call it like a function in most languages—name(arguments)—and the returned value is substituted right into the surrounding expression. Note the EXIT instruction: it stops the main program before execution falls into the routine definitions below it.

Create a file named functions.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
/* Defining and calling functions in REXX */

/* Call a function inline - its return value is used in place */
say greet("World")
say greet("REXX programmer")

/* Functions can take multiple arguments */
area = rectangle_area(4, 5)
say "Area of 4 x 5 rectangle:" area

/* A function call can appear inside a larger expression */
say "Double the area:" rectangle_area(4, 5) * 2

exit

/* greet: takes one argument, returns a greeting string */
greet: procedure
  parse arg name
  return "Hello," name"!"

/* rectangle_area: takes two arguments, returns their product */
rectangle_area: procedure
  parse arg width, height
  return width * height

PARSE ARG name assigns the first argument to name. With multiple arguments, PARSE ARG width, height splits them on the commas used at the call site. The RETURN instruction hands a value back to the caller, where it replaces the function call in the expression.

Subroutines and the CALL Instruction

The same routine can be invoked as a subroutine with the CALL instruction. After a CALL, the returned value (if any) is placed in the special variable RESULT. This style is handy when a routine performs an action and you want to check its result separately, or when it takes a variable number of arguments that you read with the ARG() built-in.

Create a file named subroutines.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
/* Subroutines: invoked with CALL, return value lands in RESULT */

/* CALL runs a routine; its RETURN value goes into the variable RESULT */
call describe 7
say result

call describe 42
say result

/* ARG() reports how many arguments were passed; ARG(i) reads the i-th */
call sum_all 10, 20, 30
say "Sum is" result

exit

/* describe: classify a number as even or odd */
describe: procedure
  parse arg n
  if n // 2 = 0 then
    return n "is even"
  else
    return n "is odd"

/* sum_all: add up however many arguments were passed */
sum_all: procedure
  total = 0
  do i = 1 to arg()
    total = total + arg(i)
  end
  return total

The // operator is REXX’s remainder operator, so n // 2 is 0 for even numbers. Because sum_all loops from 1 to arg(), it works no matter how many values you pass—a flexible alternative to fixed parameter lists.

Variable Scope with PROCEDURE

By default, a REXX routine shares all variables with the rest of the program—there is no automatic local scope. Adding the PROCEDURE keyword after the label changes that: the routine gets its own private set of variables, protecting the caller’s data. When a routine still needs to see specific caller variables, PROCEDURE EXPOSE names exactly which ones to share.

Create a file named scope.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
/* Variable scope in REXX routines */

counter = 100
config  = "production"

say "Before call, counter =" counter

/* 1. PROCEDURE isolates this routine's variables from the caller */
call try_to_change
say "After call, counter =" counter

/* 2. PROCEDURE EXPOSE shares only the named caller variables */
call show_config

/* 3. No PROCEDURE keyword - the routine shares ALL caller variables */
total = 5
call add_to_total
say "Shared total =" total

exit

try_to_change: procedure
  counter = 999
  say "Inside routine, local counter =" counter
  return

show_config: procedure expose config
  say "Config from caller:" config
  return

add_to_total:
  total = total + 10
  return

The counter assigned inside try_to_change is local, so the caller’s counter stays 100. show_config can read config because it was exposed. And add_to_total, which omits PROCEDURE entirely, freely modifies the caller’s total. Using PROCEDURE is the recommended practice—it prevents subtle bugs from accidentally clobbered variables.

Recursion

Because each PROCEDURE invocation has its own local variables, REXX functions can safely call themselves. Recursion is a natural fit for problems defined in terms of smaller versions of themselves, like the factorial and Fibonacci sequences.

Create a file named recursion.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
/* Recursion in REXX: a routine that calls itself */

/* Classic factorial: n! = n * (n-1)! */
do n = 1 to 6
  say n"! =" factorial(n)
end

say "---"

/* Build the first 10 Fibonacci numbers */
line = ""
do i = 0 to 9
  line = line fib(i)
end
say strip(line)

exit

factorial: procedure
  parse arg n
  if n <= 1 then
    return 1
  return n * factorial(n - 1)

fib: procedure
  parse arg n
  if n < 2 then
    return n
  return fib(n - 1) + fib(n - 2)

Each recursive call to factorial multiplies n by the factorial of n - 1 until it hits the base case of 1. The fib function adds the two previous values. strip trims the leading space left by the concatenation so the output is clean.

Built-in Functions

REXX comes with a generous library of built-in functions, especially for string handling—the language’s specialty. You call them with the same name(args) syntax as your own functions, and you can freely mix built-ins with custom routines.

Create a file named builtins.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
/* REXX ships with a rich library of built-in functions */

text = "REXX Programming"

/* String functions */
say "Length:" length(text)
say "Upper:" translate(text)
say "Word 2:" word(text, 2)
say "Word count:" words(text)
say "Substring:" substr(text, 1, 4)
say "Reversed:" reverse(text)

/* Numeric functions */
say "Max:" max(3, 17, 9, 42, 8)
say "Min:" min(3, 17, 9, 42, 8)
say "Absolute:" abs(-15)
say "Rounded:" format(3.14159, , 2)

/* Combine built-ins inside your own function */
say "Initials:" initials("ada countess lovelace")

exit

/* initials: take the first letter of each word, uppercased */
initials: procedure
  parse arg phrase
  out = ""
  do i = 1 to words(phrase)
    out = out || left(word(phrase, i), 1)
  end
  return translate(out)

translate with a single argument uppercases a string, word and words work on whitespace-separated words, and format rounds a number to a given number of decimal places. The initials routine shows how built-ins like words, word, and left combine to build new behavior.

Running with Docker

The examples run unchanged under Regina REXX in Docker. Pull the image once, then run each file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Pull the Regina REXX image
docker pull rzuckerm/rexx:3.6-5.00-1

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

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

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

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

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

Expected Output

Running functions.rexx:

Hello, World!
Hello, REXX programmer!
Area of 4 x 5 rectangle: 20
Double the area: 40

Running subroutines.rexx:

7 is odd
42 is even
Sum is 60

Running scope.rexx:

Before call, counter = 100
Inside routine, local counter = 999
After call, counter = 100
Config from caller: production
Shared total = 15

Running recursion.rexx:

1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
---
0 1 1 2 3 5 8 13 21 34

Running builtins.rexx:

Length: 16
Upper: REXX PROGRAMMING
Word 2: Programming
Word count: 2
Substring: REXX
Reversed: gnimmargorP XXER
Max: 42
Min: 3
Absolute: 15
Rounded: 3.14
Initials: ACL

Key Concepts

  • Routines are labels — A function or subroutine is a label (name:) followed by instructions and a RETURN. There is no separate declaration syntax for the two; the call style decides how it’s used.
  • Two calling styles — Use name(args) to call a routine as a function and use its return value inline; use CALL name args to invoke it as a subroutine, then read its return value from the special variable RESULT.
  • Arguments are strings — Read arguments with PARSE ARG (splitting on commas for multiple parameters) or with the ARG() and ARG(i) built-ins when the count varies. REXX puts no type constraints on parameters.
  • PROCEDURE controls scope — Without PROCEDURE, a routine shares every variable with its caller. Add PROCEDURE to get private local variables, and PROCEDURE EXPOSE name to selectively share specific ones.
  • Recursion needs PROCEDURE — Local scope from PROCEDURE is what makes recursive functions like factorial and Fibonacci work correctly, since each call keeps its own copy of variables.
  • EXIT separates code from routines — Place EXIT before your routine definitions so the main program doesn’t accidentally fall through into them.
  • Rich built-in library — REXX provides many built-in functions, with particularly strong string handling (length, substr, word, reverse, translate, left) that you call with the same syntax as your own routines.

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