CS 60       URL http://cs.hmc.edu/~keller/rex/lite.html

Last updated 5 September 1999 by keller@cs.hmc.edu. Please report any errors or inaccuracies to keller@cs.hmc.edu.

rex lite Reference Card

The light-weight guide to the rex language and system.

Not all available functions are given here. For the full list, see http://cs.hmc.edu/~keller/rex.

Content Links

Arithmetic | Arity | Booleans | Characters | Commands | Comments | Definitions | Expressions | Forms | Failure | Incremental | Interrupt | Lists | Local | Logical | Misc | Quick Commands | Operators | Precedence | Read Loop | Relational | Rules | Sequences | Strings | Variables

Command-Line Arguments

In UNIX implementations, a file name to be read initially as rex source can be specified on the command line. Example, with the bold-face representing what the user types:

UNIX > rex fac.rex 
fac.rex loaded 
1 rex > fac(10); 
3628800 
Read Loop

rex runs an interactive "read loop", also called read-eval-print loop. This means that the following cycle repeats until the user types end-of-file (e.g. control-d):

In some cases, it is a little more complicated than this: the evaluation can be interleaved with printing the answer. This is helpful when the answer is very long, and essential when the answer is infinite. An evaluation can be interrupted by typing control-C a couple of times.

Quick Commands

Quick commands provide an alternative for entering system and help commands with a minimum of key strokes. A quick command is identified by a single line starting with * and terminated by return (no ;) The following quick commands are available:

*i Filename
Load the named file (no quotes around Filename).
*h
Enter the help loop.
*q
Toggle quotes mode. Only output is affected. In quotes mode, double quotes are shown as strings and single quotes are shown around characters.
*r
Toggle "readline" mode (off by default). In this mode, you can use emacs-like commands to edit single lines and move backward and forward in the history list of commands. (Note: In readline mode, suspending with control-Z is ineffective and causes problems.)
*t
toggle the function trace
*N
where N is a positive number: redo expression number N.
*-N
where N is a positive number: redo the expression N expressions ago
**
redo the previous expression (same as *-1)
 
Comments

Comments are as in C++ and cannot be recursive (no comments inside comments). They either start with

// and continue to end-of-line, or start with

/* and continue to */.

Expressions

There are three types of rex expressions:

All expressions to be evaluated are terminated with a semi-colon. (end-of-line doesn't terminate an expression, because expressions can take an arbitrary number of lines, and it is not generally evident when there isn't more to an expression.)

Definitions

Definitions bind variables and function names. A definition appears as

LHS = RHS;

where LHS is the variable or variables being defined, and RHS is what defines them. Examples are:

cube(x) = x*x;         // defines the cube function 
 
alist = [1, 2, 3, 4];   // defines a list variable called alist 
 
[x, y, z] = foo(alist); // defines variables x, y, and z,

 

Rules

Rules define parts of functions by using pattern matching and guards, and are collected together by rex to make a definition. A rule has the form: LHS => RHS; (The symbol => is an = followed by a > and is read "yields"). The following are examples of rules (for the greatest-common-divisor function):

gcd(0, y) => y;

gcd(x, y) => x <= y ? gcd(y % x, x);

gcd(x, y) => gcd(y, x);

The second rule above uses a guard, an expression which must be true in order for the rule to apply. As another example, the following rules use recursion and pattern matching to find the last element in a non-empty list.

last([X]) => X; 
 
last([_ | X]) => last(X);
 
Failures

A failure is said to take place when a function evaluation is attempted but there is no matching rule. This is a level-1 failure. For example, last([]) in the case of the above rules would result in such a failure. A level N+1 failure occurs when a function evaluation calls another function which results in a level N failure. A level-0 failure is said to occur if the left-hand side of an equational guard does not match the right-hand side. Failures are usually the result of programming errors.

 
Evaluable Expressions

Although all expressions have a value, the remaining kinds of expressions are entered for purposes of evaluation. They yield a value which is printed. They can also have side-effects, but the routine use of side-effects is discouraged. The computations of these expressions are done in the context of definitions and rules.

For example, to use the gcd and last functions defined above in a computation, we'd need to enter an expression:

gcd(56, 72);

