W Wrapl, The Programming Language

Chapter 4


In addition to variables, Wrapl also allows the declaration of constants. A constant declaration is similar to a variable declaration, DEF id <- value, however the initial value is required. Constants are replaced by their value when evaluated, attempting to assign to a constant results in an error. Since constant declarations do not perform any assignment themselves, evaluating them displays NIL in the interactive interpreter.

--> DEF x <- 100; NIL --> x + 20; 120 --> x <- 30; Unhandled message: Assignment to location 0 --> x; 100


Functions in Wrapl are written as follows: <parameters> expression. When a function is called, the parameters are assigned values and the expression is evaluated. The value of the expression is returned as the result of the function. If too many arguments are supplied to a function, the extra arguments are evaluated, but discarded. If too few arguments are supplied, the missing parameters are set to NIL.

For example:

--> VAR f <- <x, y> x + y; <value> --> f(10, 20, "discarded"); 30

Since functions are used commonly, Wrapl provides syntactical sugar for assigning functions. Normally, functions are assigned to constants. Within the standard Wrapl libraries, function names begin with capital letters.

--> VAR F(x, y) x + y; <value> --> F(10, 20); 30 --> DEF G(x, y, z) [x, y, z]; NIL --> G(10, 5); [10, 5, NIL] --> DEF Fact(n) n < 2 => 1 // n * Fact(n - 1); NIL --> Fact(10); 3628800


Even though a function can only contain a single expression, this is not a restriction since the expression can be a block. Blocks in Wrapl are written in parentheses ( ... ) and may contain any number (including zero) of expressions or declarations. Each declaration and each expression except the last one must be terminated by a semicolon. A semicolon is not required after the last expression, but it is allowed. A block produces the values of its last expression. For example:

--> DEF Dist(x1, y1, x2, y2) ( ... VAR dx <- x2 - x1; ... VAR dy <- y2 - y1; ... :sqrt(dy ^ 2 + dx ^ 2); ... ); NIL --> Dist(0.0, 1.0, 1.0, 0.0); 1.4142135623730951455

Blocks can be used anywhere an expression is expected. An empty block is equivalent to NIL. Blocks with only one expression can be used to control operator precedence.

--> (); NIL --> 2 * (3 + 4); 14

Variables declared within a block are visible for the entire scope of the block, including before the declaration itself. However, variables are initialized only at the declaration itself, and will have the value NIL before it. Note that this does not apply to top level declarations in the interactive interpreter, since it uses an incremental scope.

--> IMP IO.Terminal USE Out; NIL --> DEF Test() ( ... Out:write('x = {x}\n'); ... VAR x <- 100; ... Out:write('x = {x}\n'); ... NIL; ... ); NIL --> Test(); x = NIL x = 100 NIL --> x; console(6): Error: identifier x not declared --> VAR x <- 100; 100 --> x; 100

Returning from Functions

A function <params> body normally returns only the first value produced by body, or fails if body fails to produce any values.

--> ALL 1:to(4); [1, 2, 3, 4] --> DEF F() 1:to(4); NIL --> ALL F(); [1]

However, it is possible to return from any point within a function, using the expression RET value. This causes the current function to terminate, returning the first value produced by value. If value fails, then the function still terminates, but fails to produce any value. If value is omitted, NIL is used. The expression FAIL is equivalent to RET BACK, i.e. it terminates the current function, failing to produce any value.

--> DEF F() ( ... VAR x <- 100; ... RET x; ... x <- 200; -- This line is never reached ... ); NIL --> F(); 200 --> DEF IsNonZero(x) ( ... x ~= 0 => RET x; ... FAIL; ... ); NIL --> IsNonZero(1); 1 --> IsNonZero(0); failure

Perhaps the most powerful way to return from a function is to suspend a value using SUSP value. This doesn't terminate the current function, instead it stores its state, and returns execution to the calling code, with the first value produced by value. Then, if the function call is resumed (because some later expression in the calling code fails), the suspended function resumes after the SUSP expression, allowing it to suspend another value (or return or fail). This allows generators to be written in Wrapl.

--> DEF S() ( ... SUSP 1; ... SUSP 2; ... SUSP 3; ... RET 4; ... SUSP 5; -- This line is never reached ... ); NIL --> ALL S(); [1, 2, 3, 4]

SUSP value only suspends the first value produced by value, unless a later expression causes it to backtrack. However, unlike RET value, if value fails, the entire SUSP expression fails, and the current function continues executing.

--> DEF T() ( ... SUSP 1 | 2 | 3; -- Only 1 is suspended ... (SUSP 10 | 11 | 12) & BACK; -- This will suspend all three values ... SUSP BACK; -- This does nothing ... SUSP 20; ... ); NIL --> ALL T(); [1, 10, 11, 12, 20, 20]

The extra 20 in the last function demonstrates a potential pitfall when using the SUSP expression. When the suspended function is resumed, the SUSP value expression produces the last value produced by value. Thus, if used at the end of a function, the same value will be generated twice, once by the SUSP expression, and then returned as the value of the last expression in the block. In general SUSP should not be used as the last expression of a function, use RET instead, or add an explicit FAIL after.