rex would respond with the answer 8. If we entered:

last([1, 2, 3, 4, 5]);

rex would respond with the answer 5.

Variables

A variable is a sequence of contiguous characters starting with a letter (upper or lower case) or underscore, and containing letters, underscores, and digits. Names of functions are actually variables bound to anonymous function values.

Operators

The following kinds of operators are available:

Arithmetic, logical, and relational operators are pretty much the same as in C and C++.

Precedence and Grouping

The following lists operator precedence tightest to weakest. All operators group left unless otherwise indicated.

Arithmetic Functions

These functions are defined as follows:

+ as in N1 + N2 + .... + Nn
with 2 or more numeric arguments, + adds the arguments together.
with 2 list arguments, adds the arguments together component-wise.
with 1 argument, +(N) is defined as the function (X) => X+N.
 
- as in N1 - N2
with 2 arguments, - subtracts its second argument from its first.
with 2 list arguments, subtracts the arguments component-wise.
As a prefix operator -(....) is unary minus, i.e. negates its argument.
* as in N1 * N2 * .... * Nn
with 2 or more numerical arguments, * multiplies the arguments together.
with 2 list arguments, multiplies the arguments component-wise.
with the first argument a scalar and the second a list, multiplies each element in the list by the scalar.
with 1 argument, *(N) is defined as the function (X) => X*N.
/ as in N1 / N2
with 2 arguments, / divides its first argument by its second.
with 2 list arguments, divides the elements of the first list by those in the second.
with 1 argument, /(N) is defined as the function (X) => X/N. The result is integer if all arguments are integer, otherwise it is floating.
% as in N1 % N2
gives the remainder of dividing the first argument by the second. Only integer arguments are supported.
abs
abs(N) returns the absolute value of N.
ceil
ceil(N) returns the smallest integer >= N;
divides
divides(N1, N2) returns 1 if integer N1 divides N2 with no remainder.
divides(N2) is the function which, with argument N1 will give the value divides(N1, N2).

For example, divides(3)(N1) will return 1 whenever N1 divides 3.

exp
exp(N) returns the exponential function of N, i.e. pow(e, N) where e is base of the natural logarithm.
fac
fac(N) returns the factorial of N (N*(N-1)*(N-2)*....*2*1).
finite
finite(Expression) returns 1 or 0 depending on whether the argument is neither of Infinity nor -Infinity in IEEE floating point.
floor
floor(N) returns the largest integer <= N;
float
float(N) returns a floating point value of a number N, whether N is floating or integer
integer
integer(N) returns the integer part of N as an integer, whether N is integer or floating. If N is a character, it returns the character code (e.g. ascii) for N.
isnan
isnan(Expression) returns 1 or 0 depending on whether the argument is a NaN ("not a number" in IEEE floating point). Note that by IEEE rules, two Nan's are always unequal and a given Nan is not even equal to itself. We currently violate the latter rule: x == x will always be true.
is_prime
is_prime(N) returns 1 if N is prime, 0 otherwise. Only applies to integers.
make_number
make_number(A) If A is a string, make_number tries to interpret it as a decimal numeral (integer or floating, in that order) and create a number from it. Similarly, if A is a char for a decimal digit. If A is a number, returns that number. Otherwise returns an error.
minus
minus(N) returns the negative of N, i.e. the same as -N.
pow
pow(N1, N2) returns the N1 raised to the power N2. The result is floating point if either argument is, otherwise it is integer.
sq
sq(N) returns the square of N.
sqrt
sqrt(N) returns the square root of N. If N is an integer, the result is the largest integer M such that M*M <= N.
 
Relational Functions

These apply to more than just numeric values. For example, lists and arrays can be compared in lexicographic order.

==
A1 == A2 evaluates to 1 if the arguments are equal, otherwise 0.
==(A2) is the function which, with argument A1, gives the value of A1 == A2.
!=
A1 != A2 evaluates to 1 if the arguments are unequal, otherwise 0.
!=(A2) is the function which, with argument A1, gives the value of A1 != A2.
>
A1 > A2 evaluates to 1 if A1 is greater than A2.
>(A2) is the function which, with argument A1, gives the value of A1 > A2.
<
A1 < A2 evaluates to 1 if A1 is less than A2.
<(A2) is the function which, with argument A, gives the value of A1 < A2.
>=
A1 >= A2 evaluates to 1 if A1 is greater than or equal A2.
>=(A2) is the function which, with argument A1, gives the value of A1 >= A2.
<=
A1 <= A2 evaluates to 1 if A1 is less than or equal A2.
<=(A2) is the function which, with argument A1, gives the value of A1 <= A2.
max
max(A1, ...., An) evaluates to the largest of the arguments. There must be at least one argument.
min
min(A1, ...., An) evaluates to the smallest of the arguments. There must be at least one argument.
 
Booleans

The Boolean model used by rex is: 0, [ ] (the empty list), and failure values are false, and anything else is true. When a built-in returns a Boolean, it is either 0 or 1.

Logical Operators

The logical operators are:

!
!A1 returns 1 if the argument is false, otherwise returns 0.
&&
A1 && A2 && .... && An evaluates arguments in sequence, until one is false, The first false argument is returned. Otherwise 1 is returned.
||
A1 || A2 || .... || An evaluates arguments in sequence, until one is not false. The first non-false argument is returned. Otherwise 0 is returned.
implies
implies(A1, A2)
A1 is evaluated. If it is not false, the result is A2. Otherwise the result is 1.
 
Lists and List Functions

List formation is represented by [X0, X1, ...., Xn-1]. A list result will show in this fashion as well, unless it is converted to some other format. The null list is [ ].

Lists may be written to and read from files as S expressions by single commands.

A list may be formed from an item and an existing list by [X | L], meaning to construct the list consisting of X followed by the elements of L. Another way to get the same effect is cons(X, L). With respect to the list thus created, X is the "first" of the list and L is the "rest".

This notation may be generalized: [X0, X1, ...., Xn-1 | L] forms the list starting with the n elements indicated, followed by the elements of L.

An "improper list" is formed by [X | Y] where Y is not a list. A result of this form will show as [X | Y]. Programming will generally be simplified if improper lists are avoided.

 

Incremental and "infinite" lists may be created by deferring the rest of the list: In [X |$ L] the $ acts to defer the evaluation of L. Taking the rest of the list will give the seed $L which is not further evaluated until needed (e.g. in the case of checking to see whether the rest is the empty list.

Other list functions may be found under sequence functions.

list
[A1, ...., An] or list(A1, ...., An) makes a list of the values of any number of arguments. Note carefully how list differs from cons.
cons
[A1, .... | An] or cons(A1, ...., An) Function cons (of an arbitrary number of arguments, but often 2) makes a list of the values of its arguments, the value of the last argument becoming the tail of the new list. There must be at least one argument.
first
first(A) returns the first element of a non-null list.
rest
rest(A) returns a list of all but the first element of a non-null list.
null
null(A) returns 1 if its argument is the empty list and 0 otherwise.
second
second(A) returns the second element of a list with at least two elements.
third
third(A) returns the third element of a list with at least three elements.

Other selections from lists may be made by treating the list as a function from {0, 1, 2, ..., length-1} to values: i.e. L(i) is the ith element of L.

range
range(N1, N2):

If N1 <= N2, range(N1, N2) yields the list [N1, N1+1, N1+2, ...., N2].

If N1 > N2, range(N1, N2) yields the list [N1, N1-1, N1-2, ...., N2].

range(N1, N2, K) yields the list [N1, N1+K, N1+2*K, ...., N2] in case that N1 <= N2 and K is positive, or N1 >= N2 and K is negative. Otherwise the result is [ ].

Examples:

range(1, 9) ==> [1, 2, 3, 4, 5, 6, 7, 8, 9]
 
range(9, 1) ==> [9, 8, 7, 6, 5, 4, 3, 2, 1] 
 
range(1, 9, 2) ==> [1, 3, 5, 7, 9] 
 
range(9, 1, -2) ==> [9, 7, 5, 3, 1]
Incremental and Infinite-List Functions

from

from(N) creates the infinite list [N, N+1, N+2, ...]. If N is floating, the items will be floating. Otherwise all items will be fixed.

from(N, K) creates the infinite list [N, N+K, N+2*K, ...]. If N is floating, the items will be floating. If K is floating, but N is not, all but the first item will be floating. Otherwise all items will be fixed.

primes
primes() creates the (infinite) list of primes starting with 2.
random
random() creates an (infinite) list of pseudo-random non-negative integers. random(M, N) creates an (infinite) list of pseudo-random integers between M and N inclusive.

 

Strings and String Functions

A literal string must be in double quotes, e.g.

"this is a string" 

Strings are distinguished from single characters, which are in single quotes, e.g. 'x'. In either case, backslash (\) can be used as an escape character. The standard C escape codes apply:

\n newline

\\ backslash

\b backspace

\v vertical tab

\t tab

\f form-feed

\a bell

\r carriage return

Note: As with sequences, a particular character of a string may be extracted by treating the string as a function: S(i), where i is between 0 and (length of the string) -1 yields the ith character of S.

concat
concat(S1, ...., Sn) concatenates the strings to create a new string.
explode
explode(S) creates a list of chars from string S.
lconcat
lconcat(L) concatenates the strings in list L to make one single string.
lconcat(L, S) concatenates the strings in list L interposing the string S between successive words (but not at the very end) to make one single string.
implode
implode(L) creates a string from a list of characters L.
length
length(S) gives the length of string S.
make_string
make_string(A) makes a string from argument A, where A is an integer, floating, or single char.
words
words(S) creates a list of strings from a single string. The strings are "words" in the sense that they are non-whitespace characters separated by some amount of whitespace.
 
Character Functions

isalpha: returns 1 if its argument is alphabetic, otherwise returns 0

isdigit: returns 1 if its argument is a digit, otherwise returns 0

isupper: returns 1 if its argument is upper-case, otherwise returns 0

islower: returns 1 if its argument is lower-case, otherwise returns 0

ispunct: returns 1 if its argument is punctuation, otherwise returns 0

isspace: returns 1 if its argument is whitespace, otherwise returns 0

iscntrl: returns 1 if its argument is a control character, otherwise returns 0

 
Sequence Functions

By a sequence we mean either a list or an array and these functions apply to both. In a few cases they also apply to strings. The ones which apply to incremental or infinite lists, as well as finite ones are annotated as "incremental".

all
all(P, L) yields 1 if all X occurring in L are such that P(X). Otherwise yields 0.
append
append(L, M) returns the list consisting of elements of L followed by those of M. [incremental in second argument]
assoc
assoc(X, L) yields the first list element of an association list having X as its first item. If there is no such item, yields [ ]. For example, assoc('c', [['a', 1], ['b', 2], ['c', 3], ['d', 4]]) will return [c, 3].
count
count(P, L) returns the number of X occurring in L is such that P(X).
diff
diff(B, S) forms the array or list of successive "differences", when the binary function B is viewed as "subtraction". If S = [S0, S1, S2, ... Sn], then diff(-, S) = [S1-S0, S2-S1, ...., Sn-Sn-1]. Thus diff(B, S) has one fewer element than S. diff(B, [ ]) is [ ] by convention. In general, B could be any binary operator, e.g. /. [incremental]
drop
If S is an array or list, and P is a 1-ary function, then drop(P, S) is the array or list resulting from dropping those elements for which P is true. [incremental]
every
every(N, L, K) yields a list consisting of every Nth element of L starting at the Kth (where K = 0 means the first, etc. [incremental]
find
find(P, L) yields the suffix of L beginning with the first item X for which P(X). If there is no such item, yields [ ], unless L is infinite, in which case this function diverges. [See also keep.]
find_index
find(P, L) yields index (starting with 0 as the first index) of the first item X for which P(X). If there is no such item, yields -1, unless L is infinite, in which case this function diverges.
find_indices
find(P, L) yields the list of indices of all items X for which P(X). If L is infinite but there are only finitely-many such X, this function diverges after producing the indices of those X. [See also keep.] [incremental]
keep
If S is an array or list, and P is a 1-ary function, then keep(P, S) is the array or list resulting from keeping those elements for which P is true. [incremental]
leaves
leaves(A) treats the list as the root of a tree, and returns a list of the leaves of the tree. The tree can have arrays or lists as nodes.
leafcount
leafcount(A) is the length of leaves(A)
length
length(S) returns the length of the list, array, or string
map
If S is an array or list, map(F, S) constructs a new array or list the same length as S, by applying F to each element of S. map(B, S1, S2) S1 and S2 should be both arrays or both lists. The result is a new array or list constructed by applying binary function B to the elements of S1 and S2, element-by-element. [incremental]
mappend
mappend(F, L) Assuming L is a list, and F is a function which will return a list when applied to a member of L, returns the result of appending the elements of map(F, L) together in order.
member
member(X, L) yields 1 if X occurs in L; otherwise yields 0.
merge
merge(L, M) assuming that L and M are in order according to the built-in order <, merges them together into a single with respect to <. [incremental]
merge
merge(P, L, M) assuming that L and M are in order according to the partial order P, represented as a binary function, merges them together into a single list in order with respect to P. [incremental]
no
no(P, L) yields 1 if no X occurring in L is such that P(X). Otherwise yields 0.
prefix
prefix(N, L) yields the first N items in L. If L doesn't have N items, yields all of L. [incremental]
reduce
reduce(B, U, S) Here B is a binary operator with unit U and S is an array or list. The result is the accumulation of applying B to elements in S.

For example, reduce(+, 0, S) is the sum of elements in S, while reduce(*, 1, S) is the product. Grouping is to the left, i.e. if S were [S0, S1, S2, ... Sn], then the result is B( .... B(B(B(U, S0), S1), S2) ...., Sn). [incremental]

remove_duplicates
remove_duplicates(L) returns a list with any duplicates removed [incremental]
reverse
reverse(L) returns the list consisting of elements of L in reverse order
scale
If S is an array or list, scale(F, S) constructs a new array or list the same length as S, in which each element of the original is multiplied by F. [incremental]
scan
If B is a binary operator and S is an array or list then scan(B, S) results in an array or list (of the same length as S) of partial accumulations of B applied to the elements of S. For example, scan(+, S), where S = [S0, S1, S2, ... Sn], yields [S0, S0+S1, S0+S1+S2, ...., S0+....+Sn-1]. [incremental]
some
some(P, L) yields 1 if an X occurs in L such that P(X). Otherwise yields 0.
sort
sort(S) returns an array or list (whichever S is) containing the elements of S in sorted order. The ordering is the natural one built into rex.
suffix
suffix(N, L) yields the last N items in L. If L doesn't have N items, yields all of L. [incremental]
zip
zip(L, M) yields the list formed by alternating an element of L with an element of M [incremental]

 

Transcendental Functions
sin, cos, tan, atan, asin, sinh, cosh, tanh, asinh, acosh, erf, erfc
sin(N) returns the sine of N, where N is in radians. Similarly for cos, tan, atan, ....
log
log(N) returns the natural logarithm of N.
log(N1, N2) returns the logarithm of N2 base N1.

 

Miscellaneous Functions
 
id
id(Datum) returns Datum (useful in certain contexts, e.g. make_array).
k
k(Datum) makes a constant function with value Datum. That is, it returns a function which, when applied to any argument, returns Datum. i.e. k(Datum)(Anything) is Datum.
test
test(Expression1, Expression2) evaluates the expressions and compares them for equality. Usually Expression2 is the expected answer. Prints a message indicating whether the test is "ok" or "bad".
type
type(A) returns a string naming the type of its argument A. The following types are available:
integer | floating | string | char | list | array | function | builtin | error | failure
type() returns a list of strings naming the available types. Lists and arrays may have any of these types as elements, in any mixture, include lists and arrays to any number of levels of nesting.

The following functions return 1 or 0, depending on whether the argument is of the corresponding type:

atomic (not list or array)

is_list

is_integer

is_char

is_floating

is_array

is_number (integer or floating)

is_sequence (list or array)

is_string

is_function (user-defined or builtin)

is_error

 

 

Forms (not necessarily functions)
 

? as in Guard ? Body

is used in guarded rules. Guard is the guard, a logical expression, and Body is an expression. The rule applies only if the guard is true, in which case the result is the value of Body.
 
? as in Condition ? True_Alternative : False_Alternative
 

Condition is evaluated first and if true, the result is the value of True_Alternative, otherwise it is the value of False_Alternative.

 

=> (read "yields") as in Head => Body

is used in two ways: to define rules and to define anonymous functions.

To define a rule:

Head => Body;

where Head is a function name with arguments, defines one rule for the named function.

Example: foo([A | L], N) => foo(L, A+N);

To define an anonymous function:

Head => Body

where Head is just a list of arguments in parentheses, is an "anonymous function" (also known as a "lambda expression").

Example: (F, X) => F(F(X)) is the function of two arguments, say F and X, which applies F to X, then applies F to the resulting value.

 
Arity Notation

Some built-in functions are overloaded. For example, map has two or three arguments. When a built-in function is passed as an argument, the ambiguity of which function is to be used can be resolved by appending to the function name a # followed by the arity, e.g. map#2 vs. map#3. The same applies to operators, e.g. +#1 vs. +#2. Some functions can take an arbitrary number of arguments, e.g. max takes any number greater than zero of arguments. Similarly, + is two different functions: +#1 is the "curried" +, while +#_ is the addition function with any other number of arguments. If # is not attached, rex will make a choice of arity. For example, the binary arithmetic functions are the choice for +, -, *, /, %.

Local Definitions and Equational Guards

A local definition takes the form

LHS = RHS, Expression; 

The value of the overall expression is the value of Expression in the context of the bindings made in the equation. These bindings are local, and hold only for the evaluation of Expression. They have no impact on existing bindings to the variables. Example:

 root = sqrt(x), f(root, g(root)); 

Here root is bound to the value of sqrt(x), for the evaluation of f(root, g(root)). One use of a local definition is to prevent repeated expensive evaluation of an expression.

When the right-hand side of a rule has this form, we call it an equational guard. The idea is that the rule only applies if the definition succeeds. As with global definitions, a definition will fail if there is a mis-match between the LHS and RHS. Example:

 f(x) => root = sqrt(x), g(root,h(root)); 

Here the value of f(x) is computed by binding root to sqrt(x), then returning the value g(root, h(root)). Since root is only a single variable, the pattern is trivial and this guard will never fail. If the evaluation of g fails, this translates to a failure of the whole function f, not to the failure of a guard.

Local Recursive Definitions

When an ordinary local definition is made, the values of variables and functions on the RHS is that determined by the surrounding context. Sometimes it is desirable to make those values be exactly the ones being defined locally. This is particularly true in the case of defining a recursive function locally. For example, suppose for some reason we wished to apply a local definition of the factorial function. The following would not work:

 f(N) = N < 2 ? 1 : N*f(N-1), f(10);

The reason this doesn't work is that the f on the RHS of the equation (but not the one after the comma) is not the local f, but rather the surrounding f, if indeed there is one. In order to make both f's be the same, so that recursion will work, we need to use a recursive local definition, signified by braces {....}. The proper way to define and use f is:

 { f(N) = N < 2 ? 1 : N*f(N-1); f(10) }; 

In general, the expressions inside braces consist of some number of equations terminated by semi-colon, followed by an expression, the value of which becomes the value of the overall brace expression. Another use of local recursive definitions is in infinite-list functions with loops. For example, to define a function which is the infinite repetition of an item X, without recomputing X, we can define

repeat(X) = { Y = [X |$ Y]; Y }; 

Here it is Y which is the same on both the left- and right-hand sides of the equation Y = [X |$ Y].

 
System Commands

System commands look just like expressions to be evaluated, but they generally have side-effects, such as reading a rex source file. The "function" sys is thus not really a mathematical function. The second argument to sys is interpreted literally as one of a number of sub-commands, and there will usually be additional arguments, which are either literal or evaluated, depending on the sub-command.

The in sub-command:

sys(in, Filename);

Here in is literal and Filename is an expression which must evaluate to a string. That string is looked up according to the usual interpretation and loaded as a series of rex expressions.

The on and off sub-commands:

sys(on, Flag); and sys(off, Flag);

Here on and off are literal and Flag is interpreted literally (without evaluation). The corresponding flag is turned on or off. The following are possible flags: