A P P E N D I X

The Visage Language in Depth

…a lot my of opinions about what makes programming languages “expressive” comes from my observations and opinions about what allows us effective expression in natural languages.

—Christopher Oliver

The Visage language stands out from other languages you can use with the JavaFX Platform because it was designed and engineered specifically for that platform. Visage’s origins date back to the F3 language designed by Christopher Oliver while he was working at SeeBeyond. With the purchase of SeeBeyond by Sun Microsystems, F3 was renamed JavaFX Script and went on to be the primary language of the JavaFX Platform up to version 1.3. At that time, Sun also made JavaFX Script open source, releasing it to the development community to enhance and extend. With the acquisition of Sun by Oracle, JavaFX Script was dropped as the language of JavaFX in favor of pure Java APIs. The JavaFX Script language was subsequently adopted by several members of the JavaFX community under the name Visage and continues to be developed and maintained for use with JavaFX and other UI toolkits.

In this appendix, we will cover the Visage language in greater detail, describing the full syntax, programming model, and language features. This will help you to understand the language at a fundamental level, enabling you to efficiently build rich user interfaces for the JavaFX Platform.

images Note Although Visage is a programming language for the Java platform, our coverage does not require any prior Java programming experience. Because of the close relationship between Visage and Java, using Java concepts to explain corresponding Visage concepts is sometimes inevitable. We will fully explain these Java concepts when the need arises.

An Overview of Visage

The Visage programming language was designed specifically for UI programming with the JavaFX platform. It has the following characteristics:

  • It is an object-oriented language. It supports classes, instance variables, instance functions, and inheritance.
  • It is a functional language. Functions are first-class entities that can be assigned to variables, passed in, and returned from functions.
  • It is an expression language. All executable code in Visage consists of expressions.
  • It supports a declarative style suitable for GUI programming. Its object literal syntax and sequence syntax make describing GUIs easy.
  • It supports data binding. It allows for easy separation of GUI views and models, which gives rise to “the way of Visage.”
  • It is a statically typed, compiled language with basic type inference capabilities. It compiles source code into Java classes.
  • It can leverage the vast number of Java libraries. Visage classes can extend Java classes and interfaces, and Visage code can instantiate Java objects and call Java methods.

We will use Visage data types as guideposts in our coverage of the programming language. There are four kinds of data types in Visage: primitive, sequence, class, and function. We concentrate on primitive and sequence types in the first half of this appendix. In the second half, we build up to function and class types, followed by advanced features like reflection and Java interoperability.

Variables, Values, and Their Types

The simplest construct in Visage is the variable. A variable can be assigned values. Values can be literals or expressions. Each variable has a declared type. Each value has a runtime type. An assignment is valid only if the runtime type of the value is compatible with the declared type of the variable.

As you can see in Listing A-1, variable declarations are introduced by the var keyword, followed by the name of the variable, an optional type specifier, and an optional initializer, and are terminated by a semicolon (;).

Listing A-1. Variable Declarations

var b: Boolean = true;
var i: Integer = 1024;
var n: Number = 3.14;
var str: String = "Hello, World";
var dur: Duration = 60s;
var col: Color = #E01B4C

In Listing A-1, we declare six variables:

  • The variable b is of type Boolean and is initialized to the literal value true.
  • The variable i is of type Integer and is initialized to the literal value 1024.
  • The variable n is of type Number and is initialized to the literal value 3.14.
  • The variable str is of type String and is initialized to the literal value "Hello, World".
  • The variable dur is of type Duration and is initialized to the literal value 60s, meaning “60 seconds.”
  • The variable col is of type Color and is initialized to the literal value #E01B4C, which is the hexadecimal RGB string for a shade of red.

Variable Names

Visage variable names follow the Visage identifier syntax. A Visage identifier starts with a letter, followed by letters, digits, or both. For the purpose of forming Visage identifiers, letters include the uppercase and lowercase ASCII letters and other Unicode letters, the underscore (_), and the dollar character ($), and other currency characters. Digits include digits 0 through 9 and other Unicode digits.

The following are valid Visage identifiers:

i
x2
Builtins
toString
_tmp$getter

The following are invalid Visage identifiers:

2x
package-info
Hello, World

Visage identifiers are case sensitive. Therefore count, Count, and COUNT are considered three distinct identifiers.

Variable Types

Visage variables may be declared with an explicit type, as we did in Listing A-1. The type of a variable follows the variable name and is separated from the name by a colon (:). The colon is not considered a part of the variable name or a part of the type name. The type of a variable dictates the kind of values that can be assigned to the variable. For example, it is illegal to assign a String value to an Integer variable.

Visage has a type inference facility that allows you to declare a variable without an explicit type. The Visage compiler will infer the variable’s type from the initializer in the declaration or, if no initializer is given in the declaration, the first subsequent assignment.

Therefore, you can safely rewrite the variable declarations from Listing A-1 as shown in Listing A-2.

Listing A-2. Variable Declarations with Inferred Types

var b = true;
var i = 1024;
var n = 3.14;
var str = "Hello, World";
var dur = 60s;
var color = #E01B4C

Visage’s type system consists of various kinds of types. These include the primitive, sequence, function, and class types.

Primitive Types

Visage intrinsically understands a few data types. These are called the primitive types. The core primitive types in Visage are: Boolean, Integer, Character, Byte, Short, Long, Number, Float, Double, and String. These types allow Visage programs to integrate seamlessly with class libraries written in Java. There are also several extended types that let you succinctly express UI specific concepts. These are Duration, Length, Angle, and Color. Both the core and extended primitive types share some common characteristics:

  • Each primitive type—except the three “small” types, Character, Byte, and Short—has its own literal syntax so that you can put values directly into a Visage program.
  • Each primitive type has a default value that a variable of the type gets if it is declared without an initializer.
  • Each primitive type supports a set of operations that are appropriate for the type.
  • Each primitive type is backed by a class that provides more capabilities for working with variables of the type.

images Caution If you are familiar with the Java programming language, you should notice that primitive types in Visage differ from those in Java. For example, String is a primitive type in Visage but is a reference type in Java, and Java primitive types are not backed by classes.

Boolean Type

The Boolean type has two possible values: true and false. You can use a Boolean variable to denote a condition in a Visage program. Boolean values are used in conditional and looping expressions.

Boolean Literals and the Default Value

To specify an explicit boolean value in Visage, you use the literals true and false. The default value of a Boolean variable is false.

Boolean Operators

Visage’s boolean operators allow you to build new Boolean values from existing ones.

The not operator is a unary operator. Its operand must evaluate to a Boolean value, and it negates its operand.

The and operators and the or operators are binary infix operators. An infix operator is an operator that appears between its operands. Both operands must evaluate to Boolean values. The and operator produces true if both operands are true, and false otherwise; the or operator produces true if at least one operand is true, and false otherwise. Both operators perform their evaluations in a short-circuit fashion. If the first operand of an and expression is false, the and expression’s value will be false, and the second operand is not evaluated. Similarly, if the first operand of an or expression is true, the or expression’s value will be true, and the second operand is not evaluated.

The Backing Class of the Boolean Type

The Boolean type is backed by the Java class java.lang.Boolean. This means that you can call any Java methods, including static methods, in java.lang.Boolean on a Visage Boolean value.

Since java.lang.Boolean is a small class and most of its methods are geared toward supporting Java functionality, the fact that it is the backing class of the Visage Boolean type is rarely used.

In Listing A-3, you can see Visage Boolean variables at work.

Listing A-3. Boolean Variables at Work

var a = true;
var b = false;

println("a = {a}");
println("b = {b}");

println("not a = {not a}");
println("not b = {not b}");

println("a and a = {a and a}");
println("a and b = {a and b}");
println("b and a = {b and a}");
println("b and b = {b and b}");

println("a or a = {a or a}");
println("a or b = {a or b}");
println("b or a = {b or a}");
println("b or b = {b or b}");

In Listing A-3 presents two features of Visage that we have not covered in detail yet. The println() function prints its argument to the console; it is a built-in function. The curly braces embedded in a String allow us to put the String representation of an arbitrary Visage expression in a String.

Integer Type

The Integer type represents 32-bit signed integral values, with valid values ranging from –2,147,483,648 to 2,147,483,647. You can use Integer values as counters and sequence indexes.

Integer Literals and the Default Value

Visage supports three forms of integral type literals. Decimal literals consist of a leading nonzero decimal digit (1 through 9) followed by a series of decimal digits (0 through 9). Octal literals consist of a leading digit zero (0) followed by a series of octal digits (0 through 7). Hexadecimal literals consist of a leading digit zero (0) followed by the character x or X, and then a series of hexadecimal digits (0 through 9, a through f, and A through F). The default value of an Integer variable is 0.

Here are some examples of integral type literals:

2009
03731
0x07d9
0X07D9

An integral type literal whose value falls within the range of the Integer type is an Integer literal. It has an inferred type of Integer. An integral type literal can also be a Long literal, as you’ll see shortly.

images Caution When you’re initializing or assigning to an Integer variable, the integral type literal that you use must be an Integer literal. Using an integral type literal outside the range of the Integer type will cause a compilation error. Catching errors early is one of the benefits of a compiled language.

Arithmetic Operators

Visage’s arithmetic operators operate on Character, Byte, Short, Integer, and Long values as well as Number, Float, Double, and Duration values. These operators are the addition (+), subtraction (-), multiplication (*), division (/), and modulo (mod) operators. Notice that not all arithmetic operations are meaningful for Duration values. For example, you cannot multiply two Duration values.

When applied to Integer operands, these operators always produce Integer values. They associate from left to right. The multiplication, division, and modulo operators have higher precedence over the addition and subtraction operators. The addition, subtraction, and multiplication operators are capable of overflowing the legal limits of the Integer type. In such cases, Visage will silently produce an inaccurate result. Dividing an Integer value by zero causes an exception to be thrown at runtime. You will learn more about exceptions in the section entitled “Exceptions” later in this appendix.

Visage supports the preincrement operator (++i), the postincrement operator (i++), the predecrement operator (--i), and the postdecrement operator (i--). Visage also supports the negation operator (-x).

The Backing Class of the Integer Type

The Integer type is backed by the Java class java.lang.Integer. This means that you can call any Java methods, including static methods in java.lang.Integer, on a Visage Integer value.

Again, since most methods of java.lang.Integer are geared toward supporting Java functionality, you will rarely call these methods on Visage Integer values.

Listing A-4 shows Visage Integer variables at work.

Listing A-4. Integer Variables at Work

var i = 1024;
var j = 2048;
var k = 15625;

println("i = {i}");
println("j = {j}");
println("k = {k}");

println("i + j = {i + j}");
println("i - j = {i - j}");
println("i * k = {i * k}");
println("k / i = {k / i}");
println("k mod j = {k mod j}");

var max = Integer.MAX_VALUE;
var min = Integer.MIN_VALUE;

println("max = {max}");
println("min = {min}");

println("max + 1 will overflow to {max + 1}");
println("min - 1 will overflow to {min - 1}");
println("max * min will overflow to {max * min}");

println("i = {i}, ++i = {++i}");
println("j = {j}, --j = {--j}");
println("k = {k}, k++ = {k++}");
println("k = {k}, k-- = {k--}");

Listing A-4 uses a Visage feature that allows us to access Java classes and their members directly. We access the static fields MAX_VALUE and MIN_VALUE of the Visage Integer, backed by the java.lang.Integer class. You will learn how to access Java programming language features from Visage in the section entitled “Leveraging Java from Visage” later in this appendix.

Running this example will produce the following output:


i = 1024

j = 2048

k = 15625

i + j = 3072

i - j = -1024

i * k = 16000000

k / i = 15

k mod j = 1289

max = 2147483647

min = -2147483648

max + 1 will overflow to -2147483648

min - 1 will overflow to 2147483647

max * min will overflow to -2147483648

i = 1024, ++i = 1025

j = 2048, --j = 2047

k = 15625, k++ = 15625

k = 15626, k-- = 15626

Character Type

The Character type represents 16-bit unsigned character values, with valid values in the range from 0 to 65,535. You can use Character values to represent Unicode characters.

Sources of Character Values and the Default Value

Visage does not support Character literals. The main source of Character values is from Java code that contains fields and returns values of the Java char type or the java.lang.Character wrapper type. You can assign Integer literals that fall within the Character range to Character type variables. The default value of a Character variable is 0.

You can also assign values of other numeric types to variables of the Character type. However, doing so will result in a loss of precision and a compilation warning will be generated.

Here is an example:

var ch: Character = 100;
println("ch={ch}.");

This will print ch=d to the console, since 100 is the Unicode value, as well as the ASCII value, of the character “d.”

images Caution If you are familiar with Java, you might be tempted to try to assign 'd' to a variable of Character type. This will generate a compilation error because 'd' is a String value in Visage and cannot be assigned to a Character variable.

Arithmetic Operations

Character values may participate in arithmetic operations. The type of the result of an arithmetic operation involving a Character value depends on the type of the other operand:

  • It is Integer if the other operand is a Character, Byte, Short, or Integer value.
  • It is Long if the other operand is a Long value.
  • It is Float if the other operand is a Float value.
  • It is Double if the other operand is a Double value.
The Backing Class of the Character Type

The Character type is backed by the Java class java.lang.Character. This means that you can call any Java methods, including static methods in java.lang.Character, on a Visage Character value. Many of the methods are geared toward supporting Java functionality and will rarely be called from Visage.

Byte, Short, and Long Types

The Byte type represents 8-bit signed integer values, with valid values ranging from –128 to 127. The Short type represents 16-bit signed integer values, with valid values ranging from –32,768 to 32,767. The Long type represents 64-bit signed integer values, with valid values ranging from –263 to 263–1.

Sources of Byte and Short Values, Long Literals, and the Default Values

Visage does not support Byte or Short literals. The main source of values of Byte and Short types is from Java code that contains fields and returns values of the corresponding Java primitive types, byte and short, and their wrapper types, java.lang.Byte and java.lang.Short. You can assign integral type literals that fall within their range to Byte or Short variables.

An integral type literal whose value falls outside the range of the Integer type but within the range of the Long type is a Long literal. It has an inferred type of Long. You can assign any Integer or Long literals to Long variables.

images Caution An integral type literal whose value falls outside the range of the Long type will generate a compilation error.

The default value of a Byte, Short, or Long variable is 0.

You can also assign values of other numeric types to variables of Byte, Short, or Long types. A compilation warning is generated if you are assigning a wider typed value to a narrower typed variable, such as assigning an Integer value to a Short variable.

Arithmetic Operations

Values of Byte, Short, or Long types may participate in arithmetic operations with other numeric values. The general rules governing the type of the result of arithmetic operations of numeric values are the same as those in the Java programming language:

  • If one of the operands is a Double value, the result is a Double value.
  • If one of the operands is a Float value, the result is a Float value.
  • If one of the operands is a Long value, the result is a Long value.
  • Otherwise, the result is an Integer value.
The Backing Classes of the Byte, Short, and Long Types

The Byte, Short, and Long types are backed by the Java classes java.lang.Byte, java.lang.Short, and java.lang.Long, respectively. This means that you can call any Java methods, including static methods in their backing classes on Visage Byte, Short, or Long values.

Float and Number Types

In Visage, the Float type is synonymous with the Number type. They have the same behavior under all circumstances. The Number type represents 32-bit floating-point numeric values, with valid absolute values between approximately 1.4 × 10-45 and 3.40 × 1038. Not every real number can be represented by a floating-point number. Therefore, floating-point arithmetic is only an approximation.

images Caution If you are familiar with the Java programming language, notice that the Visage Number type is distinct from the java.lang.Number abstract class in Java. In Visage, it represents 32-bit floating-point values backed by java.lang.Float.

Number Literals and the Default Value

Visage supports two forms of floating-point type literals. In the decimal notation, you specify a floating-point type literal as an integral part followed by the decimal point (.) and then the fractional part. Either the integral part or the fractional part, but not both, may be omitted. In the scientific notation, you specify a floating-point type literal as a magnitude followed by the letter “E” or “e” and then an exponent. The magnitude may be an integral number or a decimal number. The exponent may be a positive or a negative integer. The default value for a Number variable is 0.0.

The following are some examples of floating-point type literals:

3.14
2.
.75
3e8
1.380E-23

A floating-point type literal whose value falls within the range of the Number type is a Number literal. It has an inferred type of Number. A floating-point type literal can also be a Double literal, as you will see shortly.

Arithmetic Operations

All the arithmetic operators you learned in the section “Integer Type” apply to the Number type. Moreover, you can use the binary arithmetic operators on mixed Integer and Number values producing Number values. In such calculations, the Integer value is first promoted to a Number value before the arithmetic operation is performed.

Arithmetic operations on Number values may overflow the legal limits of the Number type and become positive infinity or negative infinity. Certain operations may produce results so small that they underflow the legal limits of the Number type and become zero (0.0). Certain invalid expressions, such as dividing infinity by infinity, produces a NaN (not a number) value.

The Backing Class of the Number Type

The Number type is backed by the Java class java.lang.Float. This means that you can call any Java methods, including static methods, in java.lang.Float on a Visage Number value.

You can use the isInfinite() and isNaN() methods of java.lang.Float to test whether a Number value is infinity or a NaN value.

Listing A-5 shows Visage Number variables at work.

Listing A-5. Number Variables at Work

var x = 3.14159;
var y = 2.71828;

println("x = {x}");
println("y = {y}");

println("x + y = {x + y}");
println("x - y = {x - y}");
println("x * y = {x * y}");
println("x / y = {x / y}");
println("x mod y = {x mod y}");

var max = Float.MAX_VALUE;
var min = Float.MIN_VALUE;

println("max = {max}");
println("min = {min}");

println("max * 2 will overflow to {max * 2}");
println("min / 2 will underflow to {min / 2}");

var inf = max * max;

println("inf = {inf}");
println("inf.isInfinite() = {inf.isInfinite()}");
println("inf / inf = {inf / inf}");

var nan = inf / inf;

println("nan = {nan}");
println("nan.isNaN() = {nan.isNaN()}");

Double Types

The Double type represents 64-bit floating-point numeric values, with valid absolute values between approximately 4.9 × 10324 and 1.79 × 10308.

Double Literals and the Default Value

A floating-point type literal whose value falls outside the range of the Float type but within the range of the Double type is a Double literal. It has an inferred type of Double. You can assign any Number (Float) or Double literals to Double variables.

The Backing Class of the Double Type

The Double type is backed by the Java class java.lang.Double. This means that you can call any Java methods, including static methods, in java.lang.Double on a Visage Double value.

String Type

The String type represents a sequence of Unicode characters. You have seen Visage String values in use in previous examples.

String Literals and the Default Value

Visage supports String literals enclosed in double-quote (") or single-quote (') characters. The two notations are interchangeable with two exceptions:

  • The double-quote character itself can appear without being escaped in a single-quoted string but must be escaped by a backslash () prefix in a double-quoted string.
  • The single-quote character itself can appear without being escaped in a double-quoted string but must be escaped by a backslash prefix in a single-quoted string.

String literals may not span multiple lines. However, the Visage compiler will automatically merge adjacent String literals and String expressions into one at compile time, which allows you to split a string across multiple lines.

Open and close brace characters ({, }) that are not escaped have special meaning in String literals. They are used to form String expressions, which we will cover in the upcoming section.

The backslash character is an escape character. Visage understands the following escape sequences in its String literals:

  • udddd: A Unicode character, with each d a hexadecimal digit
  • ddd: An octal character, with each d an octal digit
  • : Backspace (u0008)
  • f: Form feed (u000C)
  • : Newline (u000A)
  • : Return (u000D)
  • t: Tab (u0009)
  • ': Single quote
  • ": Double quote
  • {: Open brace
  • }: Close brace

The default value of a String variable is "" (an empty string). Here are some examples of String literals:

"Hello, World."
"It's raining."
'The string "abc" has three characters'
String Expressions

You can include a string representation of any Visage expression in a Visage string by forming string expressions. A string expression is formed by adding brace-delimited segments into a string literal, with each brace-delimited segment containing a Visage expression.

You have seen string expressions at work in Listings A-3, A-4, and A-5.

The Backing Class of the String Type

The String type is backed by the Java class java.lang.String. This means that you can call any Java methods, including static methods, in java.lang.String on a Visage String value.

The java.lang.String class is a fairly extensive class, and you can take advantage of many of its methods in your Visage code. Here’s a sampling of the methods that are of interest to Visage programmers:

  • length()
  • startsWith(prefix)
  • startsWith(prefix, offset)
  • endsWith(suffix)
  • indexOf(str)
  • indexOf(str, fromIndex)
  • lastlndexOf(str)
  • lastlndexOf(str, fromlndex)
  • substring(beginlndex)
  • substring(beginlndex, endlndex)
  • matches(regex)
  • contains(s)
  • replaceFirst(regex, replacement)
  • replaceAll(regex, replacement)
  • replace(target, replacement)
  • toLowerCase()
  • toUpperCase()
  • trim()

These methods do what their names suggest. Each character in a string has an index, which is zero based. Thus the first character has index 0, the second character has index 1, and so forth. Visage strings are immutable. None of the methods modify the string on which they are called. Some of the methods, such as trim(), return a new string. Therefore, if the variable str has the value " Hello ", calling str.trim() will not change what’s stored in the str variable. To capture the trimmed string, you must assign the return value of the call to a variable.

In Listing A-6, you can see Visage String variables at work.

Listing A-6. String Variables at Work

var greeting = "Hello, World.";

println("greeting = {greeting}");
println("greeting.length() = {greeting.length()}");
println("greeting.startsWith("H") = {greeting.startsWith("H")}");
println("greeting.endsWith(".") = {greeting.endsWith(".")}");
println("greeting.indexof(",") = {greeting.indexOf(",")}");
println("greeting.substring(3, 5) = {greeting.substring(3, 5)}");
println("greeting.toUpperCase() = {greeting.toUpperCase()}");
println("greeting.toLowerCase() = {greeting.toLowerCase()}");

var multiLine = "To construct a multi-line string in Visage, "
          "you can use its string literal and string expression "
          "concatenation capabilities.";
println(multiLine);

var str = "abcabc";
println("str = {str}");
println("str.indexOf('b') = {str.indexOf('b')}");
println("str.lastIndexOf('b') = {str.lastIndexOf('b')}");
println("str.contains('cab') = {str.contains('cab')}");
println("str.replaceFirst('a', 'x') = {str.replaceFirst('a', 'x')}");
println("str.replaceAll('a', 'x') = {str.replaceAll('a', 'x')}");

One thing from Listing A-6 that bears mentioning is that in the embedded expression region of a string expression, you do not need to escape the single-quote or double-quote characters even if they are the same ones used in the surrounding string expression.

Duration Type

The Duration type represents an amount of time. Duration values are used in Visage’s keyframe animation facilities.

Duration Literals and the Default Value

Duration literals are formed by appending a time unit to an integer or decimal literal. Visage understands four time units: hour (h), minute (m), second (s), and millisecond (ms). The default value of a Duration variable is 0.0ms.

The following are some examples of Duration literals:

0.1h
2m
30s
1000ms
Arithmetic Operations Involving Durations

By their nature, Duration values can enter into arithmetic operations. Visage supports adding two Duration values, subtracting one Duration value from another, multiplying a Duration value by a numeric value, and dividing a Duration value by a numeric value. The results of these operations are Duration values. Visage also supports dividing one Duration value by another Duration value, resulting in a Number value. The negation operator also applies to Duration values.

The Backing Class of the Duration Type

Unlike the other four primitive types, which are backed by Java classes, the Duration type is backed by a Visage class, visage.lang.Duration. Here are the instance functions of the visage.lang.Duration class that should be of interest to you:

  • toMillis()
  • toSeconds()
  • toMinutes()
  • toHours()
  • valueOf(ms)

Internally, Visage keeps track of Duration values in terms of numbers of milliseconds since midnight Greenwich mean time (GMT) January 1, 1970. The first four functions convert a Duration value to the specified unit and return a Number value.

The valueOf(ms) function is a factory function and should be called on the Duration class instead of on any Duration values. It returns a newly constructed Duration value that represents the supplied number of milliseconds.

images Note We will show you the usage of Visage classes in much more detail in the “Object Literals” section and how to write Visage classes in the “Working with Classes” section.

Listing A-7 shows the Visage Duration variables at work.

Listing A-7. Duration Variables at Work

var oneHour = 1h;
var oneMinute = 1m;
var oneSecond = 1s;
var oneMillisecond = 1ms;

println("oneHour = {oneHour}");
println("oneMinute = {oneMinute}");
println("oneSecond = {oneSecond}");
println("oneMillisecond = {oneMillisecond}");

println("oneHour + 30 * oneMinute = {oneHour + 30 * oneMinute}");
println("oneMinute - 20 * oneSecond = {oneMinute - 20 * oneSecond}");
println("oneSecond / 2 = {oneSecond / 2}");
println("-oneMillisecond = {-oneMillisecond}");
println("oneHour.toHours() = {oneHour.toHours()}");
println("oneHour.toMinutes() = {oneHour.toMinutes()}");
println("oneMinute.toSeconds() = {oneMinute.toSeconds()}");
println("oneSecond.toMillis() = {oneSecond.toMillis()}");

var now = Duration.valueOf(1229923309734.0);
println("now = {now}");

Length Type

Lengths represent a scalar unit of measurement. They are used in Visage to specify lengths and distances and are commonly used in specifying layouts and widths.

Length Literals and the Default Value

Length literals are formed by appending a length unit to an integer or decimal literal. Visage understands ten types of length units: inches (in), centimeters (cm), millimeters (mm), pixels (px), points (pt), picas (pc), ems (em), density-independent pixels (dps), scale-independent pixels (sps), and percentage (%). The default value of a Length variable is 0px.

The following are some examples of Length literals:

12px
5mm
2cm
1.5em
10dp
20%
Core Types of Length Literals

Visage supports five core types of length literals. Each of the core types has different properties and suggested uses based on the type of API you are using and the target device. Using the right type of length will make your application look good on different devices regardless of the screen size and resolution.

  • Exact screen pixels: This is an exact measurement in device pixels (px) of the screen and should be used wherever you need pixel-perfect alignment. Be careful using pixels directly, because doing so will reduce the portability of your application across devices.
  • Density-independent lengths: These are lengths that scale based on the target device. The reference measurement is called a density-independent pixel (dp) and has a one-to-one ratio with pixels on a 96dpi (dots-per-inch) device held at arm’s length. Other units of this type include centimeters (cm), millimeters (mm), inches (in), points (pt), and picas (pc), all of which are converted for a 96dpi screen. This type of unit is ideal to use when you want your application to be easily portable.
  • Scale-independent lengths: These are adjusted pixel measures based on the density of the screen and scale factor of the application. This type is most often used to update the font sizes based on user scaling but can also be used for layout and to scale graphics. The only unit of this type, the scale-independent pixel, is suffixed with sp.
  • Typographic length: This length unit is based on the size of fonts and ligatures. Historically this is the height of the “M” ligature in the given font, but in practice will be the same as the reference font height. Em literals are suffixed with “em”.
  • Percentage: This relative length unit is expressed as a percentage, suffixed with %. This is context-sensitive based on the use of the length, but it usually refers to a fraction of the parent length.
Length Arithmetic and Conversions

By their nature, Length values can enter into arithmetic operations. Visage supports adding two Length values, subtracting one Length value from another, multiplying a Length value by a numeric value, and dividing a Length value by a numeric value. The results of these operations are Length values. Visage also supports dividing one Length value by another Length value, resulting in a Number value. The negation operator also applies to Length values.

Arithmetic involving lengths of the same core type will result in a length value of the same type. Arithmetic involving lengths of different types will result in a compound type that contains the constituent length components in their unconverted forms. This value can then be converted back into a simple length by calling one of the conversions functions on the backing Length class.

The Backing Class of the Length Type

The Length type is backed by the Visage class visage.lang.Length. Here are the instance functions of the visage.lang.Length class that should be of interest to you:

  • toInches()
  • toCentimeters()
  • toMillimeters()
  • toPoints()
  • toPicas()
  • toEms()
  • toPixels()
  • toDensityIndependentPixels()
  • toScaleIndependentPixels()
  • toPercentage()
  • valueOf(length, unit)

Internally, Visage keeps track of Length values as a compound structure containing the five length types discussed earlier. The core types are pixels, density-independent pixels, scale-independent pixels, ems, and percentage, each expressed as Double values.

Each of the first ten conversion functions will return a Double containing the magnitude of the length in the requested units. If the length has a different core type than the requested unit, you will get an UncovertableLengthException unless you pass in the optional conversion parameters. There is also an alternate variant of these ten functions that ends in Length and returns a wrapped Length type instead of a Double.

The valueOf(length, unit) function is a factory function and should be called on the Length class instead of on any Length values. It returns a newly constructed Length value that represents the supplied length in the given units. The units correspond to the ten supported length types and are enumerated in the LengthUnit class.

Listing A-8 shows the Visage Length variables at work.

Listing A-8. Length Variables at Work

var onePixel = 1px;
var onePoint = 1pt;
var onePica = 1pc;
var oneDensityIndependentPixel = 1dp;
var oneMillimeter = 1mm;
var oneCentimeter = 1cm;
var oneInch = 1in;
var oneScaleIndependentPixel = 1sp;
var oneEm = 1em;
var onePercent = 1%;

println("onePixel = {%#s onePixel}");
println("onePoint = {%#s onePoint}");
println("onePica = {%#s onePica}");
println("oneDensityIndependentPixel = {oneDensityIndependentPixel}");
println("oneMillimeter = {%#s oneMillimeter}");
println("oneCentimeter = {%#s oneCentimeter}");
println("oneInch = {%#s oneInch}");
println("oneScaleIndependentPixel = {oneScaleIndependentPixel}");
println("oneEm = {oneEm}");
println("onePercent = {onePercent}");

println("3mm + 2mm = {%#s 3mm + 2mm}");
println("5mm - 2mm = {%#s 5mm - 2mm}");
println("3mm * 2 = {%#s 3mm * 2}");
println("2 * 3mm = {%#s 2 * 3mm}");
println("2cm * 2.5 = {%#s 2cm * 2.5}");
println("2.5 * 2cm = {%#s 2.5 * 2cm}");
println("3mm / 2 = {%#s 3mm / 2}");
println("2.5cm / 2.5 = {%#s 2.5cm / 2.5}");
println("2.5mm / 5.0mm = {2.5mm/5.0mm}");

println("oneInch.toCentimeters() = {oneInch.toCentimeters()}");
println("oneInch.toMillimeters() = {oneInch.toMillimeters()}");
println("oneInch.toDensityIndependentPixels() = {oneInch.toDensityIndependentPixels()}");
println("onePica.toPoints() = {onePica.toPoints()}");
println("onePoint.toDensityIndependentPixels() = {onePoint.toDensityIndependentPixels()}");

var tenInches = Length.valueOf(10, visage.lang.LengthUnit.INCH);
println("tenInches = {%#s tenInches}");

In Listing A-8, we made use of one of the special features of length literal formatting. By specifying the alternative formatting using %#s, the output will automatically be converted to the closest matching unit. This provides a much more readable program result than having all the output in density independent pixels.

Angle Type

Angles represent a rotational unit of measurement. They are commonly used in Visage to specify rotation of objects, such as in transformations.

Angle Literals and the Default Value

Angle literals are formed by appending an angle unit to an integer or decimal literal. Visage understands 3 angle units: degrees (deg), radians (rad), and turns (turn). The default value of an Angle variable is 0rad.

The following are some examples of Angle literals:

45deg
1rad
.3turn
Angle Arithmetic and Conversions

By their nature, Angle values can enter into arithmetic operations. Visage supports adding two Angle values, subtracting one Angle value from another, multiplying an Angle value by a numeric value, and dividing an Angle value by a numeric value. The results of these operations are Angle values. Visage also supports dividing one Angle value by another Angle value, resulting in a Number value. The negation operator also applies to Angle values.

When you perform arithmetic on two angles of the same type, it will be done in the original units reducing rounding errors. However, if the types differ they will first be converted to radians as the common unit and then the arithmetic will be performed.

The Backing Class of the Angle Type

The Angle type is backed by the Visage class visage.lang.Angle. Here are instance functions of the visage.lang.Angle class that should be of interest to you:

  • toDegrees()
  • toRadians()
  • toTurns()
  • valueOf(angle, unit)

Internally, Visage keeps track of Angle values as a double and unit pair. This reduces loss of precision when storing angles in any of the three types.

Each of the first three conversion functions will return a Double containing the magnitude of the angle in the requested units. An alternate variant of these functions ends in Angle and returns a wrapped Angle type instead of a Double.

The valueOf(angle, unit) function is a factory function and should be called on the Angle class instead of on any Angle values. It returns a newly constructed Angle value that represents the supplied angle in the given units. The units correspond to the three supported angle types and are enumerated in the AngleUnit class.

Listing A-9 shows the Visage Angle variables at work.

Listing A-9. Angle Variables at Work

var oneDegree = 1deg;
var oneRadian = 1rad;
var oneTurn = 1turn;

println("oneDegree = {oneDegree}");
println("oneRadian = {oneRadian}");
println("oneTurn = {oneTurn}");

println("3deg + 2deg = {3deg + 2deg}");
println("5deg - 2deg = {5deg - 2deg}");
println("3deg * 2 = {3deg * 2}");
println("2 * 3deg = {2 * 3deg}");
println("2rad * 2.5 = {2rad * 2.5}");
println("2.5 * 2rad = {2.5 * 2rad}");
println("3deg / 2 = {3deg / 2}");
println("2.5rad / 2.5 = {2.5rad / 2.5}");
println("2.5deg / 5.0deg = {2.5deg / 5.0deg}");

println("oneRadian.toDegrees() = {oneRadian.toDegrees()}");
println("oneTurn.toDegrees() = {oneTurn.toDegrees()}");
println("oneDegree.toTurns() = {oneDegree.toTurns()}");

var ninetyDegrees = Angle.valueOf(10, visage.lang.AngleUnit.DEGREE);
println("ninetyDegrees = {ninetyDegrees}");

Color Type

The Color type lets you specify different visual pigments in your application using a shorthand literal syntax. It is commonly used for fill and line color of shapes or gradients and color themes used in user interface components.

Color Literals and the Default Value

Color literals are formed by appending a list of hexadecimal red, green, and blue values to a hash symbol (#). You can also specify the alpha or opacity of the color as an optional fourth hexadecimal value separated by a pipe (|). The default value of a Color variable is #000000|FF, which is black with full opacity.

There are a few different syntax options you can use when specifying colors. If you do not need a high degree of precision, you can use single hexadecimal values (4-bit) instead of double hexadecimal values (8-bit). If you use 4-bit color values, you must use them for all the component values, and these will be automatically repeated to fill an 8-bit value.

Also, the alpha component is optional, and if omitted, will be assumed to be fully opaque with a value of FF. Finally, the case of the hexadecimal symbols is irrelevant.

The following are some examples of Color literals:

#BF40BF
#b5d
#000|C
#ba55d3|5d
Color Arithmetic

Using the regular infix arithmetic operators, you can perform simple blending and adjustment of colors. Visage supports adding two Color values, subtracting one Color value from another, multiplying a Color value by a numeric value, and dividing a Color value by a numeric value. The results of these operations are Color values. Visage also supports dividing one Color value by another Color value, resulting in a Number value. The negation operator also applies to Color values.

The arithmetic operations will behave the same as if you individually applied the same operation to each of the red, green, and blue component values individually. Since these values are stored as floats internally, you can freely overflow or underflow the color range while performing arithmetic. However, most uses will clip the value to the visible color range.

The Backing Class of the Color Type

The Color type is backed by the Visage class visage.lang.Color. Here are the instance functions of the visage.lang.Color class that should be of interest to you:

  • clip()
  • valueOf(hex:Integer, hasAlpha:Boolean)
  • color(red:Float, green:Float, blue:Float, opacity:Float)
  • hsb(hue:Float, saturation:Float, brightness:Float, opacity:Float)
  • hsl(hue:Float, saturation:Float, lightness:Float, opacity:Float)
  • rgb(red:Integer, blue:Integer, green:Integer, opacity:Float)

Internally, Visage keeps track of Color values as individual red, green, blue, and opacity values of type Float. This allows arithmetic to temporarily overflow or underflow the visible range. However, the clip() function can be used to return a Color with its values in the range from 0 to 1.

The remaining functions are factories that should be called on the Color class instead of on any Color values. They provide different methods of specifying colors in alternate encodings and color models. The valueOf(hex, hasAlpha) function accepts its argument in the form of an integer hexadecimal value that can optionally have the alpha component stored in the lower 8 bits. The color(red, green, blue, opacity) function is the closest to the internal representation, and should be used where precise color values need to be specified. The hsb and hsl functions allow you to specify colors using these two alternative color models. Finally, the rgb function is similar to the valueOf but takes its inputs in separate integer components. For all of the functions, the opacity argument is optional and will be assumed to be fully opaque if not specified.

In addition to these functions, the Visage Color class also contains constants for all of the Web “X11 Colors” from the CSS3 specification, which can be a convenient way of finding web-safe colors to use in your application.

Listing A-10 shows the Visage Color variables at work.

Listing A-10. Color Variables at Work

var a = Color.MAGENTA;
var b = Color.YELLOW;

println("Magenta = {a}");
println("Yellow = {b}");

println("Magenta + Yellow = {a + b}");
println("Magenta - Yellow = {a - b}");
println("Magenta * 2 = {a * 2}");
println("2 * Magenta = {2 * a}");
println("Magenta * 2.5 = {a * 2.5}");
println("2.5 * Magenta = {2.5 * a}");
println("Magenta / 2 = {a / 2}");
println("Magenta / 2.5 = {a / 2.5}");
println("Magenta / Yellow = {a / b}");

var navy = Color.valueOf(0x000080, false);
println("navy = {navy}");
var silver = Color.rgb(0xC0, 0xC0, 0xC0);
println("silver = {silver}");
var red = Color.color(1, 0, 0);
println("red = {red}");

Working with Sequences

In the previous section, you learned about Visage primitive types. Primitive types represent indivisible pieces of data. However, sometimes you need to aggregate individual data together and manipulate that data as a unit. Visage provides two mechanisms for data aggregation. First, you can group dissimilar data into classes, create objects of classes, and manipulate the objects in your code. Second, you can group similar data into a construct called a sequence and manipulate the sequence and its values in your code.

You will learn how to create objects of existing classes in the “Object Literals” section and how to create classes in the “Working with Classes” section, both later in this appendix.

In this section, we will teach you the Visage sequence concept, how to create sequences, and how to manipulate sequences.

Sequence Types

A Visage sequence represents an ordered list of data of the same type. Each individual piece of data in a sequence is an element of the sequence. The type of elements of a sequence is called the sequence’s element type. The type of a sequence is written as its element type followed by a pair of square brackets ([]).

Here are some examples of sequences of primitive types:

var booleans: Boolean[] = [true, false, true, true, false];
var integers: Integer[] = [l, 3, 5, 7, 9];
var numbers: Number[] = [3.14159, 2.71828];
var strings: String[] = ["hello", "hello again", "goodbye"];
var durations: Duration[] = [0.5m, 1.0m, 1.5m, 2.0m];

In these examples, the variables are of sequence types. With the help of Visage’s type inference facility, you can omit the data types:

var booleans = [true, false, true, true, false];
var integers = [l, 3, 5, 7, 9];
var numbers = [3.14159, 2.71828];
var strings = ["hello", "hello again", "goodbye"];
var durations = [0.5m, 1.0m, 1.5m, 2.0m];

The element type of a sequence can be one of the primitive types, a class type, or a function type. We will cover class types and function types in detail later in this appendix. In this section, we will use one small example of the class type to illustrate the interaction between sequence types and class types. For the rest of this section, we will use the following class:

class Point {
  var x: Number;
  var y: Number;
}

This defines a class type Point. You can declare a variable of type Point (called an object) and initialize it with an object literal as follows:

var p: Point = Point { x: 3.0, y: 4.0 }

Once again, you can omit the type specifier for the variable and let Visage's type inference facility deduce the type:

var p = Point { x: 5.0, y: 12.0 }

You can also declare a variable of a class type without giving it an initializer:

var p: Point;

In this case, the variable will get the default value for class types: null.

You are now ready to declare a variable of type Point[], a sequence of Point objects:

var points: Point[] = [Point {x: 3.0, y: 4.0}, Point {x: 5.0, y: 12.0}];

With the help of type inference, you can omit the type specifier:

var points = [Point {x: 3.0, y: 4.0}, Point {x: 5.0, y: 12.0}];

As is the case with primitive types, if a variable is declared to be of a sequence type but without an explicit initializer, it gets the default value of the sequence type. The default value of a sequence type is a sequence with zero elements in it. This sequence is called the empty sequence.

Constructing Sequences Using Explicit Sequence Expressions

In the previous section, all variables of sequence types are initialized with explicit sequence expressions. An explicit sequence expression is constructed by enclosing its elements within a pair of brackets. The elements in an explicit sequence expression are separated by commas. The comma may be omitted after an element that is an object literal or other expression that ends with a closing brace. Thus, the declaration

var points = [Point {x: 3.0, y: 4.0}, Point {x: 5.0, y: 12.0}];

can be written as

var points = [
  Point {x: 3.0, y: 4.0}
  Point {x: 5.0, y: 12.0}
];

You cannot create a sequence with “holes” in it. In other words, every element in a sequence must be a non-null value of the element type. Since primitive values are always non-null, this rule applies only to sequences of nonprimitive types.

The empty sequence can be constructed with the [] expression.

Constructing Numeric Sequences Using Range Expressions

When creating numeric sequences—that is, sequences of Integer or Number values—you can use range expressions in addition to explicit sequence expressions. A Visage range expression allows you to create an arithmetic progression from a start value, an end value, and an optional step value. The step value defaults to 1 or 1.0, depending on whether the element type is Integer or Number. Here are a few examples:

var oneToTen = [1..10];
var oneToTenOdd = [1..10 step 2];
var ticks = [3.0 .. 5.0 step 0.5];
var decreasing = [10..1 step -l];

images Tip Visage represents range expressions internally in an efficient manner. When you use a range expression like [0..1000000], Visage will not actually build a sequence with a million elements and therefore will not take up megabytes of memory.

In the previous range expressions, the start value and the end value are separated with two dots (..), and the step value, if present, is separated from the end value with step. This notation produces sequences that may include the end value itself. To obtain a sequence that does not include the end value, two dots and a less-than sign (..<) may be places between the start value and the end value. Here is an example of this form of range expression:

var oneToNine = [1 ..< 10];

images Caution If the step value is negative while the end value is greater than the start value, or if the step value is positive while the end value is less than the start value, the range expression produces an empty sequence. This may happen when you try to construct a decreasing sequence, one in which the end value is less than the start value, but you fail to specify a negative step value. For example, the range expression [10..1] will produce an empty sequence. The Visage compiler will generate a warning for such expressions.

In Listing A-11, you can see explicit sequence expressions and range expressions at work.

Listing A-11. Constructing Sequences with Explicit Sequence Expressions and Range Expressions

var booleans = [true, false, true, true, false];
var integers = [1, 3, 5, 7, 9];
var numbers = [3.14159, 2.71828];
var strings = ["hello", "hello again", "goodbye"];
var durations = [0.5m, 1.0m, 1.5m, 2.0m];

print("booleans = "); println(booleans);
print("integers = "); println(integers);
print("numbers = "); println(numbers);
print("strings = "); println(strings);
print("durations = "); println(durations);

class Point {
  var x: Number;
  var y: Number;
  override function toString() {
    "Point { x: {x}, y: {y} }"
  }
}

var points = [Point {x: 3.0, y: 4.0}, Point {x: 5.0, y: 12.0}];

print("points = "); println(points);

integers = [1, 3, 5, 7, 9];
print("integers = "); println(integers);

var oneToTen = [1..10];
var oneToTenOdd = [1..10 step 2];
var ticks = [3.0 .. 5.0 step 0.5];
var decreasing = [10..1 step -1];
var oneToNine = [1 ..< 10];

print("oneToTen = "); println(oneToTen);
print("oneToTenOdd = "); println(oneToTenOdd);
print("ticks = "); println(ticks);
print("decreasing = "); println(decreasing);
print("oneToNine = "); println(oneToNine);

print("[10..1] = "); println([10..1]);

In Listing A-11, we used another Visage built-in function, print(). The print() function differs from println() in that it does not append a new line at the end of its output.

images Note In Visage, converting a sequence to a string via "{seq}" will produce a string without the surrounding brackets and separators between elements. For example, println("{[1, 2, 3]}") prints 123, while println([1, 2, 3]) prints [ 1, 2, 3 ].

Manipulating Sequences

Visage provides a rich set of built-in facilities for you to easily manipulate sequences:

  • You can access the size of a sequence, a single element of a sequence, a slice or a segment of consecutive elements of a sequence, or a subset of nonconsecutive elements that satisfy certain criteria.
  • You can reverse a sequence.
  • You can insert an element into a sequence.
  • You can insert another sequence into a sequence.
  • You can delete an element from a sequence.
  • You can delete a slice from a sequence.

Each element in a sequence has an index and sequence indexes are zero-based. The first element has index 0, the second element has index 1, and so on.

Accessing the Size of a Sequence

You can use the size of operator to access the size of a sequence. The size of a sequence is the number of elements in the sequence. The size of an empty sequence is zero. In the following example

var integers = [1, 3, 5, 7, 9];
var s = sizeof integers;

the value of s would be 5.

images Note Although the size of operator is primarily used with sequences, you can use it with nonsequence variables and values. A primitive value always has size 1. A variable of class type has size 1 if it holds an object of the class, and it has size 0 if it is null.

Accessing an Element in a Sequence

To access a single element of a sequence, you use the sequence indexing expression. It consists of the sequence name or another expression that evaluates to a sequence followed by a pair of brackets that encloses the index of the element. Here are some examples:

var integers = [1, 3, 5, 7, 9];
var a = integers[0];
var b = integers[4];
var c = integers[-1];
var d = integers[5];

If the index is within the range between zero and one less than the size of the sequence, the appropriate element will be produced. If the index is outside of that range, the default value of the element type of the sequence will be produced. Thus in this example, a would be 1, b would be 9, and both c and d would be 0, the default value for the Integer type.

images Note Although indexing a variable of sequence type is the most common use of sequence indexing expressions, the Visage syntax allows you to index any expression that evaluates to a sequence. For example, [1, 3, 5, 7, 9][2] is a valid sequence indexing expression. Its value is 5. This is also the case for the sequence slice expression and the sequence select expression that you will learn in the next two sections.

Accessing a Slice of a Sequence

To access a consecutive subset of elements of a sequence, you use the sequence slice expression. It consists of the sequence name or another expression that evaluates to a sequence, followed by a pair of brackets that encloses a starting index and an optional ending index separated by two dots (..) or two dots and a less-than sign (..<). If the ending index is not specified, it is understood to be the index of the last element of the sequence, namely, the size of the sequence minus one. This expression produces a slice of the original sequence. Here are some examples:

var integers = [l, 3, 5, 7, 9];
var seql = integers[0..2];
var seq2 = integers[0..<2];
var seq3 = integers[2..];
var seq4 = integers[-3..10];
var seq5 = integers[2..<2];
var seq6 = integers[2..0];
var seq7 = integers[-2..-l];
var seq8 = integers[5..6];

If the two-dot notation is used, all elements of the original sequence whose index is greater than or equal to the starting index and less than or equal to the ending index are elements of the slice. If two dots and a less-than sign are used, all elements of the original sequence whose index is greater than or equal to the starting index and less than the ending index are elements of the slice. The elements in the slice appear in the same order as they appear in the original sequence.

If no element of the original sequence satisfies the slice’s condition, the slice is an empty sequence. This is the case, for example, if the slice’s ending index is less than the slice’s starting index, if the slice’s starting index is greater than or equal to the size of the sequence, or if the slice’s ending index is less than zero. Notice that it is okay for the starting index to be less than zero or for the ending index to be greater than or equal to the size of the sequence.

Thus in the previous example, seql is [l, 3, 5], seq2 is [l, 3], seq3 is [5, 7, 9], and seq4 is [l, 3, 5, 7, 9]. And seq5, seq6, seq7, and seq8 are equal to the empty sequence []. The conditions in seq5 and seq6 are not satisfied by any indexes. The condition in seq7 is satisfied only by -2 and -1, but integers does not contain elements with indexes -2 or -1. Similarly, the condition in seq8 is satisfied by 5 and 6, but integers does not contain elements with indexes 5 or 6.

Accessing a Subset of a Sequence Through a Predicate

To access a not-necessarily-consecutive subset of elements of a sequence, you use the sequence select expression. It consists of

  • The sequence name or another expression that evaluates to a sequence
  • A pair of brackets that encloses a predicate in the form of a selection variable
  • A pipe (|)
  • A Boolean expression involving the selection variable

Here are some examples:

var integers = [l, 3, 5, 7, 9];
var seq1 = integers[x | x > 4];
var seq2 = integers[x | indexof x <2];
var seq3 = integers[x | x > 10];

The resulting sequence will contain all elements of the original sequence whose values satisfy the predicate. A value satisfies a predicate if the Boolean expression in the predicate is true when the value is substituted for the selection variable. The indexof operator can be used inside the predicate to obtain the index of the selection variable x in the original sequence. If no elements of the original sequence satisfy the predicate, the resulting sequence is the empty sequence. The elements in the resulting sequence appear in the same order as they appear in the original sequence.

Thus, in the previous example seq1 is [5, 7, 9], seq2 is [l, 3], and seq3 is the empty sequence.

Reversing a Sequence

Visage provides a reverse operator to reverse a sequence. The reverse operator does not modify the original sequence but produces a new sequence that contains the same elements as the original sequence in the reverse order. Here is an example:

var integers = [1, 3, 5, 7, 9];
var seq1 = reverse integers;

In this example, seq1 is [9, 7, 5, 3, 1].

Inserting an Element into a Sequence

So far, you’ve learned four methods of accessing the elements of a sequence. One thing that these methods have in common is that they do not change the original sequence in any way. Coming up, we will show you methods for altering an existing sequence.

To add one element to a sequence, you use the insert expression. Only a value of a compatible type may be inserted into a sequence. The only time you are allowed to insert a value of one primitive type into a sequence of a different type is when you are inserting a numeric value into a sequence of a different numeric type. You may lose precision when the sequence's element type is narrower than the type of the value being inserted. For example, inserting a Number value into an Integer sequence will cause the Number value's fractional part to be dropped. For class types, you are allowed to insert an object of class type into a sequence of its superclass type. You will learn about superclasses in the section entitled “Extending Classes” later in this appendix.

There are three forms of insert expressions: the insert-into form, the insert-before form, and the insert-after form. Here are some examples:

var numbers = [3.14159, 2.71828];
insert 0.57722 into numbers;
insert 1.618 before numbers[0];
insert 1.4142 after numbers[2];

The insert-into form takes two pieces of information as its input: the value to be inserted and the sequence variable to insert the value into. It then appends the value to the end of the sequence. Thus, after the first insert expression in the previous example, numbers will be [3.14159, 2.71828, 0.57722].

The insert-before form takes three pieces of information as its input: the value to be inserted, the sequence variable to insert the value into, and an index. It then inserts the value into the sequence at a position just before the index. Thus, after the second insert expression, numbers will be [1.618, 3.14159, 2.71828, 0.57722].

The insert-after form takes the same three pieces of information as the insert-before form as its input and inserts the value into the sequence at a position just after the index. Thus, after the third insert expression, numbers will be [1.618, 3.14159, 2.71828, 1.4142, 0.57722].

Attempting to insert an element after an invalid index will keep the sequence unchanged. Attempting to insert an element before an invalid index will also keep the sequence unchanged, except when the index is equal to the size of the sequence (in that case, the element is appended to the sequence).

images Caution The way you provide the requisite information to the three insert forms is somewhat unconventional. It is designed to make the whole expression easy to remember. Although the last part of the insert-before and insert-after forms looks identical to an element access expression, it is not one.

Inserting Another Sequence into a Sequence

In addition to inserting a single element into a sequence, Visage supports inserting another sequence into a sequence. To do so, you can use the same three forms of insert expressions you just learned As with a single element insertion, the element type of the other sequence must be compatible with the target sequence.

Notice that after the insertion, the original sequence is still a flat sequence, only with more elements. Visage does not support nested sequences.

Attempting to insert an empty sequence will keep the original sequence unchanged. Attempting to insert null into a sequence will also keep the original sequence unchanged.

Here is an example:

var strings = ["hello", "hello again", "goodbye"];
insert ["how are you", "see you"] after strings[l];

After this insertion, the sequence strings will be ["hello", "hello again", "how are you", "see you", "goodbye"].

Deleting Elements from a Sequence

To delete elements from a sequence, you use the delete expression. There are four forms of delete expressions, as you can see here:

var strings = ["hello", "hello again", "how are you", "see you", "goodbye"];
delete "see you" from strings;
delete strings[2];
delete strings[0..1];
delete strings;

The delete from form takes two pieces of information as its input: the value to be deleted and the sequence variable to delete the value from. It then deletes all occurrences of the value from the sequence. The type of the value must be compatible with the element type of the sequence. If the value does not occur in the sequence, the sequence is unchanged. Thus, after the first delete expression in the previous example, strings will be ["hello", "hello again", "how are you", "goodbye"].

The other three forms are variants of the delete form. The first variant takes two pieces of information as its input: a sequence variable and an index. It then deletes the element at the index from the sequence. If the index is not valid, the sequence remains unchanged. Thus, after the second delete expression, strings will be ["hello", "hello again", "goodbye"].

The second variant takes three pieces of information as its input: a sequence variable, a starting index, and an optional ending index. It then deletes the slice from the sequence. The information is arranged in a form reminiscent of a sequence slice expression. Both the .. and the ..< forms are supported. If the ending index is not specified, it is understood to be the index of the last element of the sequence. Thus, after the third delete expression in the previous example, the string will be ["goodbye"].

The third variant takes only one piece of information, a sequence variable, and deletes all elements from the sequence. Thus, after the fourth delete expression, the string will be the empty sequence, [].

images Caution Although the last part of the first and the second variants of the delete form looks identical to a sequence indexing expression or a sequence slice expression, it is not one.

Sequence Manipulation Example

Listing A-12 shows sequence manipulation constructs at work.

Listing A-12. Manipulating Sequences

var integers = [1, 3, 5, 7, 9];

print("integers = "); println(integers);
print("sizeof integers = "); println(sizeof integers);
print("integers[0] = "); println(integers[0]);
print("integers[4] = "); println(integers[4]);
print("integers[-1] = "); println(integers[-1]);
print("integers[5] = "); println(integers[5]);

print("integers[0..2] = "); println(integers[0..2]);
print("integers[0..<2] = "); println(integers[0..<2]);
print("integers[2..] = "); println(integers[2..]);
print("integers[-3..10] = "); println(integers[-3..10]);

print("integers[2..<2] = "); println(integers[2..<2]);
print("integers[2..0] = "); println(integers[2..0]);
print("integers[-2..-1] = "); println(integers[-2..-1]);
print("integers[5..6] = "); println(integers[5..6]);

print("integers[x | x > 4] = "); println(integers[x | x > 4]);
print("integers[x | indexof x < 2] = "); println(integers[x | indexof x < 2]);
print("integers[x | x > 10] = "); println(integers[x | x > 10]);

print("reverse integers = "); println(reverse integers);

var numbers = [3.14159, 2.71828];
print("numbers = "); println(numbers);

insert 0.57722 into numbers;
print("numbers = "); println(numbers);

insert 1.618 before numbers[0];
print("numbers = "); println(numbers);

insert 1.4142 after numbers[2];
print("numbers = "); println(numbers);

var strings = ["hello", "hello again", "goodbye"];
print("strings = "); println(strings);

insert ["how are you", "see you"] after strings[1];
print("strings = "); println(strings);

delete "see you" from strings;
print("strings = "); println(strings);

delete strings[2];
print("strings = "); println(strings);

delete strings[0..1];
print("strings = "); println(strings);

delete strings;
print("strings = "); println(strings);

Comprehending Sequences

Sequences play an important role in Visage. They are a versatile container of application objects. The explicit sequence expression syntax, together with the object literal syntax, form the basis of the declarative GUI programming style that is a distinguishing characteristic of Visage applications.

Visage allows you to do more with sequences using the for expression, which produces new sequences based on one or more existing sequences. Following the functional programming language tradition, syntaxes for generating new sequences from existing ones are called sequence comprehension.

The for expression starts with the for keyword, which is followed by one or more comma-separated in clauses enclosed in a pair of parentheses (()). Each in clause may have an optional where clause. The in clauses are followed by the body of the for expression. The following is a simple example of a for expression:

for (x in [1..4]) x*x

Its in clause has the form x in [1..4] and its body is the expression x*x. It produces the sequence [1, 4, 9, 16].

An in clause starts with a variable name followed by the in keyword and a sequence expression. The variable named in the in clause is called the iteration variable. The optional where clause, if present, follows the in clause with the where keyword and a Boolean expression involving the iteration variable of the in clause. The following example shows a for expression with a where clause:

for (x in [1..4] where x > 2) x*x

Its in clause has the form x in [1..4] where x > 2. The where clause serves to filter out some of the elements from the sequence in the in clause. This for expression produces the sequence [9, 16].

When a for expression has multiple in clauses, the iteration variable names of the in clauses must be distinct. The elements of the resulting sequence are ordered as if an iteration variable in a later in clause varies faster than iteration variables in earlier in clauses. Therefore, in the following example:

var rows = ["A", "B"];
var columns = [1, 2];
var matrix = for (row in rows, column in columns) "{row}{column}";

the resulting sequence matrix will be ["A1", "A2", "B1", "B2"]. The sequences iterated by the different in clauses need not be different sequences, as shown here:

var digits = [1, 2, 3];
var seq = for (x in digits, y in digits) "{x}{y}";

The resulting sequence seq will be ["11", "12", "13", "21", "22", "23", "31", "32", "33"].

In a for expression with multiple in clauses, the where clause associated with a later in clause may refer to iteration variables of earlier in clauses. However, the where clause associated with an earlier in clause cannot refer to iteration variables of later in clauses. In other words, the scope of an iteration variable of an in clause is its own where clause, the where clause of later in clauses, and the body of the for expression. You will learn more about scopes of variables in Visage later in this appendix when we talk about Visage expressions. In the following example, the where clause of the second in clause refers to the iteration variable of both the first and the second in clauses:

var digits = [l, 2, 3];
var seq = for (x in digits where x > 1, y in digits where y >= x) {
  "{x}{y}"
}

The resulting sequence seq will be ["22","23","33"]. This example also illustrates the use of a block expression as the body of a for expression. You will learn more about block expressions in the “Visage Expressions” section later in this appendix.

In Listing A-13, you can see sequence comprehension at work.

Listing A-13. Sequence Comprehension

var seq = for (x in [1..4]) x*x;
print("seq = "); println(seq);

seq = for (x in [1..4] where x > 2) x*x;
print("seq = "); println(seq);

var rows = ["A", "B"];
var columns = [1, 2];
var matrix = for (row in rows, column in columns) "{row}{column}";
print("matrix = "); println(matrix);

var digits = [1, 2, 3];
var seq1 = for (x in digits, y in digits) "{x}{y}";
print("seq1 = "); println(seq1);

var seq2 = for (x in digits where x > 1, y in digits where y >= x) {
  "{x}{y}"
}
print("seq2 = "); println(seq2);

Using Utility Functions in visage.util.Sequences

The Visage runtime includes the class visage.util.Sequences, which provides some useful sequence manipulation functions. It includes the following functions:

  • binarySearch(seq, key)
  • binarySearch(seq, key, comparator)
  • indexByIdentity(seq, key)
  • indexOf(seq, key)
  • isEqualByContentIdentity(seql, seq2)
  • max(seq)
  • max(seq, comparator)
  • min(seq)
  • min(seq, comparator)
  • nextIndexByIdentity(seq, key, pos)
  • nextIndexOf(seq, key, pos)
  • reverse(seq)
  • shuffle(seq)
  • sort(seq)
  • sort(seq, comparator)

All of the functions take at least one argument of the sequence type. A sequence that is passed in as a parameter is not modified by the functions. A new sequence is returned instead if necessary.

Some functions have a variant that takes an additional comparator argument. The variant that takes a comparator is necessary only if the element type of the sequence does not have its own natural ordering or if you want to override the natural ordering. All Visage primitive types have a natural ordering. A comparator is an object of a Visage or Java class that implements the java.util.Comparator Java interface. We will explain how to define Visage classes and how to use Visage’s Java interoperability later in this appendix.

A few of the methods deal with identities of elements in sequences. You will learn about identities of Visage objects in the "Relational Operators" section later in this appendix. For now, it suffices to say that every Visage object has an identity and a value, and object comparisons in Visage are usually carried out by comparing object values. However, under some special circumstances it is necessary to compare object identities. Values of primitive types have values but not identities.

The binarySearch() function takes a sorted sequence and a key (and an optional comparator) and uses a binary search algorithm to find the index of the key in the sequence. The result is a meaningless integer if the sequence is not sorted. The result is the index of the key in the sequence if the key appears in the sequence. If the key appears multiple times, one of the indexes is returned, but you cannot tell which one. If the key does not appear in the sequence, a negative integer is returned.

The indexOf() and indexByIdentity() functions take a sequence and a key and find the index of the first occurrence of the key in the sequence. If the key does not appear in the sequence, -1 is returned.

The nextIndexOf() and nextIndexByIdentity() functions take a sequence, a key, and a starting position and find the index of the first occurrence of the key in the sequence on or after the specified position. If the key does not appear on or after the specified position, -1 is returned.

The isEqualByContentIdentity() takes two sequences and determines if the sequences contain the same elements according to object identity.

The max(), min(), reverse(), shuffle(), and sort() functions work as their names suggest. A runtime exception will be thrown if an empty sequence is passed to the max() and min() functions.

Listing A-14 uses some of the utility functions.

Listing A-14. Sequence Utility Functions

import visage.util.Sequences.*;

var seq = [1, 4, 2, 8, 5, 7];
print("seq = "); println(seq);

println("The index of 4 in seq = {indexOf(seq, 4)}");
println("The max value of seq = {max(seq)}");
println("The min value of seq = {min(seq)}");

print("reverse(seq) = "); println(reverse(seq));
print("shuffle(seq) = "); println(shuffle(seq));

var sorted = sort(seq);
print("sortd = "); println(sorted);

var index = binarySearch(sorted, 4);
println("Found 4 in sorted at index {index}");

var integers = [1, 3, 5, 3, 1];
print("integers = "); println(integers);
println("indexOf(integers, 3) = {indexOf(integers, 3)}");
println("nextIndexOf(integers, 3, 2) = {nextIndexOf(integers, 3, 2)}");

In Listing A-14, the import statement import visage.util.Sequences.*; allows you to call the functions of the class. We will cover import statements in more detail in the section entitled “Import Directives” later in this appendix.

Visage Expressions

A Visage expression is a chunk of Visage code that the Visage compiler understands. The compiler will generate code that evaluates Visage expressions into Visage values. The values are fed into yet more expressions, which evaluate to more values, leading eventually to the solution to your problem.

Expressions and Their Types

Visage understands many kinds of expressions, and all executable Visage code is composed of expressions. Every expression has some expectation for its constituent parts and makes certain guarantees for the value it produces. If these expectations are not met, the compiler will reject the program and report an error. For example, the expression a and b expects its operands to be values of type Boolean and produces a Boolean value as a result. The compiler will flag the expression 3 and 4 as an error. As another example, consider the variant of the delete expression that deletes all elements from a sequence. This expression expects the operand following delete to be a variable of the sequence type and produces no results. The compiler will flag the expression delete 5; as an error. The expression delete [1, 3, 5, 7, 9]; is similarly in error because its operand, although a sequence, is not a variable of the sequence type but rather an explicit sequence expression. These checks are called type checks.

And because the Visage compiler performs type checks at compile time, Visage falls into the category of statically typed programming languages. In Visage, all variables have a static type: it is either explicitly specified or inferred. The type of a variable cannot be changed during the lifetime of the variable. This is another benefit of type checking.

Expressions such as the delete expression that produce no results are said to be of the void type. The void type is a special type. There could never be a value of the void type, and you cannot declare a variable to be of the void type. You can use Void as a function’s return type to indicate that the function returns nothing. Expressions that are not of the void type are called value expressions.

images Note There is a difference between an expression being of the void type and having the value null. An expression of the void type can never have a value, not even null. On the other hand, if an expression is capable of having a null value, it is capable of having a non-null value.

Block Expression

A block expression is formed by enclosing a number of other expressions within a pair of braces ({}). The type of the block expression is the type of the last expression it encloses. If the last expression is not of the void type, then the value of the last expression is the value of the block expression. Here is an example:

var x = {
  var a = 3;
  var b = 4;
  a*a + b*b
}

The block in the example contains three expressions: two variable declaration expressions and an arithmetic expression. After execution, the variable x will have the value 25.

Blocks introduce a new scope. Variables declared inside the block are not visible to code outside the block. You cannot declare a variable with the same name as another variable in the current block level or the surrounding level, up to the enclosing function or class.

In the next example, we use a block of the void type:

var a = 3;
var b = 4;
{
  var s = a*a + b*b;
  println("s = {s}");
}

Since the block is of the void type, we cannot assign it to a variable. The block only serves to confine the scope of the variable s.

Precedence and Groupings

When presented with a compound expression that involves multiple operators, Visage will carry out the operations in accordance with the precedence assigned to each operator. For operators of the same precedence, Visage will carry out the operations in accordance to the associativity assigned to the operators. For example, the well-known precedence rules for arithmetic operators are observed in Visage. Thus, the value of 1 + 2 * 3 is 7 rather than 9, and the value of 6 / 2 * 3 is 9 rather than 1.

A pair of parentheses can be used to force the operations to be done in a different order. Thus, the value of (1 + 2) * 3 is 9, and the value of 6 / (2 * 3) is 1. Only value expressions can be surrounded by parentheses.

Expression Separator

The semicolon (;) serves as an expression terminator. You have seen its use in all the example programs in this book so far. Some expressions have a natural termination point. For example, both the block expression you learned in this section and the for expression you learned in the previous section naturally terminate at the closing brace. For such expressions, the semicolon is optional; in other words, the compiler will not specifically look for a semicolon at these locations, but if a semicolon is present, the compiler will not complain either. A few expressions that you will learn later in this appendix—such as the while expression, one form of the if expression, and the object literal expression—also fall into this category.

The semicolon is also optional after the last expression in a block. For all other expressions, a semicolon is required.

Variable and Constant Declarations

In Visage, variable declarations are expressions. They are called variable declaration expressions. Here are examples of the basic forms of variable declaration expressions:

var a;
var b: Integer;
var c: Number = 3.14;
var d = "Hello, World.";
var e = bind c;
var f = d on replace { println("f changed.") }

Here we declared six variables:

  • Variable a is declared with neither a type nor an initializer.
  • Variable b is declared to be of type Integer but without an initializer.
  • Variable c is declared to be of type Number and initialized to the value 3.14.
  • Variable d is declared without a type specifier and initialized to the string "Hello, World.".
  • Variable e is declared without a type but with a binding.
  • Variable f is declared without a type but with an initializer and a trigger.

A variable declaration is introduced by the keyword var followed by a variable name and an optional type specifier, an optional value expression or bind expression, and an optional trigger. We will provide an in-depth coverage of bind expressions in the section entitled “Working with Data Bindings” and cover triggers in more detail in the section entitled “Triggers,” both later in this appendix.

Notice that the colon that separates the variable name and the type is omitted if the type is omitted. If a type is not specified, the type of the variable is determined by Visage’s type inference facility. If an initializer is given in the variable declaration, the type of the initial value is taken as the type of the variable. Otherwise, the type of the variable is determined by the first subsequent assignment to the variable. If the variable is never assigned a value in the program, the variable is taken to be of type Object, which is a class type.

Once the compiler determines the type of a variable that is declared without a type, it will treat the variable as if it is declared with the inferred type.

If the type is specified, the type of the initializer value or the binding expression must be compatible with the specified type.

images Caution It is generally a good idea to either specify a type or an initializer in a variable declaration. If neither is specified, only nonsequence values can be assigned to the variable.

Constants are named values that cannot be subsequently assigned. They are declared in constant declaration expressions. A constant declaration is introduced by the keyword def followed by a constant name and an optional type specifier, a required value expression or bind expression, and an optional trigger. Here are some examples:

def PI = 3.14159;
def GREETING = "Hello";
var x = 1024;
def y = bind x;

Although a constant can never be assigned a new value, its value may change if it is declared with a data binding and the expression it binds to changes.

Variable and constant names cannot be keywords and must be unique within the same function.

Assignment Operator

The assignment expression assigns a new value to a previously declared variable. The second line in the following example is an assignment expression:

var x: Integer = 1024;
x = 2048;

The assignment expression consists of a variable name followed by the equal sign (=) and an expression. The value on the right-hand side must have a type that is compatible with the type of the variable. After the assignment expression, the value of the variable will be the value on the right-hand side.

If the variable is declared without a type and without an initializer or binding, the first subsequent assignment will determine the variable’s type.

The assignment expression itself, considered as an expression, has a value that is the same as the value that is assigned to the variable. You can chain several assignments together, as shown in the following code:

var a;
var b;
a = b = 3;

The assignment operator is right associative. Thus, the third line in the previous code is equivalent to a = (b = 3). Therefore, b is assigned the value 3, and then a is assigned the value of the expression b = 3, which is also 3.

Compound Assignment Operators

The compound assignment expression performs an arithmetic operation between the value of the left-side variable and the value of the right-side expression and assigns the result to the variable. The second to the fifth lines of the following example are compound assignment expressions:

var x: Integer = 1024;
x += 1;
x -= 2;
x *= 3;
x /= 4;

The compound assignment expression consists of a variable name followed by one of the compound assignment operators (+=, -=, *=, /=) and an expression. The value of the variable and the value of the expression must be numeric or duration values. The appropriate arithmetic operations indicated by the compound assignment operator are performed and the result assigned to the variable. Thus, x += 1 behaves the same as x = x + 1, and x will be 1025 after it. Similarly, x -= 2 behave the same as x = x - 2, and x will be 1023 after it. And x will be 3069 after x *= 3, and 767 after x /= 4.

Compound assignment operations play the same role as the regular assignment operation in inferring variable types.

The compound assignment expression itself has a value that is the same as the value assigned to the variable. The compound assignment operators are right associative and can be chained together, although such chaining is rarely used.

Relational Operators

Visage supports six relational operators: the equals operator (==), the not-equals operator (!=), the less-than operator (<), the less-than or equal operator (<=), the greater-than operator (>), and the greater-than or equal operator (>=).

The relational expression consists of a left-side expression followed by a relational operator and a right-side expression. The equals and the not-equals operators can be used to compare values of any types, whereas the other four operators can be used only to compare values of numeric or duration types.

The Visage equals operator performs value comparisons. For primitive types, this gives you intuitive results. For example, the expressions true == true, 3 == 3, 4 == 4.0, 5.5 == 5.5, "hello" == "hello", and 1m == 60s all evaluate to true.

For class types, value comparison is done using the equals() instance function of the class. You will learn more about classes later in this appendix. For now it is enough to know that value comparison for class types can be controlled by the programmer. The default behavior of the equals() instance function is to perform object identity comparisons. In this comparison, each newly created object is not equal to any previously created objects.

images Caution If you are familiar with the Java programming language, you should recognize that the semantics of the == operator in Visage is different from that of the == operator in Java, where the former performs value comparisons and the latter performs object identity comparisons.

Two sequences are equal if they have the same size and if, for each valid index, the corresponding elements are equal.

Listing A-15 shows some of the expressions you have learned in this section at work.

Listing A-15. Basic Expressions

// block expressions
var x = {
  var a = 3;
  var b = 4;
  a*a + b*b
};
println("The value of x is {x}");

// precedence and groupings
println("1 + 2 * 3 = {1 + 2 * 3}");
println("(1 + 2) * 3 = {(1 + 2) * 3}");
println("6 / 2 * 3 = {6 / 2 * 3}");
println("6 / (2 * 3) = {6 / (2 * 3)}");

// var and def
var o;
var i: Integer;
var n: Number = 3.14;
var str = "Hello, World.";
var j = bind i;
var greeting = str on replace { println("greeting changed") };

def PI = 3.14159;
def k = bind i;
// assignment and type inference
var v1;
println("Before: v1 = {v1}");
v1 = 42;
println("After: v1 = {v1}");

class Point {
  var x: Number;
  var y: Number;
  override function toString() {
    "Point { x: {x}, y: {y} }"
  }
}
var v2;
println("Before: v2 = {v2}");
v2 = Point {x: 3, y: 4};
println("After: v2 = {v2}");

// compound assignment
x = 1024;
println("x = {x}");
x += 1;
println("x = {x}");
x -= 2;
println("x = {x}");
x *= 3;
println("x = {x}");
x /= 4;
println("x = {x}");

// relational operators
println("true == true is {true == true}");
println("3 == 3.0 is {3 == 3.0}");
println('"hello" == "hello" is {"hello" == "hello"}'),

println("3.14159 > 2.71828 is {3.14159 > 2.71828}");
println("1h < 100m is {1h < 100m}");

var p1 = Point {x: 3, y: 4};
var p2 = Point {x: 3, y: 4};
println("p1 == p1 is {p1 == p1}");
println("p1 == p2 is {p1 == p2}");

While Expression

A while expression is introduced by the while keyword, followed by a pair of parentheses that encloses a condition, which must be an expression of type Boolean, and a body expression after the closing parenthesis. Although the syntax allows any expression to be the body, a block is the most common body of while expressions. A semicolon is required to terminate the while expression if the body is not a block.

First, the condition is checked. If it is true, the body of the while expression is evaluated, and the condition is checked again. As long as the condition is true, the body is executed repeatedly. The while expression itself is of the void type, so you cannot assign a while expression to a variable as you can with blocks.

The following code prints the squares of the first ten natural numbers:

var i = 1;
while (i <= 10) {
  println("{i} squared: {i * i}");
  i += 1;
}

You can use the keyword break to break out of a while expression. The keyword continue can be used to skip the rest of the code in one iteration. If there are multiple nested loops in your code, these keywords only affect the innermost loop that encloses them. In the following code, the first loop prints natural numbers up to 7, and the second prints only the even ones:

var i = 1;
while (i <= 10) {
  if (i > 7) {
    break;
  } else {
    println(i);
  }
  i += 1;
}

var j = 1;
while (j <= 10) {
  if (j mod 2 != 0) {
    j += 1;
    continue;
  } else {
    println(j); j += 1;
  }
}

Revisiting the for Expression

Because of its close relationship with sequence comprehension, we covered the for expression in the “Working with Sequences” section earlier in this appendix. Recall that a for expression is introduced by the for keyword, followed by a pair of parentheses that enclose one or more comma-separated in clauses, and an expression after the closing parenthesis.

Strictly speaking, a for expression is sequence comprehension only if its body is a value expression. If the body of a for expression is of the void type, the for expression is more like the while expression and exhibits a loop behavior. Here is an example that prints the first ten natural numbers:

for (x in [1..10]) {
  println(i);
}

As is the case for while loops, the keywords break and continue can be used in for loops. Again, you can use the break keyword to break out of a for loop and the continue keyword to skip the rest of the code in one iteration. In the following code, the first for loop prints natural numbers up to 7, and the second prints only the even ones:

for (x in [1..10]) {
  if (x > 7) {
    break;
  } else {
    println(x);
  }
}

for (x in [1..10]) {
  if (x mod 2 != 0) {
    continue;
  } else {
    println(x);
  }
}

images Caution The syntax of the for expression allows the break and continue keywords to be used in any kind of for expression. However, with the current Visage compiler, using these keywords in sequence comprehension will cause either a compiler crash or a runtime exception.

If Expression

The if expression is introduced by the if keyword, followed by a pair of parentheses that enclose a condition, which must be an expression of type Boolean; a then clause after the closing parenthesis; and an optional else clause. The then clause has two forms: it can either be an expression or the then keyword followed by an expression. The else clause is the else keyword followed by an expression. The then keyword is customarily used in short if expressions where both the then and else clauses contain simple nonblock expressions. Here are some examples:

// short form if expression
var x = if (i == j) then 1 else 0;
// long form if expression
if (x < 0) {
  println("{x} < 0");
} else {
  println("{x} >= 0");
}

In the short-form if expression, the expressions for the then and else clauses are simple Integer values. Thus, the entire if expression is of type Integer. In the long-form if expression, the expressions for the then and else clauses are both block expressions of the void type. Consequently, the entire if expression is of the void type and cannot be assigned to another variable.

In general, the type of an if expression is determined by the types of the two expressions in the then and else clauses. If both expressions are of the same type, the entire if expression is of that type. If one of them is of the void type, the entire if expression is of the void type. If the else clause is omitted, the if expression is of the void type.

The situation becomes more complicated if the two expressions are of different types. In such situations, Visage will attempt to find a type that will accommodate both expressions. Consider the following examples:

var x = if (true) then 3 else "4";
var y = if (true) then 5 else [6];

After the assignments, the variable x will have the type Object and value 3, and y will have the type Integer[] and value [5]. In practice, if expressions with dissimilar then and else clause expressions are rarely needed.

The else clause of one if expression can be another if expression. This joined if expression allows you to test for multiple conditions, as shown here:

if (x <o) {
  println("{x} < 0");
} else if (x == 0) {
  println("{x} = 0");
} else {
  println("{x} > 0");
}

Listing A-16 shows the looping and conditional expressions at work.

Listing A-16. Looping and Conditional Expressions

// while loop
var i = 1;
while (i <= 10) {
  println("{i} squared: {i * i}");
  i += 1;
}

// break from while loop
var j = 1;
while (j <= 10) {
  if (j mod 2 != 0) {
    j += 1;
    continue;
  } else {
    println(j);
    j += 1;
  }
}

// continue in for loop
for (x in [1..10]) {
  if (x > 7) {
    break;
  } else {
    println(x);
  }
}

// if expressions
var k = if (i == j) then 1 else 0;

if (k < 0) {
  println("{k} < 0");
} else {
  println("{k} >= 0");
}

for (x in [-1..1]) {
  if (x < 0) {
    println("{x} < 0");
  } else if (x == 0) {
    println("{x} = 0");
  } else {
    println("{x} > 0");
  }
}

// if expression with dissimilar then and else clauses
var a = if (true) then 3 else "4";
println("a = {a}");
// assign an Integer to a
a = 5;
println("a = {a}");
// assign a Stirng to a
a = "hi";
println("a = {a}");

var b = if (true) then 7 else [8];
print("b = "); println(b);

In this section, we covered some of the most basic expressions in Visage. These expressions are building blocks for larger pieces of code.

Object Literals

This section examines class types and their objects. We'll begin with using Visage classes since it is easier to use them than to write them, and there are plenty of classes already written for you in the Visage JavaFX API. Writing your own classes is covered in the section entitled “Working with Classes” later in this appendix.

Classes and Objects

The class is a unit of code that encapsulates a data model and functions that manipulate the data model. A class contains instance variables, instance functions, and initialization blocks. The instance variables represent the data modeled by the class, and the instance functions perform computations based on the values of the instance variables. The initialization blocks set up the initial values of the instance variables in a way that is meaningful to the class.

To use a class, you must instantiate it. When you instantiate a class, the Visage runtime system allocates memory to hold all the instance variables of the class and initialize the memory according to the initializers and initialization blocks. This properly initialized memory is called an object of the class. It is also called an instance of the class.

The runtime system may also perform some other housekeeping chores before it hands you an object reference. The object reference allows you to read from and write to its instance variables that are accessible to you as specified by the class. It also allows you to call its instance functions that are accessible to you as specified by the class.

When you are finished with an object reference, you don’t have to do anything special. You simply let it go out of scope. The runtime system will figure out that you will never use that object reference again and reclaim the memory that it occupies. This process is called garbage collection.

You can instantiate a class multiple times to create multiple objects of the class. Different objects of the same class may have the same set of instance variables, but each object’s instance variable values are independent of every other object’s instance variable values.

The Object Literal Expression

To instantiate a Visage class, you use an object literal expression. Unlike some of the expressions you encountered in the previous section, the object literal expression is quite involved and sometimes occupies numerous program lines.

To illustrate this point, we’ll pick a class from the Visage JavaFX API whose fully qualified name is visage.javafx.scene.shape.Circle and try to instantiate it in various ways. We will refer to it by its simple name, Circle. Since this class represents an onscreen circle, when you compile and run the snippets of codes in this section, a window will pop up with a circle drawn in it. You will need to close the window manually when you are finished examining its content.

To instantiate a class, you must know what the class offers. You get that information by reading the Visagedoc of the class. “Visagedoc” is the documentation that is generated directly from the source code using the Visagedoc tool, which comes with the Visage distribution.

Initializing Instance Variables

According to the API documentation, the Circle class defines three instance variables: centerX, centerY, and radius, all of type Number and all having a default value of 0.0. We can read, write, and initialize all three instance variables. In the following example, we instantiate a circle of radius 100.0 centered at the point (100.0, 100.0):

import visage.javafx.scene.shape.Circle;
var circle = Circle { centerX: 100.0, centerY: 100.0, radius: 100.0 }

An object literal expression starts with the name of the class, followed by a pair of braces that encloses the following parts:

  • Instance variable initializers
  • Variable declarations
  • Instance variable overrides
  • Instance function overrides
  • Default instance variable initializer

In the previous example, we supplied three instance variable initializers. An instance variable initializer consists of the instance variable name followed by a colon and a value or a bind expression. You will learn about bind expressions in the next section. The expression must be of a type that is compatible with the type of the instance variable. If multiple instance variable initializers are present, they can be separated by commas, semicolons, or spaces. Commas are typically used when the initializers all appear on the same line, and white spaces are typically used when the initializers contain complicated expressions and must be presented one per line. We could have written this example in a multiline form:

import visage.javafx.scene.shape.Circle;
var circle = Circle {
  centerX: 100.0
  centerY: 100.0
  radius: 100.0
}
Default Instance Variables

Some classes mark one of the variables as being the default. For example, the Circle class has the radius property marked as the default. In an object literal expression, you can initialize the default instance variable by simply including a value with no label:

import visage.javafx.scene.shape.Circle;
var circle = Circle {100.0}

This is a shorthand form to create a circle that has a radius of 100 centered at (0, 0).

In addition, you can use this together with the instance variable initializers to do more complex initialization:

import visage.javafx.scene.shape.Circle;
var circle = Circle {
  centerX: 100.0
  centerY: 100.0
  100.0
}

This will create a circle of radius 100 centered at (100, 100), producing identical results to the same example at the end of the last section with the radius label explicitly called out. For more information about how to set variables in your own classes as the default, see the section entitled “Declaring Default Instance Variables.”

Declaring Constants and Variables

You can declare constants and variables in an object literal expression to aid the initialization process. Constant and variable declarations in an object literal must be separated from other parts of the object literal by a semicolon. Constants and variables defined in an object literal expression are confined to the scope of the object literal expression. In Listing A-17, we’ve introduced a variable r to help initialize the Circle instance.

Listing A-17. Declaring a Variable in an Object Literal Expression

import visage.javafx.scene.shape.Circle;

var circle = Circle {
  var r = 100.0;
  centerX: r
  centerY: r
  radius: r
}
Overriding Instance Functions and Instance Variables

You can override instance variables and instance functions in an object literal expression to change the behavior of the class just for the instance. Such overrides must be separated from other parts of the object literal by a semicolon if they do not end with a closing brace. You may want to override an instance variable to add a trigger. We will cover instance variable overrides and instance function overrides, as well as triggers, in more detail later in this appendix.

Listing A-18 illustrates both an instance variable and an instance function overriding in object literal expressions. We overrode the instance variable x to add a replace trigger. We also overrode the toString() instance function to give our point p a nicer printed representation.

Listing A-18. Overriding Instance Variables and Functions in an Object Literal Expression

class Point {
  var x: Number;
  var y: Number;
}

var p = Point {
  override var x on replace {
    println("x is now {x}");
  }
  override function toString(): String {
    "Point({x}, {y})"
  }
  x: 3.0
  y: 4.0
}

println(p);

The instance function toString() in Point is inherited from the java.lang.Object class. The println() function uses an object’s toString() instance function to generate a string representation of the object. When the code in Listing A-18 is run, the following output is printed to the console:


x is now 3.0

Point(3.0, 4.0)

Manipulating Objects

Once you obtain an object reference by instantiating a Visage class using an object literal notation, you can manipulate the object through the object reference. You can also assign the object reference to a variable and manipulate the object through the object variable. You can pass the object reference or the object variable as function parameters. In general, you can use object references and object variables the same way you use primitive values and variables or sequence values and variables.

To take advantage of the functionality the class provides, you need to access the instance variables and instance functions of the class.

Manipulating Object States

The values of all instance variables of an object are called the object’s state. You access an object’s instance variables using a member access expression. The member access expression consists of a left side, a dot (.), and a right side. The left side of the dot must be an expression that evaluates to an object reference. The right side of the dot must be the name of an instance variable or an instance function of the class. Assume p is a variable of type Point and is assigned a valid instance of Point; then p.x and p.y are member access expressions that refer to the state of the object.

It is the class writer’s job to decide what kind of access rights you have regarding instance variables and instance functions of its instances. Access rights are granted based on whether your code is in the same script file, in the same package, or in a different package than the class you are accessing. Using access modifiers to specify instance variable and instance function access rights is explained in the section entitled “Access Modifiers” later in this appendix. For now, assume that any code that is in the same file as the class has full access rights to all its instance variables and instance functions. Listing A-19 shows code in the same file reading from and writing to instance variables x and y of the class Point.

Listing A-19. Accessing Instance Variables

class Point {
  var x: Number;
  var y: Number;
  override function toString(): String {
    "Point({x}, {y})"
  }
}

// reading instance variables
var p = Point { x: 3.0, y: 4.0 };
println("p.x = {p.x}");
println("p.y = {p.y}");
println("p = {p}");

// writing to instance variables
p.x = 5.0;
p.y = 12.0;
println("p = {p}");
Invoking Instance Functions

The dot notation also allows you to access instance functions of a class. Functions play an important role in the Visage language. Not only can you define and call functions and instance functions, you can assign functions to variables, pass functions into other functions, and use functions as return values in another function. Variables that refer to functions have function types. Function types, along with primitive, sequence, and object types, are the only four kinds of types of Visage. We will fully explore this functional programming aspect of Visage later in this appendix.

The function invocation expression consists of a function name or an expression of the function type followed by a pair of parentheses that encloses a comma-separated list of arguments. The number of arguments must agree with the number of arguments in the function’s definition. The type of each argument must be compatible with the type that is specified for that argument in the function definition. The function invocation expression’s type is the return type of the function it invokes. If that type is not the void type, the function invocation expression’s value is the return value of the function.

You have seen function invocation expressions at work throughout this appendix. We have used the println() function in our examples.

As is the case for instance variables, it is the class writer’s job to decide what kind of access rights you have regarding instance functions. Listing A-20 shows several instance function invocations.

Listing A-20. Invoking Instance Functions import java.lang.Math.*;

import java.lang.Math.*;

class Point {
  var x: Number;
  var y: Number;
  function distanceFromOrigin(): Number {
    sqrt(x*x + y*y)
  }
  function translate(dx: Number, dy: Number) {
    x += dx;
    y += dy;
  }
  override function toString(): String {
    "Point({x}, {y})"
  }
}

var p = Point { x: 3.0, y: 4.0 };
println("p = {p}");
println("Distance between p and the origin = {p.distanceFromOrigin()}");

p.translate(2.0, 8.0);
println("p = {p}");
println("Distance between p and the origin = {p.distanceFromOrigin()}");

print("Distance between Point {x: 8.0, y: 15.0} and the origin = ");
println(Point {x: 8.0, y: 15.0}.distanceFromOrigin());

The first line in Listing A-20 imports a number of methods, including sqrt(), from the Java class java.lang.Math into the program. We used sqrt() in the distanceFromOrigin() instance function to calculate the distance from the point to the origin. In the second line from the last, we escaped the brace characters in the string literal to turn off the special meaning of braces. The last line of the code demonstrates invoking an instance function directly on an object literal, without assigning the object reference to a variable.

Handling Nulls in Visage

In the previous examples, we ensured that all the variables we accessed and objects we invoked functions on were initialized before we used them. However, in a complicated program there are often times when we might get passed in an object that has a null value and inadvertently try to access a member variable or function.

In these cases, Visage will gracefully proceed. For a variable, it will return the default value for the variable type, and for a function call, it will skip the invocation and return the default value for the expected function result.

Listing A-21 demonstrates the result of calling methods and accessing variables on a null object.

Listing A-21. Null Safe Handling in Visage

class A {
  var objVar:Object;
  var intVar:Integer;
  var stringVar:String;

  function retObj():Object {
    println("should not get called");
    objVar
  }
}

var nullA:A = null;

println("nullA.objVar = {nullA.objVar}");
println("nullA.intVar = {nullA.intVar}");
println("nullA.stringVar = {nullA.stringVar}");
println("nullA.retObj() = {nullA.retObj()}");

Running this code will return the default object types as shown in the following output:


nullA.objVar = null

nullA.intVar = 0

nullA.stringVar =

nullA.retObj() = null

For user interface code, this graceful handling of nulls prevents exceptions or other interruptions that would take away from the user experience. However, there are times when you want to fail immediately if the variable you are accessing is null. This can be accomplished by using the null-check operator (!.), which will throw a java.lang.NullPointerException if the operand is null.

In the following example, we expect a NullPointerException to result from accessing a null object:

var nullA:A = null;
nullA!.objVar;

Checking null on dereference is handy for debugging code and happens to be the default behavior in Java. You can find more information about handling exceptions, such as the NullPointerException that the null-check operator throws, in the section entitled “Exceptions.”

Creating Java Objects with the new Operator

The new expression consists of the keyword new, followed by the name of a Java class and an optional pair of parentheses that encloses a comma-separated list of constructor arguments. It calls the constructor of the Java class that matches the number and type of the arguments and results in an object reference. If the constructor without any arguments (also called the no-arg or default constructor) is intended, you can either use a pair of empty parentheses or omit the parentheses altogether.

In the following example, we instantiate the java.util.Date class and call its getTime() method:

import java.util.Date;
var date = new Date();
var time = date.getTime();

images Note Visage allows you to use a new expression to instantiate a Visage class. The effect of the new expression for a Visage class is the same as an object literal expression with an empty pair of braces. For consistency, you should always use object literal expressions to instantiate Visage classes.

Making of a Declarative Syntax

One characteristic of object literal expressions is that they are self-explanatory. You can understand what is being instantiated by reading the object literal expression without having to refer to the class definition. If an instance variable in a class is itself of the class type or the sequence type, its instance variable initializer can be another nested object literal expression or an explicit sequence expression. This combination gives object literal expressions a hierarchical feel, which makes it ideal for describing GUIs.

Working with Data Bindings

Visage’s data binding facility allows any variable to be bound to a value expression. When any constituent part of the bound expression is changed, the bound expression is recalculated and the variable’s value is also changed. The data binding capability is at the center of the Visage approach for GUI development, in which onscreen UI controls’ properties are bound to a model object and the GUI is controlled through state changes in the model object.

Bind Expression

A bind expression is introduced by the bind keyword, followed by a value expression and optionally the with inverse keywords. Bind expressions are automatically reevaluated when their dependencies change. Unlike all the other expressions that you’ve learned so far, bind expressions are not true stand-alone expressions. A bind expression must appear on the right-hand side of constant declarations, variable declarations, or instance variable initializers in object literal expressions. It also puts restrictions on the value expression that appears on its right-hand side. The constant, variable, or instance variable is said to be bound to the expression.

Here are some examples of bind expressions:

var a = 3.14159;
def b = bind a;
var c = bind a;
var p = Point { x: bind a, y: bind a }

The constant b, variable c, and instance variables p.x and p.y are all bound to the bind expression bind a. Any value assigned to a after the binding will also become the new value of b, c, p.x, and p.y.

A bound variable or a bound instance variable (except for bindings with inverse, which we will explain shortly) cannot be assigned another value. The following lines will cause a compilation error:

var a = 1024;
var b = bind a;
b = 2048;

images Caution The compiler cannot effectively detect all assignments to bound variables at compile time. Assignments to bound variables will cause a runtime exception to be thrown.

What Does the Data Binding Remember?

With a regular (nonbinding) assignment or initialization, the right-hand side expression is evaluated and its value is assigned to the left-hand side variable. With a binding assignment or initialization, the Visage runtime system not only evaluates the expression and assigns the value to the variable but also remembers the entire expression, figuring out which variables the expression depends on and keeping an eye on the dependent variables. When any one of them gets a new value, the saved expression is updated in a bind context and its value becomes the new value of the bound variable.

In the following example, the variable z becomes a bound variable in the third line. It gets the value 7. The Visage also remembers that z is bound to x + y, which depends on the variables x and y. When the value of x or y is changed, the expression x + y is updated, and its new value becomes the new value of z.

var x = 3;
var y = 4;
var z = bind x + y;

A bind expression cannot contain assignments, pre- or postincrement or decrement expressions, or while expressions.

Binding to if Expressions

When a variable is bound to an if expression, its dependencies are the union of the dependencies of the condition, the then clause, and the else clause. To update an if expression in a bind context, the if expression is simply reevaluated. Here is an example:

var b = true;
var x = 3;
var y = 4;
def z = bind if (b) then x else y;

In this example, z depends on b, x, and y. When any one of them changes, the value of z is updated by reevaluating the if expression.

Binding to for Expressions

Since for expressions of the void type are not value expressions, the only kind of for expressions that can appear in a bind expression are sequence comprehensions. When a variable is bound to a for expression, its dependencies are the union of the dependencies of the sequences specified in the in clauses and the dependencies of the where clauses, excluding the iteration variables. To update a for expression in the bind context, the body of the for expression is reevaluated for a minimal number of element tuples in the sequences.

var a = 1;
var b = 10;
var m = 4;
def c = bind for (x in [a..b] where x < m) { x * x }

In the preceding example, the dependencies of c are the union of the dependencies of [a..b], which is a and b, and the dependencies of x < m, which is x and m. Excluding the iteration variable x, that gives us the dependencies a, b, and m. With the given values of a, b, and m, the qualified x values are 1, 2, 3. If m is changed to 7, the qualified x values will also include 4, 5, and 6. The body of the for expression is only evaluated for the new x values of 4, 5, and 6.

Binding to a Block

Visage restricts the expressions that can appear in a block in a binding context. The block can have only a certain number of constant declarations and a final value expression. The constant declarations are treated as if they are binding declarations. When a variable is bound to a block, its dependencies are the dependencies of the last expression in the block, which may, in turn, depend on the constant declarations in the block or variables outside the block.

var a = 3;
var b = 4;
def c = bind {
  def d = a;
  def e = b;
  d * d
}

In the preceding example, the dependencies of c are the same as the dependencies of d * d, which are the same as the dependencies of d, which is a. Therefore, the value of c is updated when the value of a is changed but not when the value of b is changed.

The restrictions on the content of blocks under a binding context apply also to blocks in if and for expressions.

images Note The compiler allows you to include variable declarations in a block in the bind context. However, since no assignments can appear in the block, these variable declarations are effectively constant declarations. They are also treated as if they are binding declarations.

Binding to Function Invocation Expressions

Visage has two kinds of functions: functions and bound functions. Both kinds of functions behave the same way except when their invocation is under a binding context. You will learn how to define functions and bound functions in the section entitled “Bound Functions” later in this appendix.

When a variable is bound to a (nonbound) function invocation expression, its dependencies are the union of the dependencies of the arguments. When a variable is bound to a bound function invocation expression, its dependencies are the dependencies of the bound function body treated like a block. The update rule for functions is that the function is reinvoked when the dependencies change. The update rule for bound functions is the same as the update rule for blocks.

function sumOfSquares(x, y) { x * x + y * y }
var a = 3;
def c = bind sumOfSquares(a + 5, a + 6);

In the preceding example, the dependency of c is the union of that of a + 5 and a + 6, which is a. Therefore, the value of c is updated when the value of a is changed. The function is reinvoked with the new arguments.

Binding to Object Literal Expressions

When a variable is bound to an object literal expression, its dependencies are the union of the dependencies of the right-hand side expressions of those instance variable initializers whose right-hand side expression is not already a bound expression. When a dependency changes, a new object is instantiated.

var a = 3;
var b = 4;
def p = bind Point { x: a, y: b };
def q = bind Point { x: bind a, y: b };
def r = bind Point { x: bind a, y: bind b };

In the preceding example, the dependencies of p are a and b, the dependency of q is b, and the dependency of r is empty. When a is changed, a new instance of Point is created for p, but not for q and r. However, since q.x and r.x are bound to a, their values will be updated. Similarly, when b is changed, a new instance of Point is created for p and q, but not for r. However, r.y is updated. Since the dependencies of r are empty, binding r to the object literal expression is unnecessary.

images Caution Two levels of binding are at work in the preceding example: one at the object level and one at the instance variable level. Changes in dependencies of bound instance variables will not cause a new instance of Point to be created; only the value of the instance variable is updated. Changes in dependencies of bound object literal will cause the creation of a new instance of Point. This subtle difference may not be readily differentiated when you print out the values of p, q, or r with println().

Bidirectional Bindings and Lazy Bindings

Visage supports bidirectional bindings. A bidirectional binding is specified by appending the with inverse keywords to the end of a bind expression. The only expression allowed is a variable name. The following trivial example illustrates this construct:

var a = 3;
var b = bind a with inverse;

A value can be assigned to a bidirectionally bound variable, and its new value will also become the new value of its peer. Bidirectional bindings are useful in GUI programs where several onscreen elements are used to edit the same underlying quantity, such as when you want the user to change the RGB settings of a color using sliders and text fields.

Visage supports lazy bindings. A lazy binding is specified by adding the lazy keyword after bind. Both regular bindings and bidirectional bindings can be lazy bindings. Lazy bindings have the same meaning as regular bindings with the exception that the bound variable is not updated until its value is needed. This may reduce the number of recalculations to the minimum and therefore boost the performance of the code.

In Listing A-22, you can see Visage bindings at work.

Listing A-22. Data Bindings

var pointInstance = 1;
class Point {
  var instance = pointInstance++;
  var x: Number;
  var y: Number;
  override public function toString() {
    "Point({x}, {y})@{instance}"
  }
}

// data bindings
var a = 3.14159;
def b = bind a;
var c = bind a;
var p = Point { x: bind a, y: bind a };
println("a = {a}, b = {b}, c = {c}, p.x = {p.x}, p.y = {p.y}");

a = 2.17828;
println("a = {a}, b = {b}, c = {c}, p.x = {p.x}, p.y = {p.y}");

// binding to arithmetic expressions
var x1 = 3;
var y1 = 4;
var z1 = bind x1 + y1;
println("x1 = {x1}, y1 = {y1}, z1 = {z1}");
x1 = 5;
println("x1 = {x1}, y1 = {y1}, z1 = {z1}");
y1 = 12;
println("x1 = {x1}, y1 = {y1}, z1 = {z1}");

// binding to if expression
var b2 = true;
var x2 = 3;
var y2 = 4;
def z2 = bind if (b2) then x2 else y2;
println("b2 = {b2}, x2 = {x2}, y2 = {y2}, z2 = {z2}");
b2 = false;
println("b2 = {b2}, x2 = {x2}, y2 = {y2}, z2 = {z2}");
x2 = 5;
println("b2 = {b2}, x2 = {x2}, y2 = {y2}, z2 = {z2}");
y2 = 12;
println("b2 = {b2}, x2 = {x2}, y2 = {y2}, z2 = {z2}");

// binding to for expression
var a3 = 1;
var b3 = 10;
var m3 = 4;
def c3 = bind for (x in [a3..b3] where x < m3) { x * x };
print("a3 = {a3}, b3 = {b3}, m3 = {m3}, c3 = "); println(c3);
m3 = 7;
print("a3 = {a3}, b3 = {b3}, m3 = {m3}, c3 = "); println(c3);
a3 = 2;
print("a3 = {a3}, b3 = {b3}, m3 = {m3}, c3 = "); println(c3);
b3 = 5;
print("a3 = {a3}, b3 = {b3}, m3 = {m3}, c3 = "); println(c3);

// binding to block
var a4 = 3;
var b4 = 4;
def c4 = bind {
  def d4 = a4;
  def e4 = b4;
  d4 * d4
};
println("a4 = {a4}, b4 = {b4}, c4 = {c4}");
a4 = 5;
println("a4 = {a4}, b4 = {b4}, c4 = {c4}");
b4 = 12;
println("a4 = {a4}, b4 = {b4}, c4 = {c4}");

// binding to function invocation expression
function sumOfSquares(x, y) { x * x + y * y }
var a5 = 3;
var b5 = 4;
def c5 = bind sumOfSquares(a5 + 5, a5 + 6);
println("a5 = {a5}, b5 = {b5}, c5 = {c5}");
a5 = 5;
println("a5 = {a5}, b5 = {b5}, c5 = {c5}");
b5 = 12;
println("a5 = {a5}, b5 = {b5}, c5 = {c5}");

// binding to object literals
var a6 = 3;
var b6 = 4;
def p6 = bind Point { x: a6, y: b6 };
def q6 = bind Point { x: bind a6, y: b6 };
def r6 = bind Point { x: bind a6, y: bind b6 };
println("a6 = {a6}, b6 = {b6}, p6 = {p6}, q6 = {q6}, r6 = {r6}");
a6 = 5;
println("a6 = {a6}, b6 = {b6}, p6 = {p6}, q6 = {q6}, r6 = {r6}");
b6 = 12;
println("a6 = {a6}, b6 = {b6}, p6 = {p6}, q6 = {q6}, r6 = {r6}");

// bidirectional binding
var a7 = 3;
var b7 = bind a7 with inverse;
println("a7 = {a7}, b7 = {b7}");
a7 = 4;
println("a7 = {a7}, b7 = {b7}");
b7 = 5;
println("a7 = {a7}, b7 = {b7}");

Working with Functions

In Visage, functions serve to break long and complicated code into manageable pieces. Once written, functions can be invoked multiple times with different arguments, so they also provide a code reuse mechanism. Additionally, functions are one of the ingredients of the two programming paradigms: functional programming and object-oriented programming.

Functions come into being in two ways: as function definitions or as anonymous function expressions. The following sections explore how to create functions each way.

Function Definitions

A function definition is introduced by the function keyword, followed by the name of the function, a pair of parentheses that encloses zero or more argument specifications (if more than one, the arguments will appear in a comma-separated list), an optional return type specifier, and a body block.

In Listing A-23, we define a function that calculates the nth value of the recursively defined Fibonacci series. The first two numbers in the series are 0 and 1, and any subsequent number in the series is the sum of the two preceding numbers.

Listing A-23. The Fibonacci Series

function fib(n:Integer):Integer {
  if (n <= 0) then 0 else
    if (n == 1) then 1 else
      fib(n - 1) + fib(n - 2)
}
println("fib(6) = {fib(6)}");

When you run the program in Listing A-23, the following output is printed to the console:


fib(6) = 8

In the preceding definition, we defined a function named fib. It takes one argument of type Integer named n. It returns an Integer value. And its body is a block that contains a single if expression. Both the parameter type and the return type are explicitly specified.

Function definitions are not expressions. They can appear in only two places: at the top level of a Visage source file or within the scope of a Visage class. Function definitions that appear at the top level of a Visage source file are called script functions. Those that appear within the scope of a class are called instance functions. They cannot appear inside other expressions, such as in a block. You will learn about classes and instance functions in the “Working with Classes” section later in this appendix.

In Visage, function names introduced by function definitions live in their own space, which is different from the space where variable and constant names introduced by variable or constant declarations live. As a result, in a Visage file, you can define both a function foo and a variable foo, and the compiler will happily compile your source file.

Return Types

In Visage, the return type of a function is specified after the closing parenthesis of the argument list, separated from the argument list by a colon (:). Any Visage types, including primitive types, sequence types, function types, and class types, can serve as the return type of a function. A special type specifier, Void, can also be used as the return type of functions. As a matter of fact, the only legitimate use of Void is as the return type of a function. Listing A-24 shows some examples of function definitions.

Listing A-24. Function Definitions with Explicit Return Types

class Point {
  var x: Number;
  var y: Number;
}

function primes(n:Integer):Integer[] {
  [2..n][k | sizeof [2..<k][d | k mod d == 0] == 0];
}

function midpoint(p1:Point, p2:Point):Point {
  Point { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 }
}

function printSquaresTable(n:Integer):Void {
  for (x in [1..n]) {
    println("{x} squared = {x*x}");
  }
}

The function primes returns a sequence of integers consisting of all prime numbers less than or equal to the parameter value n. Its body is a block that contains a sequence comprehension consisting of all ks in the range from 2 to n for which a condition is true. The condition involves another sequence comprehension that contains all factors of k, between 2 and k-1, and the condition is true only if the size of the factor sequence is zero.

The midpoint function calculates the midpoint between its two arguments.

The printSquaresTable function prints a table of squares of integers between 1 and the function parameter n. It has a return type of Void, which means that this function does not return any values.

images Note One subtlety of the Void return type is that it does not prevent the body of the function from implicitly returning a value as the last expression of its body. The value will simply be ignored, and any function invocation expressions cannot be used in contexts where a value expression is expected. For example, var x = printSquareTables() will generate a compilation error.

As is the case for variable declarations, under certain restrictions, Visage’s type inference facility can infer the return type of a function if an explicit type specifier is not given. Three of the four functions that you have seen earlier can also be defined without explicit return types, as demonstrated in Listing A-25.

Listing A-25. Function Definitions with Implicit Return Types

function primes(n:Integer) {
  [2..n][k | sizeof [2..<k][d | k mod d == 0] == 0];
}

function midpoint(p1:Point, p2:Point) {
  Point { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 }
}

function printSquaresTable(n:Integer) {
  for (x in [1..n]) {
    println("{x} squared = {x*x}");
  }
}

A restriction on inferring the return types of functions is that the function definition cannot have cycles. The fib function cannot be defined with implicit return types because it is recursively defined. This restriction also applies to two or more functions that reference each other in their bodies in such a way as to cause a cycle. For example, if function f calls function g, function g calls function h, and function h calls back to function f, all three functions need to have explicit return types.

Parameters and Their Types

A Visage function can have zero or more parameters. In function definitions, the function parameters appear after the function name as a comma-separated list enclosed in a pair of parentheses. In the four examples from the last section, the functions fib, primes, and printSquaresTable have one parameter, and the midpoint function has two parameters.

Each parameter is specified as a parameter name followed by an optional type specifier. If more than one parameter is specified, they all must have distinct names.

When a parameter’s type is not explicitly specified, the type inference facility will infer the type of the parameter. However, the power of Visage’s type inference facility for implicitly typed function parameters is weaker than that for either variables or return types. The inferred type will be either Object or Double depending on whether the parameter participates in arithmetic operations inside the function body.

None of the four functions from the last section is a good candidate for type inference on parameter types. Because the parameter n in the function fib participates in arithmetic operations, its inferred type is Double, not Integer as we intended. Because the function bodies of primes, midpoint, and printSquaresTable do not contain any arithmetic operations on their parameters, the inferred types of the parameters will be Object, which is not specific enough for these functions to compile successfully.

Naturally, function definitions that are candidates for type inference on parameter types are ones that perform numerical calculations. Listing A-26 gives an example.

Listing A-26. Function Definitions with Implicit Parameter Types

import java.lang.Math.*;

function hypot(x, y) {
  sqrt(x*x + y*y)
}

println("hypot(3, 4) = {hypot(3, 4)}");

This function calculates the hypotenuse of a right triangle given the other two sides. When you run the program in Listing A-26, the following output is printed to the console:


hypot(3, 4) = 5.0

Note that we used the import java.lang.Math.*; statement to bring all the functions in the Java class java.lang.Math into our program. We used the sqrt() function in our calculations. We will cover import statements in the “Import Directives” section later in this appendix.

Function Bodies and the Return Expression

The body of a function is a block. Everything that you’ve learned about blocks also applies to function bodies. Additionally, function bodies may contain return expressions. When a function is invoked, its function body is executed until the end of the body or a return expression is reached. The execution of a function body may also end because of exceptions. We will cover exceptions in the “Exceptions” section.

A return expression is introduced by the return keyword, followed optionally by an expression. If the return keyword is not followed by an expression, the return expression is a stand-alone return expression. A stand-alone return expression is of the void type and has no value. If the return keyword is followed by an expression, the return expression is of the same type as the following expression, and its value is the value of the following expression.

Here is a stand-alone return expression:

return;

And here is a return expression that returns an integer value of 1024:

return 1024;

Return expressions may appear only inside a function body. If the function definition has an explicit return type that is not Void, all the return expressions inside the function body as well as the last expression of the function body must be compatible with the specified return type. If the function definition has an explicit return type of Void, all return expressions inside the function body must be stand-alone return expressions. If the function definition has no explicit return type, the return type of the function is inferred from the last expression of the function body and all the return expressions. The inferred return type will be a type that can accommodate all the return expressions.

Listing A-27 presents some examples of function definitions whose bodies contain return expressions.

Listing A-27. Function Definitions with Return Expressions

function gcd(a:Integer, b:Integer):Integer {
  if (b == 0) {
    return a;
  } else {
    return gcd(b, a mod b);
  }
}

function ampm(hour:Integer, minute:Integer):Void {
  if (hour == 0 and minute == 0) {
    println("midnight");
    return;
  }
  if (hour < 12) {
    println("a.m.");
    return;
  }
  if (hour == 12 and minute == 0) {
    println("noon");
    return;
  }
  println("p.m.");
}

function onClick(n:Integer) {
  if (n == 0) {
    return "No click";
  }
  if (n == 1) {
    return "Single click";
  }
  if (n == 2) {
    return "Double click";
  }
  return "Multiple click";
}

println("gcd(24, 18) = {gcd(24, 18)}");
ampm(0, 0);
ampm(6, 0);
ampm(12, 0);
ampm(18, 0);
println(onClick(0));
println(onClick(1));
println(onClick(2));
println(onClick(3));

The function gcd() calculates the greatest common divisor of parameters a and b using the Euclidean algorithm. It has an explicit return type of Integer, and both return expressions in the function body are of type Integer. The function ampm() prints out midnight, a.m., noon, or p.m. depending on the hour and minute parameters. It has an explicit return type of Void, and all three return expressions in the function body are stand-alone return expressions. The function onClick() attempts to return a string that describes the type of click based on the parameter n. It does not have an explicit return type, and since all return expressions in the function body are of type String, the return type of the function is inferred to be String.

When you run the program in Listing A-27, the following output is printed to the console:


gcd(24, 18) = 6

midnight

a.m.

noon

p.m.

No click

Single click

Double click

Multiple click

Function Body Scope

Within the body of a script function, you can access three kinds of variables:

  • Script variables and constants declared in the same Visage source file
  • Function parameters that are passed into the function invocation expression
  • Local variables and constants that are declared in the function body

You also have access to other script functions in the same Visage source file. This list of accessible entities to script functions will grow when you see how to organize your Visage code into multiple files and how to make entities from one file accessible from other files in the “Organizing Visage Code” section later in this appendix.

Listing A-28 illustrates the accessibility of script variables from script functions.

Listing A-28. Accessing Script Variables from Function Definitions

var q:Integer[];

function enqueue(i:Integer):Void {
  insert i into q;
  println("Enqueued {i}.  Size of q is {sizeof q} now.");
}

function dequeue():Integer {
  if (sizeof q == 0) {
    println("Size of q is {sizeof q} now. Returning -1.");
    return -1;
  } else {
    var i = q[0];
    delete q[0];
    println("Dequeued {i}.  Size of q is {sizeof q} now.");
    return i;
  }
}

enqueue(1);
enqueue(2);
enqueue(3);
println(dequeue());
println(dequeue());
enqueue(4);
println(dequeue());
println(dequeue());
println(dequeue());

The script variable q is a sequence of Integer values. The two script functions enqueue() and dequeue() manipulate q in their bodies. The enqueue() function uses the insert statement to add the parameter i at the end of the sequence q and prints a message to the console. The dequeue() function first checks whether q is empty. If q is empty, the function prints a message and returns –1. Otherwise, it saves q[0] into a local variable i, uses the delete statement to remove the first element from q, prints a message, and returns the local variable i.

When you run the program in Listing A-28, the following output is printed to the console:


Enqueued 1.  Size of q is 1 now.

Enqueued 2.  Size of q is 2 now.

Enqueued 3.  Size of q is 3 now.

Dequeued 1.  Size of q is 2 now.

1

Dequeued 2.  Size of q is 1 now.

2

Enqueued 4.  Size of q is 2 now.

Dequeued 3.  Size of q is 1 now.

3

Dequeued 4.  Size of q is 0 now.

4

Size of q is 0 now. Returning -1.

-1

Function parameters and local variables and constants can be seen only in the body of the function where they are defined. One function cannot see the parameters and local variables of another function. Function parameters are read-only. You cannot change the values of a function’s parameters in the body of that function.

The following function will generate the compilation error message "You cannot change the value(s) of 'x' because it is a parameter":

// Won't compile
function f(x:Number):Void {
  x = 1024;
}

Overloaded Functions

In Visage, you can have several function definitions with the same name, as long as they can be distinguished by differences in either the number or types of parameters. Multiple functions with the same name but different parameters are called overloaded functions.

The definitions of overloaded functions are no different from the definitions of non-overloaded functions. You simply use the same function name in two or more function definitions.

Listing A-29 shows an example of some overloaded functions.

Listing A-29. Overloaded Function Definitions

function f(i:Integer):Void {
  println("Integer version of f is called with i = {i}.");
}

function f(n:Number):Void {
  println("Number version of f is called with n = {n}.");
}

function f(str:String):Void {
  println("String version of f is called with str = {str}.");
}

f(1024);
f(3.14);
f("Hello, World");

When these functions are invoked, the function invocation expression will look like f(exp), where exp is some expression. The Visage compiler determines which of the overloaded functions is called by examining the type of the expression exp and selects the most appropriate function to call. In the three function invocation expressions in Listing A-29, because 1024 is of type Integer, 3.14 is of type Number, and "Hello, World" is of type String, f(1024) invokes the version of f that takes a single Integer parameter, f(3.14) invokes the version of f that takes a single Number parameter, and f("Hello, World") invokes the version of f that takes a single String parameter.

When you run the program in Listing A-29, the following output is printed to the console:


Integer version of f is called with i = 1024.

Number version of f is called with n = 3.14.

String version of f is called with str = Hello, World.

Without adding a version of f for a single Boolean parameter, the function invocation expression f(false) would cause a compilation error because the compiler cannot find an appropriate version of f to call.

images Caution To cut down the confusion of the program that calls overloaded functions, you should overload a function name only with functions that logically perform the same task, with slight differences in the parameters passed in.

Implicit Type Conversions

Up to this point, we have used the term compatible when we referred to the ability to assign a value to a variable or to pass a value into a function invocation expression as a parameter.

Visage will allow a value of one type to be assigned to a variable of a different type in three situations:

  • Numeric conversions
  • Sequence conversions
  • Class hierarchies

Numeric conversions happen when a value of one numeric type is assigned to a variable of a different numeric type. Here is an example:

var n:Number = 1024;

This code looks normal, yet it does involve an implicit numeric conversion. The literal value 1024, which is of type Integer, is assigned to the variable n, which is of type Number. This assignment succeeds because Visage allows Integer values to be converted to Number values.

Sequence conversions happen when a value of a nonsequence type is assigned to a variable of the corresponding sequence type. Visage allows this conversion to simplify sequence manipulation code, because in many cases, a sequence variable contains only one element. Therefore, the two assignments in the following snippet of code are equivalent:

var strs:String[];
strs = ["Hello, World"];
strs = "Hello, World";

As this conversion demonstrates, if a sequence contains only one element, the brackets are optional. There is no implicit conversion from a sequence type to its element type. The following code will cause a compilation error:

// Won't compile
var str:String = ["Hello, World"];

Defining class hierarchies is discussed further in the “Creating Class Hierarchies” section later in this appendix; for now, you just need to understand the rule for assignability regarding such hierarchies: if class Bar extends class Foo, an instance of Bar can be assigned to a variable of type Foo. Here is a simple example:

class Foo {}
class Bar extends Foo {}
var foo:Foo = Bar {};

In the third line, we created a new instance of Bar with an object literal expression and assigned the resulting instance of Bar to the variable foo, which is declared with type Foo. Notice that no conversion on the instance is performed nor is it needed. An instance of a subclass is naturally also an instance of its superclasses. This is different from numeric or sequence conversions.

For the purposes of assignment and function parameter passing, the primitive and function types are considered assignable to the Object type. Sequence types are not considered assignable to the Object type.

Resolving Overloaded Functions

The rules the Visage compiler uses to find the function to invoke for a function invocation expression are as follows:

  1. Find all functions with the given name, with the same number of parameters as in the function invocation expression, and with compatible types for each parameter. If only one function matches, invoke that function.
  2. If more than one function remains, and if one function in the set has parameter types that are more specific than another function in the set, eliminate the less-specific function. Repeat this process until no more eliminations can be made.
  3. If exactly one function remains, invoke that function. If more than one function remains, the function invocation expression cannot be resolved, and a compilation error results.

Listing A-30 presents an example of an ambiguous invocation of overloaded functions.

Listing A-30. Resolving Overloaded Function Definitions

function f(i:Integer, n:Number):Void {
  println("Integer, Number version of f is called with ({i}, {n}).");
}

function f(n:Number, i:Integer):Void {
  println("Number, Integer version of f is called with ({n}, {i}).");
}

f(1024, 3.14);
f(6.28, 2048);
// f(1024, 2048); // Won't compile

Both overloaded functions match the third (commented out) invocation. However, because the first function definition is more specific in the first parameter and the second function definition is more specific in the second parameter, neither function can be eliminated in the second step of the resolution rules.

When you run the program in Listing A-30, the following output is printed to the console:


Integer, Number version of f called with (1024, 3.14).

Number, Integer version of f called with (6.28, 2048).

Function Types and Anonymous Functions

Although Visage’s function definition facility allows you to define functions at the script and class level, Visage’s anonymous function expression allows you to create functions anywhere an expression can appear.

Because an anonymous function expression is an expression, you may naturally ask two questions of any Visage expressions: What is the type of the expression? And what is the value of the expression? The answers are straightforward. Visage anonymous function expressions have function types, and they evaluate to closures.

The concepts of anonymous function expressions, closures, and function types are intricately related, so we will first present a simple example in Listing A-31, which involves all of these concepts. We will then point to portions of the example when we explain the relevant concepts.

Listing A-31. Anonymous Function Example

import java.lang.Math.*;

{
  var hypot: function(:Number, :Number):Number;

  hypot = function(x, y) {
    sqrt(x*x + y*y);
  };

  println("hypot(3, 4) = {hypot(3, 4)}");
}

We deliberately put all the code inside a block, where a function definition is not allowed but anonymous function expressions are allowed.

We first declare a variable, hypot, with the type specifier function(:Number, :Number):Number. This specifies a function type representing all functions that takes two Number parameters and returns a Number value. We then assign an anonymous function expression to the variable hypot. The anonymous function expression, which is shown in bold type, resembles an ordinary function definition without the function name. After the assignment, the variable hypot has as its value a closure that is the result of evaluating the anonymous function expression. We finally invoke the closure, which performs the calculation specified in the anonymous function expression, in this case calculating the hypotenuse of a right triangle, which will output the following:


hypot(3, 4) = 5.0
More Details on Function Type Specifiers

The format of a function type specifier is simple. It starts with the function keyword, followed by a pair of parentheses enclosing a comma-separated list of parameter type specifiers, and then the return type specifier. You define the parameter type specifier by including a colon (:) followed by the type name or by including a parameter name followed by another colon and the type name. You provide the return type specifier using a colon followed by the type name.

Because no function body is associated with a function type specifier, all parameter types must be provided. You can omit the return type specifier. However, if you do so, the return type is assumed to be Void.

The following is a list of function type specifiers that are found in the Visage JavaFX API documentation:

function(:Duration):Void
function(:Event):Void
function(:Exception):Void
function(:InputStream):Void
function(:KeyEvent):Void
function(:MediaError):Void
function(:MediaTimer):Void
function(:MouseEvent):Void
function(:MouseEvent):Boolean
function(:OutputStream):Void
function(:Sequence):Void
function(:String):Void
function(:String):Boolean
function(:Boolean):Void
function(:Integer):Void

These are the data types of various event handlers in Visage JavaFX API classes. The function(:MouseEvent):Void type, for example, is the type of instance variables named onMousePressed, onMouseReleased, and onMouseMoved in the visage.javafx.scene.CustomNode class.

You can build more complicated function types. Here is an example:

function(:Integer):function(:Integer):Integer

This is the type of a function that takes an Integer parameter and returns a function. The function it returns takes an Integer parameter and returns an Integer.

More Details on Anonymous Function Expressions

As you saw in Listing A-31, an anonymous function expression is almost identical with a function definition in form. In that program, we used a form of anonymous function expression that relied on Visage’s type inference facility to determine the function type of the resulting closure.

You can also use the form of anonymous function expression that explicitly specifies the parameter and return types, as shown in Listing A-32.

Listing A-32. Anonymous Function with Explicit Types

{
  var primes = function(n:Integer):Integer[] {
    [2..n][k | sizeof [2..<k][d | k mod d == 0] == 0]
  };
  println(primes(64));
}

In this example, we redefine our earlier primes function as an anonymous function expression with explicitly specified types. Because we do not explicitly specify the type of primes, it gets the type of the first value that is assigned to it. In this case, the value is the closure resulting from evaluating the anonymous function expression.

If you attempt to assign a closure to an explicit function type variable and the type of the closure is different from the type of the variable, a compilation error will be generated.

More Details on Closures

One of the benefits of supporting function types in a language is that functions can be assigned to variables, passed into functions, and returned from functions. Programming languages that support such features are usually said to support first-class functions. And you can use a style of programming that relies heavily on first-class functions called functional programming.

Listing A-33 shows a typical first example of functional programming.

Listing A-33. Functional Programming

{
  var addN: function(:Integer):function(:Integer):Integer;
  addN = function(n) {
    function(i:Integer) {
      i + n
    }
  };

  var addTen = addN(10);
  println("addTen(4) = {addTen(4)}.");
}

We declare addN as a function type variable that takes an Integer parameter and returns a function, which, in turn, takes an Integer parameter and returns an Integer. We then assign a closure to it: any Integer parameter will return a function that adds the Integer to its parameter. We then assign addN(10) to the variable addTen. The variable addTen is, therefore, of the function type function(:Integer):Integer, which is the return type of addN, and the closure that is assigned to addTen will simply add 10 to its parameter. Finally, we invoke addTen with a parameter of 4, which should produce the result 14.

When we run the program in Listing A-33, the following output is printed to the console:


addTen(4) = 14.

In the “Function Body Scope” section earlier in this appendix, we showed that the function definition’s body can access all script variables and constants and all script functions. In much the same way, the body of an anonymous function expression can also access all the functions, variables, and constants that are in scope when the anonymous function expression is evaluated. The resulting closure will capture these functions, variables, and constants.

Much of the power of closure comes from the fact that the environment that it captures remains available even after the original environment is gone. In Listing A-33, for example, by the time addTen(4) is invoked, the earlier invocation addN(10) has already returned, yet addTen can still access the parameter 10 that is passed into the invocation of addN(10).

Bound Functions

A bound function is a function definition decorated with the bound modifier whose body is restricted to a number of variable and constant declarations followed by one final expression.

When a variable is bound to a function invocation expression that includes a bound function, its dependency is the dependency of the last expression of the bound function, traced transitively to either a parameter of the function or a script variable. Listing A-34 shows an example of a bound function.

Listing A-34. Binding to a Bound Function

var x = 3;

bound function f(y:Number, z:Number):Number {
  var u = y;
  var v = z;
  x + u
}

var a = 4;
var b = 5;
var c = bind f(a, b) on replace {
  println("x = {x}, a = {a}, b = {b}, c = {c}.");
};

println("About to change x...");
x = 5;
println("About to change a...");
a = 6;
println("About to change b...");
b = 7;

In Listing A-34, the dependency of c is the dependency of x + u, the last expression of the bound function f. Because x is a script variable and u depends on the first parameter y, the dependency of c is the script variable x and the first parameter of the function invocation expression, a. We add an on replace trigger to monitor the update of c. We will cover triggers in the “Triggers” section later in this appendix.

When we run the program in Listing A-34, the following output is printed to the console:


x = 3, a = 4, b = 5, c = 7.0.

About to change x...

x = 5, a = 4, b = 5, c = 9.0.

About to change a...

x = 5, a = 6, b = 5, c = 11.0.

About to change b...

As you can see, the value of c is indeed updated when x and a changed but not when b changed.

When a bound function is invoked in a bind expression, its function body is also evaluated in a bound context, which causes each assignment in the body to behave as if it were a binding assignment.

This allows complicated expressions to be bound to a variable through a series of simpler bound functions.

Handling Exceptions

As your Visage code base grows, systematically handling errors becomes more important. The exception facility is Visage’s way of dealing with exceptional conditions.

The Call Stack

As your program runs, the code will call into and exit from functions. Every time the code calls into a function, the Visage runtime creates a stack frame. The stack frame for each function call contains the parameters that are passed into the function as well as local variables and constants declared within the function. The call stack is the accumulation of stack frames of all active function calls. As the code makes deeper and deeper function calls, the call stack grows. As the code exits the called functions, the call stack shrinks.

As you saw earlier, reaching a return expression inside a function body and reaching the end of the function body are two ways to return from function calls. These are called normal exits from function calls.

If your code encounters an exceptional condition or an error condition and cannot complete the calculation the function is designed to do, you can use Visage’s exception facility to throw an exception. If your code calls functions that may throw exceptions, you can use a try-catch-finally expression to install handlers for the exceptions that it is prepared to handle. When an exception is thrown, the Visage runtime will look for a handler for the exception. If one cannot be found in the current function, it will exit the function and continue looking for one in the calling function. The runtime may exit several levels of function calls before a handler is found. These are called abnormal exits from function calls. The process of exiting functions abnormally in search of an exception handler is called unwinding of the call stack.

Throwing Exceptions

You throw an exception using a throw expression. A throw expression is introduced by the throw keyword, followed by an instance of a class that extends the Java class java.lang.Throwable. When a throw expression is evaluated, the Visage exception handling facility is started.

The class java.lang.Throwable is the root of the exception class hierarchy for Java, which include many exception classes that can be used directly in Visage. You can also define your own exception classes for use in your code. However, for the majority of Visage programs, the Java exceptions are adequate.

Listing A-35 shows an example of a function that throws an exception.

Listing A-35. Throwing an Exception

import java.lang.IllegalArgumentException;

function weekday(i:Integer):String {
  if (i == 0) then "Sunday" else
  if (i == 1) then "Monday" else
  if (i == 2) then "Tuesday" else
  if (i == 3) then "Wednesday" else
  if (i == 4) then "Thursday" else
  if (i == 5) then "Friday" else
  if (i == 6) then "Saturday" else
  throw new IllegalArgumentException("Invalid weekday: {i}.");
}

for (i in [0..7]) {
  println("weekday({i}) = {weekday(i)}.");
}

In Listing A-35, we define a simple function that translates numeric representations of weekdays into their corresponding string representations. When the parameter is not in the correct range, we throw a newly constructed instance of java.lang.IllegalArgumentException. We then called the function eight times in a for loop, the last time with an illegal argument. Because java.lang.IllegalArgumentException is a Java class, we use the new keyword to create an instance of it for use in our thrown exception. You will find out more about instantiating Java classes in the “Leveraging Java from Visage” section later in this appendix.

When we run the program in Listing A-35, the following output is printed to the console:


weekday(0) = Sunday.

weekday(1) = Monday.

weekday(2) = Tuesday.

weekday(3) = Wednesday.

weekday(4) = Thursday.

weekday(5) = Friday.

weekday(6) = Saturday.

java.lang.IllegalArgumentException: Invalid weekday: 7.

at ThrowingException.weekday(ThrowingException.visage:11) at
ThrowingException.visage$run$(ThrowingException.visage:15)

We call the function without a custom exception handler, so when Visage unwinds the stack, it exits not only the weekday() function but also the calling code into the default exception handler, which simply prints the stack trace to the console.

The following are general-purpose exception classes that can be used when appropriate in your Visage code:

java.lang.Exception
java.lang.IllegalArgumentexception
java.lang.IllegalStateException
java.lang.RuntimeException
java.lang.UnsupportedOperationException
Handling Exceptions

To handle potential exceptions that may be thrown from a piece of code, use a try-catch-finally expression.

A try-catch-finally expression starts with a try block, which is introduced by the try keyword and encloses the code that may throw exceptions. This, in turn, is followed by zero or more catch clauses and an optional finally block. A catch clause is introduced by the catch keyword, followed by a pair of parentheses that encloses an exception specification in the same format as an explicitly or implicitly typed function parameter, and a block that is the handler for the specified exception. Only subclasses of java.lang.Throwable are allowed in exception specifications of catch clauses. If a type is not specified, the inferred type of an exception specification is java.lang.Throwable. A finally block is introduced by the finally keyword followed by a block.

At least one catch clause or finally block must be present in a try-catch-finally expression.

When a try-catch-finally expression is evaluated, the try block is executed. If no exception is thrown, the finally block, if present, is executed, and execution continues past the try-catch-finally expression. If an exception is thrown, the catch blocks are checked one by one for compatibility with the thrown exception. If a compatible catch clause is found, the exception is handled by the try-catch-finally expression. If the exception is handled, its handler and the finally block, if present, are executed, and execution continues past the try-catch-finally expression. If the exception is not handled, the finally block, if present, is executed, and the exception is propagated outside the try-catch-finally expression.

In Listing A-36, we define a wheelOfException() function that throws the five general-purpose exceptions you learned in the last section and then call the function through several levels of function calls in a try-catch-finally expression.

Listing A-36. Handling Exceptions

import java.lang.*;

function wheelOfException(i:Integer):Void {
  if (i == 0) throw new Exception("Zero");
  if (i == 1) throw new IllegalArgumentException("One");
  if (i == 2) throw new IllegalStateException("Two");
  if (i == 3) throw new RuntimeException("Three");
  if (i == 4) throw new UnsupportedOperationException("Four");
  println("Out of exceptions for i = {i}");
}

function catchExceptions(i:Integer):Void {
  try {
    wheelOfException(i);
  } catch (iae:IllegalArgumentException) {
    println("Caught IllegalArgumentException with message: "
             "{iae.getMessage()}.");
  } catch (ise:IllegalStateException) {
    println("Caught IllegalStateException with message: "
             "{ise.getMessage()}.");
  } catch (uoe:UnsupportedOperationException) {
    println("Caught UnsupportedOperationException with message: "
             "{uoe.getMessage()}.");
  } catch (re:RuntimeException) {
    println("Caught RuntimeException with message: "
             "{re.getMessage()}.");
  } catch (e:Exception) {
    println("Caught Exception with message: "
             "{e.getMessage()}.");
  } finally {
    println("Reached finally block.");
  }
}

function callWheelOfException() {
  for (i in [0..5]) {
    catchExceptions(i);
  }
}

callWheelOfException ();

When we run the program in Listing A-36, the following output is printed to the console:


Caught Exception with message: Zero.

Reached finally block.

Caught IllegalArgumentException with message: One.

Reached finally block.

Caught IllegalStateException with message: Two.

Reached finally block.

Caught RuntimeException with message: Three.

Reached finally block.

Caught UnsupportedOperationException with message: Four.

Reached finally block.

Out of exceptions for i = 5

Reached finally block.

Two points from the preceding example are worth noting:

  • The catch clauses in the try-catch-finally expression are ordered from the most specific to the least specific. Because RuntimeException extends Exception, and the other three exceptions extend RuntimeException, we have to put the catch clauses for the other three exceptions before that of RuntimeException, and we also have to put the catch clause for RuntimeException before that of Exception. Had the order been reversed, the catch clause for Exception would have caught all the exceptions because all five exceptions are compatible with Exception.
  • The finally block is always executed regardless of whether an exception is thrown.

Working with Classes

You create class types by defining classes. Visage’s class facility supports instance variables and instance functions, member access control, and inheritance. Classes can be instantiated at runtime to create objects. Objects are linked together to form a network of objects that communicate by calling each other’s instance functions. Such systems of well-behaved communicating objects are called object-oriented software systems. Visage’s class facilities make it an object-oriented programming language, just like its function facilities make it a functional programming language.

Class Definitions

A class definition is introduced by the class keyword, followed by the name of the class, an optional superclass and mix-in list, and a pair of braces that encloses zero or more class members. The superclass and mix-in list, if present, start with the keyword extends, followed by a comma-separated list of names of classes that serve as the superclasses and mix-ins of the class being defined. Four kinds of members may be included in a class definition: instance variables and constants, instance functions, the init block, and the postinit block. Instance variable and constant declarations have the same form as script variable and constant declarations. Instance function definitions have the same form as script function definitions but may use two more kinds of expressions in their function bodies: this and super. The init block is introduced by the init keyword, followed by a block. The postinit block is introduced by the postinit keyword, followed by a block. A class definition may include zero or more instance variables and constants, zero or more instance functions, an optional init block, and an optional postinit block.

In this section and the next, we examine class definitions to see how each part of a class definition works. In the example in Listing A-37, we define a class that keeps track of coins in a piggy bank. For simplicity, we consider only pennies (1 cent) and nickels (5 cents). The class has tree instance variables and two instance functions. After the class definition, we create two instances of the class using object literal expressions and invoke instance functions on the object references.

Listing A-37. A PiggyBank Class

import java.lang.*;

class PiggyBank {
  var name: String;
  var pennies: Integer;
  var nickels: Integer;

  function putInPennies(count: Integer):Void {
    if (count <= 0) {
      throw new IllegalArgumentException("count must be positive.");
    } else {
      pennies += count;
      println("You put {count} pennies into {name}.");
    }
  }

  function putInNickels(count: Integer):Void {
    if (count <= 0) {
      throw new IllegalArgumentException("count must be positive.");
    } else {
      nickels += count;
      println("You put {count} nickels into {name}.");
    }
  }

  function total():Integer {
    pennies + nickels * 5
  }
}

var myPiggyBank = PiggyBank { name: "My Piggy Bank" };
myPiggyBank.putInPennies(7);
myPiggyBank.putInNickels(3);
myPiggyBank.putInNickels(1);
println("{myPiggyBank.name} has {myPiggyBank.total()} cents:  "
  "{myPiggyBank.pennies} pennies and {myPiggyBank.nickels} nickels.");

var yourPiggyBank = PiggyBank { name: "Your Piggy Bank" };
yourPiggyBank.putInPennies(4);
yourPiggyBank.putInNickels(6);
yourPiggyBank.putInPennies(9);
println("{yourPiggyBank.name} has {yourPiggyBank.total()} cents:  "
  "{yourPiggyBank.pennies} pennies and {yourPiggyBank.nickels} nickels.");

When we run the program from Listing A-37, the following output is printed to the console:


You put 7 pennies into My Piggy Bank.

You put 3 nickels into My Piggy Bank.

You put 1 nickels into My Piggy Bank.

My Piggy Bank has 27 cents:  7 pennies and 4 nickels.

You put 4 pennies into Your Piggy Bank.

You put 6 nickels into Your Piggy Bank.

You put 9 pennies into Your Piggy Bank.

Your Piggy Bank has 43 cents:  13 pennies and 6 nickels.

One thing to note in this example is that we instantiated the class twice to get two distinct instances of the same class. Even though the two instances are created from the same class, they are independent of each other. You can contrast this with the queue example in Listing A-28, where we use a script variable to represent the queue, and consequently, only one queue exists in the program. You can think of classes as molds out of which we make instances.

Class definitions are not expressions. They can appear only at the top level of a Visage source file. Visage does not support anonymous class expressions, which would be analogous to anonymous function expressions. A class definition does, however, introduce a new type, called a class type, which you can use in your programs. The class name is also the name of the class type introduced by the class. Object references created by object literal expressions have class types. In Listing A-37, the variables myPiggyBank and yourPiggyBank are both of type PiggyBank.

In Visage, each class name introduced by class definitions live in its own space, which is different from the space where names of variables and constants live, as well as the space where function names live. As a result, in a Visage source file, you can define a class foo, a function foo, and a variable foo, and the compiler will happily compile your source file. However, by convention (not enforced by the compiler), Visage class names start with an uppercase letter, whereas variable names and function names start with lowercase letters.

Instance Variables

In Listing A-37, we defined the class PiggyBank with three instance variables. Here is how they are declared:

var name: String;
var pennies: Integer;
var nickels: Integer;

Although these look like ordinary script-level variable declarations and local variable declarations in a function body or a block, they mean quite different things. For one thing, script-level and local variable declarations are expressions that, when executed, create the variable right there and then. Instance variable declarations in a class are not expressions and therefore are not executed immediately. The effects of instance variable declarations are felt when an instance of the class is created with an object literal expression.

An object literal expression may include initializers for instance variables of the class. Both the instance variable declaration in the class definition and the instance variable initializer in the object literal expression have influence on the instance variable.

Like ordinary variable declarations, instance variable declarations may include information about the variable’s name, type, default initializer, and trigger. Here is an example of a class with a fuller-featured instance variable declaration:

class A {
  var i: Integer = 1024 on replace {
    println("Trigger in class definition: i = {i}.");
  }
}

This class declares an instance variable i of type Integer, with a default initializer of 1024 and a trigger that fires when the instance variable’s value changes.

In object literal expressions, you can override instance variables, and you can provide initializers for instance variables. Instance variable overrides must include the override modifier before the var keyword and can override the default initializer or the trigger. Here are some examples of object literal expressions that create instances of class A:

var a1 = A {}
var a2 = A { i: 2048 }
var a3 = A {
  override var i = 3072 on replace {
    println("Trigger in object literal override: i = {i}.");
  }
}
var a4 = A {
  override var i = 4096 on replace {
    println("Trigger in object literal override: i = {i}.");
  }
  i: 5120
}

The type of the instance variable is dictated by the instance variable declaration in the class. You cannot change the type of the instance variable in an object literal. So naturally a1.i, a2.i, a3.i, and a4.i are Integer variables, just as the class definition indicates.

The value of the instance variable may come from three sources: the instance variable initializer in the object literal expression, the default initializer of the instance variable override in the object literal expression, and the default initializer of the instance variable declaration in the class definition. These sources are searched in this order until a value is found. In the case of a1, neither an instance variable initializer nor an instance variable override is found in the object literal expression, so the value of a1.i comes from the instance variable declaration in the class definition, which gives a1.i a value of 1024. For a2, the instance variable initializer in the object literal gives a2.i the value of 2048. For a3, no instance variable initializer exists, but it does have an instance variable override, so a3.i gets its value 3072 from the override. And finally, a4.i gets its value 5120 from the instance variable initializer.

Triggers will be covered in the “Triggers” section later in this appendix. For now, just understand that triggers are blocks of code that can be attached to a variable so that they are executed every time the variable changes its value. The on replace triggers that we use in the preceding example are the simplest triggers. When you override an instance variable in an object literal, the trigger you provide does not replace the trigger in the instance variable declaration in the class definition. Instead, both triggers are added to the instance variable, and both will fire when the instance variable’s value changes. Therefore, a1.i and a2.i each have one trigger attached, whereas a3.i and a4.i each have two triggers attached.

images Note Triggers are cumulative with instance variable overrides because one of the uses for triggers is to maintain some invariants on a variable. For example, instance variables that represent the red, green, and blue values of a color may have triggers that keep their values within the legal range. These triggers will remain in effect even if the instance variable is overridden.

In the program presented in Listing A-38, you can see the forces that influence the instance variables at work.

Listing A-38. Instance Variable Initializations

class A {
  var i: Integer = 1024 on replace {
    println("Trigger in class definition: i = {i}.");
  }
}

var a1 = A {};
var a2 = A { i: 2048 };
var a3 = A {
  override var i = 3072 on replace {
    println("Trigger in object literal override: i = {i}.");
  }
};
var a4 = A {
  override var i = 4096 on replace {
    println("Trigger in object literal override: i = {i}.");
  }
  i: 5120
};

println("a1.i = {a1.i}");
println("a2.i = {a2.i}");
println("a3.i = {a3.i}");
println("a4.i = {a4.i}");

When we run the program in Listing A-38, the following output is printed to the console:


Trigger in class definition: i = 1024.

Trigger in class definition: i = 2048.

Trigger in class definition: i = 3072.

Trigger in object literal override: i = 3072.

Trigger in class definition: i = 5120.

Trigger in object literal override: i = 5120.

a1.i = 1024

a2.i = 2048

a3.i = 3072

a4.i = 5120

Notice that the triggers are fired because when the initial values of the instance variables took effect, they all changed their values from 0 to the new value.

Declaring Default Instance Variables

You can mark one of the member variables in a class as the default. This makes it possible to initialize it in an object literal without the use of a label, offering a convenient shorthand. To declare a variable as default you use the default keyword as shown here:

class A {
  default var i: Integer = 1024;
}

You can then instantiate instances of this object by simply including the value in the object literal constructor with no label required:

var a = A { 2056 }

Each class can only have one default member variable. It is a compiler error to define multiple default variables. Default variables are inherited from superclasses but can be overridden by explicitly marking another variable as default in the subclass.

Instance Functions

Instance function definitions are function definitions that appear inside a class definition. Aside from the top level of a Visage source file, this is the only other place where function definitions may appear. You saw examples of instance functions in the PiggyBank class in Listing A-37.

Instance function definitions have the same form as script function definitions, and two more keywords may be used in instance function bodies: this and super.

A natural question to ask is, “What can instance function bodies see?” The body of an instance function definition has access to four kinds of variables:

  • Script variables and constants declared in the same Visage source file
  • Instance variables declared in the same class as the instance function
  • Function parameters that are passed into the function invocation expression
  • Local variables and constants that are defined in the instance function body

The instance function body also has access to other instance functions in the same class, and script functions and other classes in the same source file. This list of accessible entities to instance functions will grow when you learn how to organize your Visage code into multiple files and how to make entities from one file accessible from other files in the “Organizing Visage Code” section later in this appendix.

Listing A-39 illustrates the accessibility of the aforementioned variables.

Listing A-39. Instance Functions Example

var total: Integer = 0;

function displayTotal() {
  println("The total in all piggy banks is {total} cents now.");
}

class PiggyBank {
  var name: String;
  var pennies: Integer = 0;

  function putInPennies(n: Integer) {
    println("Putting {n} pennies into {name}.");
    pennies += n;
    total += n;
    displayTotal();
  }
}

var myPiggyBank = PiggyBank { name: "My Piggy Bank" };
var yourPiggyBank = PiggyBank { name: "Your Piggy Bank" };

myPiggyBank.putInPennies(15);
yourPiggyBank.putInPennies(22);
myPiggyBank.putInPennies(6);

println("{myPiggyBank.name} has {myPiggyBank.pennies} cents.");
println("{yourPiggyBank.name} has {yourPiggyBank.pennies} cents.");

Listing A-39 contains a script variable, total; a script function, displayTotal(); a class, PiggyBank, that has two instance variables, name and pennies; and an instance function, putInPennies(). The instance function putInPennies() uses the script variable total, the instance variable pennies, and the parameter n, and calls the script function displayTotal(). This program allows you to maintain any number of piggy banks, and it knows the total amount in all the piggy banks created in the program.

When we run the program in Listing A-39, the following output is printed to the console:


Putting 15 pennies into My Piggy Bank.

The total in all piggy banks is 15 cents now.

Putting 22 pennies into Your Piggy Bank.

The total in all piggy banks is 37 cents now.

Putting 6 pennies into My Piggy Bank.

The total in all piggy banks is 43 cents now.

My Piggy Bank has 21 cents.

Your Piggy Bank has 22 cents.

In the last section, you learned that instance variables live only within instances of the class. Because instance functions make use of instance variables, the invocation of instance functions must also be done through instances of the class. In Listing A-39, we invoke the instance function putInPennies() as follows:

myPiggyBank.putInPennies(15);

In this instance function invocation expression, the myPiggyBank instance of the PiggyBank class supplies the instance variables used in the putInPennies() function. The object through which an instance function is invoked is also called the target of the invocation.

images Note You can override instance functions in object literal expressions. Examples of instance function overrides can be found in the “Object Literals” section earlier in this appendix.

The this Expression

The target of an instance function invocation is available to the instance function body as the this expression. It is perhaps the simplest Visage expression, consisting of the this keyword only. Its type is the type of the class, and it evaluates to the target of the invocation. The this expression can be used just like any other expression in the instance function body. It can be passed to other functions as parameters, and it can be used as the return value of the instance function. You can also use the this expression to access the instance variables and functions of the target object, although you already have a simpler way of accessing them by using their names directly. The putInPennies() instance function from Listing A-39 can be rewritten as follows:

function putInPennies(n: Integer) {
  println("Putting {n} pennies into {this.name}.");
  this.pennies += n;
  total += n;
  displayTotal();
}

The target of an instance function invocation is not necessarily of the class where the instance function is defined. An instance function can be invoked on objects of derived classes.

The this expression can also appear in other parts of the class definition, including in instance variable declarations, init blocks, and postinit blocks.

init Blocks

Class definitions can also include init and postinit blocks. An init block is introduced by the init keyword, followed by a block. A postinit block is introduced by the postinit keyword, followed by a block. The init and postinit blocks of a class are executed as an object is being created.

Both the init and postinit blocks of a class are executed during the evaluation of object literal expressions. They are guaranteed to execute after all the instance variable initializers are evaluated.

The postinit block is further guaranteed to execute after the instance initializers of the most derived class are evaluated. This feature is useful when designing object hierarchies.

The simple example shown in Listing A-40 illustrates the relative order in which the various parts of an object literal expression are executed. We will also use some nonconventional expressions as instance variable initializers. Most Visage classes do not have to be written this way.

Listing A-40. init Blocks

var counter = 0;

class Base {
  var i: Integer;
  init {
    println("Step {counter}: Base init block.  i = {i}.");
    counter++;
  }
  postinit {
    println("Step {counter}: Base postinit block.  i = {i}.");
    counter++;
  }
}

class Derived extends Base {
  var str: String;
  init {
    println("Step {counter}: Derived init block.  i = {i}, str = {str}.");
    counter++;
  }

  postinit {
    println("Step {counter}: Derived postinit block.  i = {i}, str = {str}.");
    counter++;
  }
}

var o = Derived {
  i: {
    println("Step {counter}: i initialized to 1024.");
    counter++;
    1024;
  }
  str: {
    println('Step {counter}: str initialized to "Hello, World".'),
    counter++;
    "Hello, World";
  }
}

The initializers in the object literal expression might look a little bit strange, but rest assured that they are legitimate Visage expressions.

When we run the program in Listing A-40, the following output is printed to the console:


Step 0: i initialized to 1024.

Step 1: str initialized to "Hello, World".

Step 2: Base init block.  i = 1024.

Step 3: Derived init block.  i = 1024, str = Hello, World.

Step 4: Base postinit block.  i = 1024.

Step 5: Derived postinit block.  i = 1024, str = Hello, World.

Notice that the init and postinit blocks are executed after the instance variable initializers. Notice also that the postinit block of Base is executed after the init block of Derived.

Creating Class Hierarchies

Visage supports object-oriented programming through its class facilities. It supports such concepts as data abstraction, encapsulation, polymorphism, and inheritance. In this section, we teach you how to extend Visage classes and point out the important features of the language such as dynamic dispatch that make object-oriented programming possible.

Mix-in Classes

Visage supports a form of inheritance called mix-in inheritance. A Visage class can optionally extend one Visage class and any number of Visage mix-in classes. A mix-in class is a class whose primary purpose is to be extended by other classes and cannot be instantiated directly. Such a class is defined by using the mix-in modifier before the class keyword. Like regular classes, mix-in classes may contain instance variable and constant declarations, instance function definitions, init blocks, and postinit blocks. Here is an example:

mixin class Locatable {
  var x: Number;
  var y: Number;

  function moveToOrigin() {
    x = 0.0;
    y = 0.0;
  }

  function setLocation(x: Number, y: Number) {
    this.x = x;
    this.y = y;
  }
}

The mix-in class Locatable will cause classes that extend it to have instance variables x and y and instance functions moveToOrigin() and setLocation().

Extending Classes

You extend Visage classes by including a superclass and mix-in list in a class definition. The superclass and mix-in list appear after the class name and consist of the extends keyword followed by a comma-separated list of class names. The list may contain an optional regular (non-mix-in) class and any number of mix-in classes. The regular class in the superclass and mix-in list is called a superclass of the class. The mix-in classes in the superclass and mix-in list are called parent mix-ins of the class. A class is called a subclass of its superclass and a mixee of its parent mix-ins.

A class inherits its superclass’s instance variables, instance constants, and instance functions to which the writer of the superclass has allowed access. The smallest unit of access rights in Visage is the Visage source file. Therefore, if a subclass is in the same file as the superclass, it inherits all of its instance variables, instance constants, and instance functions.

A mixee will inherit its parent mix-in’s instance variables, instance constants, and instance functions to which the writer of the parent mix-in has allowed access.

Up to this point in the appendix, we have used single-source-file programs to demonstrate language features. As you might imagine, this approach will soon become inadequate because restricted access rights can only be demonstrated with multifile programs. And most real-world applications consist of more than one file; some may have hundreds of files. We will cover code organization in the “Organizing Visage Code” section, which discusses the details of access modifiers and access control. For the remainder of this section, we will operate under the single-source-file model, where everything is accessible by everything else.

In Listing A-41, class C extends classes A and mix-in class B. It inherits instance variables and instance functions from both A and B.

Listing A-41. Inheritance

class A {
  var b: Boolean;
  function f() {
    println("A.f() called: b is {b}.");
  }
}

mixin class B {
  var i: Integer;
  function g() {
    println("B.g() called: i is {i}");
  }
}

class C extends A, B {
  var n: Number;
  function h() {
    println("C.h() called: b = {b}, i = {i}, n = {n}.");
  }
}

var c = C { b: true, i: 1024, n: 3.14 };
c.f();
c.g();
c.h();

When we run the program in Listing A-41, the following output is printed to the console:


A.f() called: b is true.

B.g() called: i is 1024

C.h() called: b = true, i = 1024, n = 3.14.

Notice that in the object literal expression we initialize three instance variables, b, i, and n. The resulting object reference is assigned to the variable c, which the code will infer is type C. And we invoke three instance functions, f(), g(), h(), through c. Regarding the code that uses class C, the instance variables and instance functions C inherited from A and B can be used the same way as those defined directly in C.

images Note A mix-in class itself can extend other mix-in classes, but a mix-in class cannot extend a regular class. If you are familiar with Java programming, the distinction between Visage classes and mix-in classes is analogous to that between Java classes and interfaces.

Overriding Instance Variables

An instance variable override is an instance variable declaration that has the same name as an instance variable of one of its direct or indirect superclasses. It must include override as one of its modifiers and can have other access modifiers, as well as a default initializer and/or a trigger. It must not include the type specifier.

The overriding default initializer will be used instead of the superclass default initializer as the initializer for the instance variable if an initializer is not provided in an object literal expression. The overriding trigger will be added to the set of triggers of the instance variable so that both the superclass triggers, and the overriding triggers will fire when the value of the instance variable is changed. Listing A-42 shows a simple example of an instance variable override.

Listing A-42. Instance Variable Override

class A {
  var i: Integer = 1024;
}

class B extends A {
  override var i = 2048;
}

var a = A {};
var b = B {};

println("a.i = {a.i}.");
println("b.i = {b.i}.");

When we run the program in Listing A-42, the following output is printed to the console:


a.i = 1024.

b.i = 2048.

Because we do not provide an initializer in the object literal expressions, the instance variable a.i is initialized by the default initializer of i in the class A, which is 1024, and the instance variable b.i is initialized by the default initializer of i in the class B, which is 2048.

Overriding Instance Functions

An instance function override is an instance function definition that has the same name, number, and type of variables as an instance function in the superclass or the parent mix-in. It must include override as one of its modifiers and may have other access modifiers. The overriding instance function must have the same return type as the overridden instance function.

When an overridden instance function is invoked through an object of the subclass type, the overriding instance function’s body is executed. If the instance function is invoked through an object of the superclass type, the body of the original version of the instance function is executed. Listing A-43 shows an example of an instance function override.

Listing A-43. Instance Function Override

class A {
  function f(i:Integer):Integer {
    println("A.f() is invoked with parameter i = {i}.");
    i + 3
  }
}

class B extends A {
  override function f(i:Integer):Integer {
    println("B.f() is invoked with parameter i = {i}.");
    i * 5;
  }
}

var a = A {};
var b = B {};
var ra = a.f(4);
var rb = b.f(7);
println("a.f(4) = {ra}.");
println("b.f(7) = {rb}.");

When we run the program in Listing A-43, the following output is printed to the console:


A.f() is invoked with parameter i = 4.

B.f() is invoked with parameter i = 7.

a.f(4) = 7.

b.f(7) = 35.

Because a is an instance of A and not an instance of B, a.f(4) invokes the version of f() defined in class A. Similarly, because b is an instance of B, b.f(7) invokes the version of f() defined in class B.

If you accidentally leave out the override modifier when overriding an instance function, the Visage compiler will issue an error message telling you so.

Notice that when the Visage compiler tries to figure out whether an instance function is an override of a superclass or parent mix-in instance function, it checks the name, the number of the parameters, and the type of each parameter. The names of the parameters are not considered. Having determined that an instance function definition is an override of a superclass instance function, the compiler will further check whether they have the same return type and reports an error if the overriding instance function has a different return type.

The super Keyword

When one class extends another class or mix-in class, you can use the super keyword in the subclass or mixee’s instance function bodies, but only as the target of instance function invocations. The super keyword will find a matching instance function from the instance functions of the superclasses or parent mix-ins. Although it can be used for any instance functions of any superclass or parent mix-in, its use is necessary only if the instance function in the superclass or parent mix-in is overridden in the subclass or mixee. Nonoverridden instance functions of the superclass and parent mix-ins may be invoked directly without using the super target.

The technique of invoking an overridden instance function is useful in certain programming tasks. Listing A-44 presents a fun example that illustrates the use of the super keyword.

Listing A-44. Using the super Keyword

class Hamburger {
  function whatsInIt() {
    "Beef patties"
  }
}

class HamburgerWithCheese extends Hamburger {
  override function whatsInIt() {
    "{super.whatsInIt()} and cheese"
  }
}

var hamburger = Hamburger {};
var cheeseburger = HamburgerWithCheese {};
println("hamburger.whatsInIt() = {hamburger.whatsInIt()}.");
println("cheeseburger.whatsInit() = {cheeseburger.whatsInIt()}.");

Simple as this example is, it illustrates one principle of object-oriented programming: information ought to be stored in one place. By calling super.whatsInIt(), the HamburgerWith-Cheese class can avoid its listing of what’s in it ever going out of sync with that of Hamburger.

When we run the program in Listing A-44, the following output is printed to the console:


hamburger.whatsInIt() = Beef patties.

cheeseburger.whatsInit() = Beef patties and cheese.

Using Abstract and Mix-in Classes

One style of object-oriented programming calls for the separation of the interfaces of an object from the implementation details. Under this context, an interface of a class consists of the function types of its important instance functions and implementation details of a class consists of the bodies of these instance functions.

Visage supports the concept of interfaces through its abstract instance function, abstract class, and mix-in facilities.

An abstract instance function in a class or a mix-in class is an instance function definition without a body, prefaced with the keyword abstract. A class that includes an abstract instance function is automatically an abstract class and must be marked by the abstract keyword. A class that does not include any abstract instance functions can also be made abstract by marking it with this keyword. A mix-in class is implicitly abstract. In the following discussion, we focus on abstract classes. However, the principles discussed also apply to mix-in classes.

You cannot create instances of abstract classes with object literal expressions. So how are abstract classes useful? The answer is twofold. Obviously, you have to extend the abstract classes to fill in the missing function bodies by overriding the abstract instance functions. This is sometimes called implementing the interface. On the other hand, you can hide the implementation details from the code that uses the class by using only the abstract class. The calling code will not create instances of the implementing classes directly using object literal expressions because that will defeat the purpose of hiding the implementation detail from the calling code. A client will get its instance using some other means.

Listing A-45 illustrates this separation of interface from the implementation detail by using abstract classes.

Listing A-45. Separating an Interface from Implementation Details with an Abstract Class

// The interface
abstract class Food {
  abstract function getName():String;
  abstract function whatsInIt():String;
}

// The implementation
class Hamburger extends Food {
  override function getName() {
    "Hamburger"
  }
  override function whatsInIt() {
    "beef patties"
  }
}

// A function that gives the calling code an instance of Food
function getTodaysSpecial():Food {
  Hamburger {}
}

// The calling code, no direct mentioning of Hamburger is made here
var food = getTodaysSpecial();
println("Today's special is {food.getName()}.  "
        "It has {food.whatsInIt()} in it.");

In practice, one group develops the interface, the implementation, and the function that hands out the instance, and another group develops the calling code. The calling code never directly uses the name of the implementation detail class.

When we run the program in Listing A-45, the following output is printed to the console:


Today's special is Hamburger.  It has beef patties in it.

Another important detail to learn from Listing A-45 has to do with the type of the variable food. Because it does not have an explicit type, it gets its type from its initializer. In our case, the initializer is a function invocation expression, which would have a type that is the same as its return type. So food is a variable of type Food. However, when the compiler sees the instance function invocation expressions food.getName() and food.whatsInIt(), it does not merely determine that the instance functions are abstract, and therefore, the calls are invalid. It will instead wait until runtime to choose which version of these instance functions to execute based on the type of the value of the variable. When we run the program, the food variable will contain an instance of the Hamburger subclass, and the instance function overrides in that class are executed.

This way of choosing which instance function to execute based on the type of the value at runtime is called dynamic dispatch. (You may be familiar with the same concept being referred to as dynamic binding or late binding in other languages. However, because the term binding means something quite different in Visage, we will stick to the term dynamic dispatch here.)

Conflict Resolution in Mix-in Inheritance

Visage supports a restricted version of multiple inheritance using mix-ins. Mix-in inheritance is designed to support the common use cases of multiple inheritance while reducing the complexity and error-prone nature of full-blown multiple inheritance.

The key to the simplification of mix-in inheritance lies in the way conflicts are resolved when a mixee extends multiple parent mix-ins that have competing public instance variable names. In mix-in inheritance, a parent mix-in will cause the mixee to have the parent mix-in’s instance variables, if the mixee does not already have them. This process is applied for all the parent mix-ins in the order that they appear in the superclass and mix-ins list of the mixee. As a consequence, if two parent mix-ins have the same public instance variable with the same type, the mixee gets only one copy of the variable, not two.

This can be illustrated with the following example:

mixin class A {
  public var i:Integer;
  function f():Void {
    println("f() called: i = {i}.");
    i++;
  }
}

mixin class B {
  public var i: Integer;
  function f():Void {
    println("shadowed!");
  }
  function g() {
    println("g() called: i = {i}.");
    i++;
  }
}

class C extends A, B { }

var o = C { i: 1024 }
o.f();
o.g();
o.f();

Since both parent mix-in A and parent mix-in B have a public instance variable i of Integer type, the mixee C gains the instance variable i as mix-in inheritance processes A. By the time the parent mix-in B is processed, C already has instance variable i, so parent mix-in B’s requirement that its mixee have instance variable i is met, and C does not gain an additional copy of i.

When this program is run, you should see the following result:


f() called: i = 1024.

g() called: i = 1025.

f() called: i = 1026.

Understanding that all parent mix-ins share the same set of public instance variables makes sense when you think of them as part of the public interface to the class. If you are using variables instead as part of the internal implementation of your class, leave off the public modifier to declare them as script-private. This will change the mix-in behavior to variable shadowing, where each mix-in gets its own unique copy of the variable.

A compilation error results if two parent mix-ins have instance variables of the same name but different types. A compilation error also results if the mixee has, or inherits from its superclass, an instance variable with the same name as an instance variable of a parent mix-in but with different types. Similarly, if an instance function is declared in multiple parent mix-ins, or a parent mix-in and a mixee, or a parent mix-in and a superclass with the same name but with non-override-compatible function types, a compilation error results.

Casting and the instanceof and as Operators

The ability to assign an instance of a subclass or a mixee to a variable of a superclass or a parent mix-in type is a fundamental feature of the Visage language. It is what makes dynamic dispatching possible.

Sometimes, a situation may arise that necessitates your code to test whether a value in a superclass or a parent mix-in type variable is indeed an instance of a subclass or a mixee type. This is done with the instanceof operator. The instanceof expression consists of a value expression, the instanceof keyword, and a type name.

Here are some examples:

class A {}
class B extends A {}

var a:A;
a = A {}
println(a instanceof B);
a = B {}
println(a instanceof B);

The variable a is of static type A. We first assign an instance of A to it and print out the value of a instanceof B, which should be false. We then assign an instance of B to a and print out the value of the same expression, which this time should be true.

Once you have ascertained that a superclass or parent mix-in variable is holding an instance of a subclass or a mixee type, you can cast it to the subclass or mixee type. The cast expression consists of a value expression, the as keyword, and a type name. You may want to cast an expression to a subclass or a mixee type so that you can assign it to a variable of the subclass or mixee type or use it to access instance variables and instance functions that are defined in the subclass or mixee but not in the superclass or the parent mix-in. The compiler will not allow you to do these things without a cast, because the compiler has no knowledge of the dynamic type of values. Listing A-46 shows some examples of casting.

Listing A-46. Casting Examples

class A {
  function f() {
    println("A.f() called.");
  }
}

class B extends A {
  function g() {
    println("B.g() called.");
  }
}

var a: A;
a = B {}
a.f();
(a as B).g();

We assign an instance of subclass B to the variable a of superclass type A. We can call instance function f() of A directly, but we have to cast a into B to call the instance function g() of B.

If you try to cast a value to a wrong type, a java.lang.ClassCastException will be thrown.

This concludes our coverage of class types. You now know the four kinds of data types in Visage—primitive types, sequence types, function types, and class types—and can implement expressions involving these data types.

Organizing Visage Code

The single-file programs you have been writing and experimenting with so far do not represent an efficient way to develop larger Visage applications. Fortunately, Visage has some code organization features that help you to split your code into multiple files and create a hierarchical structure:

  • Scripts: A script is a single Visage source file that can be run as a program.
  • Modules: A module is a single Visage source file whose main purpose is to be used by other Visage source files.
  • Packages: A Visage source file can be declared to belong to a named package. Packages make it much easier to avoid name conflicts when you use libraries of different origins together.
  • Access modifiers: Access modifiers can be applied to classes, functions, class members, and so forth to make them visible to the outside world. Some access modifiers also control what can be done to a variable from the outside world.

The concepts examined in this section open up the single Visage file model for access from other Visage files.

Scripts

All the programs that you have seen up to this point are examples of scripts in Visage. A script is a Visage source file that can be compiled and then run and may contain class definitions, function definitions, variable and constant declarations, and loose expressions. Loose expressions are expressions that appear at the top level of a script.

Scripts are saved in files on a file system. Visage files use the .visage file name extension, which the Visage compiler visagec requires. The file name without the .visage extension will become the script’s name. A script is run with the visage command followed by the script name.

Following is a traditional Hello World example, presented as a script named HelloWorld that is saved in the HelloWorld.visage file; as you can see, it contains a comment line and a loose function invocation expression:

// HelloWorld.visage
println("Hello, World")

We compile and run the script with the following command lines:

$ visagec HelloWorld.visage
$ visage HelloWorld

Here the $ character represents the operating system command prompt. This prompt may be something different on your operating system. The compilation step will compile the script file into a Java class file, HelloWorld.class.

Of course, the traditional greeting appears when we run the script:


Hello, World

An alternative way of organizing a script is to define a function named run that takes either no arguments or a sequence of String values as arguments and move all the loose expressions into it. The run() function is the entry point to the script. And if you use a run() function that takes a sequence of Strings as arguments, the arguments sequence will contain any command-line arguments that you specify on the Visage command line after the name of the script. The Visage compiler actually transforms a script with loose expressions into a script with a run() function automatically. As far as the Visage runtime system knows, all scripts are of the neater form containing class definitions, function definitions, and variable and constant definitions, and one of the function definitions just happens to have the name run().

Here is an example script that prints out the command-line arguments, one per line:

function run(args: String[]) {
  for (arg in args) {
    println(arg);
  }
}

Modules

A module is a Visage source file that contains class definitions, function definitions, and variable and constant declarations that are intended for use in other Visage source files.

You make a class, a function, a variable, or a constant usable by other Visage source files by prefacing its definition or declaration with an access modifier. Without an access modifier, such items can be used only by the source file in which they appear. This is called script-level access. Two access modifiers are available that widen the scope of these entities. The public modifier enables any Visage source file anywhere to use these entities. This level of access is called public access. The package modifier allows any Visage source file that is tagged to be in the same package as the module itself to use the functions, classes, variables, and constants within that module. This is package-level access.

A module is said to export the entities it contains that have either package-level or public access. The Visage compiler does not allow source files that export their entities to also contain loose expressions. So the only way that a module can also be a script is to have a run() function. And in most cases, your modules will not also be scripts.

A module’s name is its file name without the .visage extension. An exported entity from a module is known to the outside world by a qualified name, which is the module name followed by a dot (.) and the entity name.

Listing A-47 shows an example of a module, and Listing A-48 presents a script that makes use of the module.

Listing A-47. A Queue Module

// Queue.visage
public var q:Integer[];

public function enqueue(i:Integer):Void {
  insert i into q;
  println("Enqueued {i}.  Size of q is {sizeof q} now.");
}

public function dequeue():Integer {
  if (sizeof q == 0) {
    println("Size of q is {sizeof q} now. Returning -1.");
    return -1;
  } else {
    var i = q[0];
    delete q[0];
    println("Dequeued {i}.  Size of q is {sizeof q} now.");
    return i;
  }
}

Listing A-48. A Program That Uses the Queue Module

// QueueDemonstration.visage
Queue.enqueue(1);
Queue.enqueue(2);
Queue.enqueue(3);
println(Queue.dequeue());
println(Queue.dequeue());
Queue.enqueue(4);
println(Queue.dequeue());
println(Queue.dequeue());
println(Queue.dequeue());

This is the same queue implementation provided in the “Function Body Scope” section earlier in this appendix, in Listing A-28. Here, we simply pull the queue implementation into its own module, export the functions by giving them public access, and modify the demonstration part by qualifying the function calls with the module name. Notice that the variable that holds the content of the queue is kept at the script level of access. This prevents it from being accidentally modified by foreign code.

images Tip Although you can create modules with unconventional characters in its names, doing so may render the module unusable within Visage programs. We recommend using module names that are valid Visage identifiers.

A Special Provision for Classes

Because of its Java heritage, Visage supports a special arrangement for classes that are defined in Visage source files that also bear the name of the class. This is, by far, the most popular code organization for Java projects.

If a class is defined in a source file bearing its name, the class is referred to using the class name just once, not twice. Other entities defined in the source file, such as other classes, functions, and variables and constants, are still referred to as before. Listings A-49 and A-50 illustrate this.

Listing A-49. A Class Defined in a File with the Same Name

// Car.visage
public class Car {
  public var make: String;
  public var model: String;
  override function toString() {
    "Car {make: {make}, model: {model}}"
  }
}

public function getACar() {
  Car { make: "BMW", model: "Z5" }
}

Listing A-50. A Class Defined in a File with a Different Name

// Parts.visage
public class Wheel {
  public var diameter: Number;
  override function toString() {
    "Wheel {diameter: {diameter}}"
  }
}

Because the Car class is defined in a file named Car.visage, the outside world can refer to it just by the name Car. The getACar() function in Car.visage is still referred to as Car.getACar(). The Wheel class is not defined in a file with the same name, so Parts is considered to be a regular module, and Wheel must be referred to by the outside world as Parts.Wheel. Listing A-51 shows the different treatment for these classes.

Listing A-51. Usage of Classes

var car = Car { make: "Ford", model: "Taurus" }
var anotherCar = Car.getACar();
var wheel = Parts.Wheel { diameter: 16 }

println(car);
println(anotherCar);
println(wheel);

images Caution Because of the special treatment of classes defined in a file with the same name, some conflicts may result. For example, in such a file, you cannot define a script function that has the same name as an instance function of the class.

Running this program will print out the car and wheel instance variables:


Car {make: Ford, model: Taurus}

Car {make: BMW, model: Z5}

Wheel {diameter: 16.0}

Packages

Visage’s package facility is a mechanism for separating source files into logical name spaces. Source files belonging to the same package are considered to be more closely related than source files in different packages. Access can be granted to source files in the same package with the package modifier.

To tag a source file as belonging to a package, you put a package directive at the beginning of the source file. A package directive consists of the package keyword followed by one or more identifiers connected by dots (.) and terminated by a semicolon. The package directive must be the first noncomment, non-whitespace line in the source file. Source files that do not contain a package directive are said to belong to the unnamed package. That’s the package all of our example code in this appendix belong to so far.

images Caution The unnamed package is provided as a place to experiment with your code and is not meant to be the permanent home of your code. In Visage, as in Java, code in named packages cannot access code in unnamed packages.

Following the recommendation of the Java platform, if your Visage code will have worldwide exposure, you should use the Internet domain name of your organization in reverse order as the starting portions of package names for your code. For smaller-scale projects, a simpler package name is sufficient.

The package name becomes the leading portion of an entity’s fully qualified name.

Listings A-52 and A-53 demonstrate the use of packages.

Listing A-52. Drinks Module in a Food Package

// Drinks.visage
package food;

public class Coffee {
  public var brand: String;
}

public class Tea {
  public var kind: String;
}
package var drinksOffered:Integer;

public function getCoffee():Coffee {
  drinksOffered++;
  Coffee { brand: "Folgers" }
}
public function getTea():Tea {
  drinksOffered++;
  Tea { kind: "Iced" }
}
public function numberOfDrinksOffered() {
  drinksOffered
}

Listing A-53. Consuming Drinks from the Food Package

// ConsumeDrinks.visage
package consumer;

var coffee = food.Drinks.getCoffee();
println("Good coffee.  It's {coffee.brand}.");
var tea = food.Drinks.getTea();
println("Good tea.  It's {tea.kind} tea.");
println("Number of drinks offered = {food.Drinks.numberOfDrinksOffered()}.");

In Listing A-53, we invoke public functions getCoffee(), getTea(), and numberOfDrinksOffered() in the Drinks module of the food package through their fully qualified names, shown in bold. The drinksOffered variable in the Drinks module has package-level access and cannot be used from the calling code because it is in a different package.

The recommended way of organizing Visage source files is to use a directory hierarchy that mirrors the package hierarchy. Thus to store the code shown in Listings A-51 and A-52 into source files, we need to create two directories, food and consumer, and put Drinks.visage in the food directory and ConsumeDrinks.visage in the consumer directory. From the directory where we have created the food and consumer directories, we can compile and run the program with the following command line:

$ visagec food/Drinks.visage consumer/ConsumerDrinks.visage
$ visage consumer.ConsumeDrinks

When we invoke the visagec compiler without an explicit -d command-line argument, it will leave the compiled class files in the same directory where the source file is found. Because we start with a source directory hierarchy that mirrors the package hierarchy, this will leave the class files in the same hierarchy, which is exactly where the Visage runtime expects them. Notice also that when we finally run the program, we have to use the fully qualified module name on the visage command line.

images Caution The package keyword is used in two contexts in Visage. It is used at the beginning of a source file to tag the file as belonging to a package. It is also used as a modifier on an entity in a module to make it visible to other modules and scripts in the package.

Import Directives

Modules and packages are great ways of organizing Visage code into a structure that’s easier to maintain. However, they involve names that are longer and with more parts. Visage’s import facility will help you regain the ability to use simple names in your code, even for entities from foreign modules, classes, and packages.

The import directive consists of the import keyword, an indication of what is to be imported, and a semicolon. Four variants exist for the import directive:

  • A package name followed by .*, which imports all module names of the package
  • A package name followed by a dot (.) and a module name, which imports just that one module name from the package
  • A package name followed by a dot, a module name, and .*, which imports all the entities defined in that module
  • A package name followed by a dot, a module name, a dot, and an entity name, which imports just that entity name from the module

When modules from the same package import each other’s names, the package name may be omitted from the import directives.

Listings A-54 through A-57 show the four versions of the program in Listing A-53 we get when we applying the four import variants to it.

Listing A-54. Importing All Modules in a Package

// ConsumeDrinksImportPackageStar.visage
package consumer;

import food.*;

var coffee = Drinks.getCoffee();
println("Good coffee.  It's {coffee.brand}.");
var tea = Drinks.getTea();
println("Good tea.  It's {tea.kind} tea.");
println("Number of drinks offered = {Drinks.numberOfDrinksOffered()}.");

Listing A-55. Importing One Module in a Package

// ConsumeDrinksImportPackageModule.visage
package consumer;

import food.Drinks;

var coffee = Drinks.getCoffee();
println("Good coffee.  It's {coffee.brand}.");
var tea = Drinks.getTea();
println("Good tea.  It's {tea.kind} tea.");
println("Number of drinks offered = {Drinks.numberOfDrinksOffered()}.");

Listing A-56. Importing All Entities from a Module

// ConsumeDrinksImportModuleStar.visage
package consumer;

import food.Drinks.*;

var coffee = getCoffee();
println("Good coffee.  It's {coffee.brand}.");
var tea = getTea();
println("Good tea.  It's {tea.kind} tea.");
println("Number of drinks offered = {numberOfDrinksOffered()}.");

Listing A-57. Importing Specific Entities from a Module

// ConsumeDrinksImportModuleEntities.visage
package consumer;

import food.Drinks.getCoffee;
import food.Drinks.getTea;
import food.Drinks.numberOfDrinksOffered;

var coffee = getCoffee();
println("Good coffee.  It's {coffee.brand}.");
var tea = getTea();
println("Good tea.  It's {tea.kind} tea.");
println("Number of drinks offered = {numberOfDrinksOffered()}.");

The style of import directive used depends mostly on taste. You should pick a style that makes the development and maintenance of your Visage project easy and hassle free.

images Caution The Visage compiler actually allows import directives to appear anywhere below the package directive. Because the effect of import directives is for the entire file and not merely the source lines below the directive, there is no good reason to put the import directives anywhere but just below the package directive.

Access Modifiers

This section describes the access modifiers for class-level entities (i.e., instance variable and constant declarations and instance function definitions). The usage scenarios of class-level entities are more complex than script-level entities, so the access modifiers for them are naturally more complex.

You can apply the public and package modifiers to instance variables and instance functions, and they mean the same thing as when they are applied to script-level entities. A public instance variable can be read from or written to from anywhere in the application. A public instance function can be invoked from anywhere in the application. An instance variable with a package modifier can be read from and written to from any code in the same package. An instance function with a package modifier can be invoked from any code in the same package.

Protected Modifiers

You can apply the protected modifier to both instance variables and instance functions. A protected instance variable can be read from and written to by code in the same package or in subclasses or mixees. A protected instance function can be invoked by code in the same package or in subclasses or mixees. The protected level of access implies package level of access.

One subtlety of protected level of access is that a subclass or a mixee that is not in the same package as the superclass or parent mix-in may access a protected instance variable or an instance function in that superclass or parent mix-in only through an object of the subclass type.

Protected instance variables and instance functions are often used to delegate implementation detail decisions to subclasses.

The simple examples in Listings A-58 and A-59 show what is and is not permitted when a subclass accesses a protected instance variable or instance function in the superclass.

Listing A-58. A Class with a Protected Variable

// a/A.visage
package a;

public class A {
  protected var i: Integer;
  public function getI() {
    i
  }
}

Listing A-59. Accessing a Protected Instance Variable

// b/B.visage
package b;

import a.A;

public class B extends A {
  public function adjustI(adj: Integer) {
    i += adj;  // OK.
  }
  public function adjustI(a: A, adj: Integer) {
    // a.i += adj; // Not allowed
  }
}

We define class A in package a with a protected instance variable i. We then define a subclass B of a.A that resides in a different package b. In the first adjustI() instance function in B, we modify a simple variable reference i, which refers to the i in the target of the invocation. This access is allowed because the target of the invocation is the class B. In the second adjustI() instance function in B, we try to modify the instance variable i of an instance a of A that is passed in as a function parameter. Because a is not of the subclass type, access is not allowed.

public-init and public-read Modifiers

You can apply the public-init and public-read modifiers to instance variables. A public-init variable can be initialized in object literal expressions from anywhere in the application. A public-read variable can be read from anywhere in the application.

Listings A-60 and A-61 illustrate public-init and public-read instance variables.

Listing A-60. Instance Variables with public-init and public-read Access

// Sandwiches.visage
package food;

public class Club {
  public-init var kind: String;
  public-read var price: Number;
}

public function calculatePrice(club: Club) {
  if (club.kind == "Roast Beef") {
    club.price = 7.99;
  } else if (club.kind == "Chicken") {
    club.price = 6.99;
  }
}

Listing A-61. Uses of public-init and public-read Instance Variables

package consumer;

import food.Sandwiches.*;

var club = Club { kind: "Roast Beef" };
calculatePrice(club);
println("The price of the {club.kind} club sandwich is {club.price}.");

The public-init modifier for the kind instance variable in Club allows us to provide an initializer to the kind instance variable in the object literal expression for Club. The public-read modifier for the price instance variable in Club allows us to read the value of the price variable out of the club object.

When used in conjunction with the package or protected modifiers, the public-init and public-read modifiers open the scope of write access.

Triggers

Triggers are an integral part of the Visage variable and constant declaration expression syntax. The trigger is the optional last part of variable declaration expressions and constant declaration expressions. A trigger is introduced by the on replace keywords, followed by a (possibly empty) variable modification specification and a block called the trigger block. The variable or constant to which the trigger is attached is called the observed variable or observed constant.

The trigger block is executed once after every change to the variable or constant being declared. A variable is considered changed when a different value is assigned to it, or, if it is initialized to a bound expression, when its bound expression is reevaluated and the new value is different from the old value. A variable of sequence type is considered changed also when elements are inserted into or deleted from the sequence or when elements of the sequence change. Because it is impossible to assign to constants, only a constant initialized to a bind expression can change, and this occurs when its bind expression is reevaluated.

The variable modification specification provides information to the trigger block. Six forms of variable modification specification exist, and these provide zero, one, two, three, or four pieces of information to the trigger block. Two of the six forms apply to sequence-type and nonsequence-type variables and constants. The other four forms make sense only for sequence-type variables and constants.

Accessing the Old and New Values in Trigger Blocks

The first form is the empty form. In this form, the trigger block has access to the variables in the surrounding scope, including the variable or constant itself, as shown in Listing A-62.

Listing A-62. A Trigger with an Empty Variable Modification Specification

var i = 1024 on replace {
  println("Variable i changed to {i}.");
}
i = 2048;
i = 2048;

The trigger in this example will be executed twice: once when the variable i is initialized, which counts as a change from the default value of 0 to the initializer value of 1024, and once when the variable i is assigned the value 2048 for the first time. The second assignment is not considered a change because the new value is the same as the old value. Within the trigger block, the variable i has the newly changed value.

When we run the program in Listing A-62, the following output is printed to the console:


Variable i changed to 1024.

Variable i changed to 2048.

Visage allows you to further modify the observed variable in a trigger. This is useful when some invariants must be maintained for the observed variable, as shown in Listing A-63.

Listing A-63. A Trigger That Further Modifies the Observed Variable to Maintain Invariants

// Using a trigger to keep the value of i to be between 0 and 9999.
var i = 1024 on replace {
  println("Variable i changed to {i}.");
  if (i < 0) {
    println("Since {i} < 0, setting i to 0.");
    i = 0;
  } else if (i > 9999) {
    println("Since {i} > 9999, setting i to 9999.");
    i = 9999;
  }
};
i = -100;
i = 20000;

The trigger in this example will be executed five times: when i is initialized to 1024, when i is assigned the value –100, when i is modified to 0 inside the trigger because –100 is less than 0, when i is assigned the value 20000, and when i is modified to 9999 inside the trigger because 20000 is greater than 9999.

When we run the program in Listing A-63, the following output is printed to the console:


Variable i changed to 1024.

Variable i changed to -100.

Since -100 < 0, setting i to 0.

Variable i changed to 0.

Variable i changed to 20000.

Since 20000 > 9999, setting i to 9999.

Variable i changed to 9999.

images Caution It is your responsibility to make sure that triggers are not executed in an infinitely recursive fashion. Programs that contain such triggers will cause a java.lang.StackOverflowError. The simplest such erroneous program is var x = 0 on replace { x++; }. However, with more complicated models and data bindings, the error may not be so obvious.

The second form of variable modification specification in a trigger consists of just a variable name. A variable so named can be accessed from within the trigger block in a read-only fashion, and it would contain the value of the observed variable just prior to the change. Listing A-64 presents an example.

Listing A-64. Accessing the Old Value from a Trigger

var i = 1024 on replace oldValue {
  println("Variable i changed from {oldValue} to {i}.");
}

i = 2048;

When we run the program in Listing A-64, the following output is printed to the console:


Variable i changed from 0 to 1024.

Variable i changed from 1024 to 2048.

The meaning of the variable name in this form of variable modification specification is not apparent from the syntax alone, so we recommend you use a name that reminds you of its role of providing the old value of the observed variable to the trigger block, such as oldValue, as we have done in the example.

For nonsequence type variables, the old values and the new values of the variables are all we are interested in, and the first two forms of the variable modification specification suffice for all their triggers.

Accessing Sequence Modification Information in Trigger Blocks

For sequence type variables, the modification scenarios are more complicated, and the other four forms of the modification specification are designed to provide the relevant information to their trigger blocks.

The third form of variable modification specification consists of a variable name, a pair of brackets ([]) enclosing a pair of variable names separated by two dots (..), an equal sign (=), and another variable name. The four variable names so designated can be accessed from within the trigger block in a read-only fashion. The first variable, the one before the brackets, contains the old value of the observed variable. The two variables between the brackets contain the low index and the high index of the portion of the sequence that has changed. The last variable, the one after the equal sign, is a sequence that contains any newly inserted elements to the observed variable. See Listing A-65 for an example.

Listing A-65. A Trigger for a Sequence Variable with Access to the Complete Set of Modification Information

println("Initializing seq...");
var seq = [1, 3, 5, 7, 9] on replace oldValue[lo..hi] = newSlice {
  print("  Variable seq changed from ");
  print(oldValue);
  print(" to ");
  println(seq);
  println("  The change occurred at low indexe {lo} and high index {hi}.");
  print("  The new slice is ");
  println(newSlice);
}

println('Executing "insert [2, 4, 6] before seq[3]"...'),
insert [2, 4, 6] before seq[3];

println('Executing "delete seq [1..2]"...'),
delete seq [1..2];

println('Executing "seq[4..5] = [8, 10]"...'),
seq[4..5] = [8, 10];

This form of the variable modification specification is designed to resemble an assignment to a slice of a sequence to remind you of the meaning of the four variable names inside the trigger block. However, because the meaning of these variable names is still not apparent, we recommend that you use descriptive names such as the ones we used in the example: oldValue, lo, hi, and newSlice.

In Listing A-65, we attach a trigger with the third form of variable modification specification to a sequence of Integer variable seq. The trigger will be executed four times: when the variable is initialized, when a sequence is inserted, when a slice is deleted, and when a slice is replaced with another slice.

When we run the program in Listing A-65, the following output is printed to the console:


Initializing seq...

  Variable seq changed from [ ] to [ 1, 3, 5, 7, 9 ]

  The change occurred at low indexe 0 and high index -1.

  The new slice is [ 1, 3, 5, 7, 9 ]

Executing "insert [2, 4, 6] before seq[3]"...

  Variable seq changed from [ 1, 3, 5, 7, 9 ] to [ 1, 3, 5, 2, 4, 6, 7, 9 ]

  The change occurred at low indexe 3 and high index 2.

  The new slice is [ 2, 4, 6 ]

Executing "delete seq [1..2]"...

  Variable seq changed from [ 1, 3, 5, 2, 4, 6, 7, 9 ] to [ 1, 2, 4, 6, 7, 9 ]

  The change occurred at low indexe 1 and high index 2.

  The new slice is [ ]

Executing "seq[4..5] = [8, 10]"...

  Variable seq changed from [ 1, 2, 4, 6, 7, 9 ] to [ 1, 2, 4, 6, 8, 10 ]

  The change occurred at low indexe 4 and high index 5.

  The new slice is [ 8, 10 ]

This output should pretty much match your intuitive ideas of what the four variables should contain under each of the triggering conditions. The only thing that we would like to point out is that the values of the high index are one less than the values of the low index under insertions.

The remaining forms of the variable modification specification are shortened versions of the third form, which is also known as the full form.

The fourth form omits the old value portion from the third form, offering only the low and high indexes and the new slice, as is shown in the following example:

var seq = [1, 3, 5, 7, 9] on replace [lo..hi] = newSlice {
  // ...
}

The fifth form omits the low and high indexes portion from the third form, offering only the old value and the new slice, as is shown in the following example:

var seq = [1, 3, 5, 7, 9] on replace oldValue = newSlice {
  // ...
}

The sixth form omits the old value and the low and high indexes portion from the third form, offering only the new slice, as is shown in the following example:

var seq = [1, 3, 5, 7, 9] on replace = newSlice {
  // ...
}

By far, the most popular forms of variable modification specifications are the empty form and the full form.

Debugging with Triggers

Aside from their uses in production code, triggers are surprisingly useful tools for tracing the values of variables throughout the life of a program. Although the Visage debugger in the NetBeans plug-in is quite powerful, in certain situations, you need a little bit of println debugging to help you understand the behavior of your Visage program.

When used in conjunction with data binding, you don’t even have to attach triggers to the module variables or instance variables that you want to monitor. You can simply declare a variable that binds to the module variable or instance variable you want to monitor and attach a trigger to your variable. Listing A-66 presents a simple example of this.

Listing A-66. Using Triggers to Monitor States of Another Object

class Gcd {
  var a: Integer;
  var b: Integer;
  function gcd(): Integer {
    if (b == 0) {
      return a;
    } else {
      var tmp = a mod b;
      a = b;
      b = tmp;
      return gcd();
    }
  }
}
var o = Gcd { a: 165, b: 105 };

var x = bind o.b on replace {
  println("Iterating: o.a={o.a}, o.b = {x}.");
}

var result = o.gcd();
println("The gcd of 106 and 105 is {result}.");

The class Gcd uses the Euclidean algorithm to calculate the greatest common divisor of its two instance variables, a and b. We are able to hook into the internal states of instance o of class Gcd without making any changes to the class itself. All we have to do is to declare a variable, x, that binds to o.b so that whenever o.b changes x is updated accordingly. Then, we attach a trigger to x that prints out the value of o.a, which we know is updated before o.b is updated, and prints out the value of x, which we know reflects the value of o.b.

When we run the program in Listing A-66, the following output is printed to the console:


Iterating: o.a=165, o.b = 105.

Iterating: o.a=105, o.b = 60.

Iterating: o.a=60, o.b = 45.

Iterating: o.a=45, o.b = 15.

Iterating: o.a=15, o.b = 0.

The gcd of 106 and 105 is 15.

String Formatting and Internationalization

This section examines two more features of strings: the fine-grained formatting of numerical, date/time, and other types of values using format specifiers and the internationalization and localization of Visage applications.

Using String Format Specifications

Visage supports another, more versatile, form of string expression in which each brace-delimited segment may contain a string format specification and a Visage expression. Listing A-67 shows some examples of this form of string expression.

Listing A-67. Format Specifiers in String Expressions

var b = true;
var i = 1024;
var n = 3.14;
var str = "Hello, World";

println("Display b in a 15 character field   [{%15b b}].");
println("Display i in a 15 character field   [{%15d i}].");
println("Display n in a 15 character field   [{%15f n}].");
println("Display str in a 15 character field [{%15s str}].");

A string format specification starts with a percent character (%), followed by an optional set of flags, an optional field-width specifier, an optional precision specifier, and a mandatory conversion character. The conversion character determines how the accompanying Visage expression is to be formatted into a resulting string. The optional flags, field-width specifier, and precision specifier control some detailed aspects of the conversion.

In Listing A-67, we use four string format specifications, %15b, %15d, %15f, and %15s, with conversion characters b (Boolean), d (integer), f (floating), and s (string). They all have a field width of 15. We surrounded the braces with brackets to illustrate the field into which the expressions are rendered.

When we run the program in Listing A-67, the following output is printed to the console:


Display b in a 15 character field   [           true].

Display i in a 15 character field   [           1024].

Display n in a 15 character field   [       3.140000].

Display str in a 15 character field [   Hello, World].

Visage relies on the Java class java.util.Formatter to perform the conversions. This class draws its inspirations from the C programming language printf() format specifications. Table A-1 summarizes some of the most useful conversion characters and their flags. Note that the double-character conversion specifications, starting from tH in the table, convert java.util.Date values.

images

images

images

The string and number conversions are similar but not identical to the conversions found in C programming language. The date/time conversions can be somewhat overwhelming because of the many components making up a date/time value. However, the last six composite conversions should cover many use cases. Some of the conversions, for example, the character used as the decimal point and the month and weekday names, are locale-sensitive.

Internationalizing Visage Programs

Internationalization is the process of extracting user-visible strings out of a program written in one language or culture to be translated into other languages and cultures. Localization is the process of translating an internationalized program into one or several specific languages or cultures.

Visage provides an internationalization and localization mechanism that is easy to use. To internationalize a program, you go through the string literals and string expressions of the program and prepend a double hash mark (##) before the ones that you wish to be translated. The double hash mark may optionally be followed by a pair of brackets ([]) that encloses a key to the string literal or string expression.

Listing A-68 shows a simple Visage program that contains several string literals and string expressions that are marked for translation.

Listing A-68. A Visage Program with Strings Marked for Translations

var name = "Weiqi";
var i = 1024;
var j = 9765625.0;
println(##[greeting]"Hello, {name}.");
println(##[trivia]"Do you know that "
  "{i} * {j} = {%,.1f i * j}?");
println(##"1024 is the 10th power of 2, "
  "and 9765625 is the 10th power of 5.");

The program in Listing A-68 contains two string expressions and a string literal that are marked for internationalization. The first string expression is given the key greeting. The second string expression is given the key trivia. The string literal is not given a key. If a string literal is marked for internationalization without a key, Visage will take the entire string literal as the key. Notice also that adjacent string literals and string expressions merged into one string literal or string expression are treated as one for the purposes of internationalization. Therefore, you need to put a double hash mark in front of only the first component.

If a string expression is marked for internationalization without a key, Visage will use a transformed version of the string expression as the key. This transformation replaces each embedded, brace-enclosed expression with its string format specification augmented with its position in the string. An embedded expression that does not have a string format specification is considered to have an implicit %s specification. The position numbers, which start with 1, are inserted after the % character and separated from the rest of the string format specification with an inserted $ character.

These transformed versions of the strings are also candidates for translation. For the program in Listing A-68, the strings to translate are as follows:

Hello, %1$s.
Do you know that %1$s * %2$s = %3$,.1f?
1024 is the 10th power of 2, and 9765625 is the 10th power of 5.

To localize a program for a particular language or a language and culture combination, you create a set of translation files, one for each Visage source file that contains strings marked for translation. The translation files must have the .visageproperties file name extension. The names of these files are derived from the Visage source file names by appending a language suffix or a language and culture suffix.

Listing A-69 shows a translation file in Simplified Chinese for the program in Listing A-68.

Listing A-69. Translation File for GreetingTrivia.visage

images

When we run the program in Listing A-68 in a non-Chinese locale, the following output is printed to the console:


Hello, Weiqi

Do you know that 1024 * 9765625.0 = 10,000,000,000.0?

1024 is the 10th power of 2, and 9765625 is the 10th power of 5.

When we run this program in a Simplified Chinese locale or with command-line options that set the user.language property to zh (Chinese) and user.country to CN (China), as in the following command line (the .visageproperties file is saved in UTF-8, so we also need to set the file.encoding property to utf-8):

visage -Dfile.encoding=utf-8 -Duser.language=zh -Duser.country=CN GreetingTrivia

we get this output:


images

The translation files must be in the class path when the program is run. If the Visage source file belongs to a package, the translation files must also be in a directory structure that reflects the package.

There is far more to internationalization and localization than merely having strings translated, but Visage’s translation mechanism gets you started very quickly.

Leveraging Java from Visage

Visage is built on top of the Java platform, so it can leverage the vast number of available Java libraries. In the following sections, we show you how to take advantage of Java libraries in Visage.

Instantiating Java Classes

Concrete Java classes can be instantiated in Visage using new expressions. A new expression is introduced by the new keyword, followed by a Java class name and a pair of parentheses that encloses a comma-separated list of zero or more expressions. The Java class must have a constructor with the same number of parameters, and each expression in the expression list must be convertible to the corresponding type of the Java constructor. Here are some examples of instantiating Java objects:

var now = new java.util.Date();
var fw = new java.io.FileWriter("output.txt");

The Visage string that we pass to the FileWriter constructor will be converted to a Java string when the Java constructor is called.

images Note The Visage syntax allows you to use an object literal expression to create objects of Java reference types that have a default constructor (i.e., a constructor that takes no arguments). Thus you can also use var now = java.util.Date {}; to create a new java.lang.Date object. Similarly, the new expression without any arguments allows you to create objects of Visage class types with the same effect as an object literal expression without any initializers. As a best practice, we recommend using new expressions for creating Java objects and using object literal expressions for creating Visage objects.

Accessing Java Object Fields

Accessing fields of a Java object is achieved using the same member access expression (the dot notation) for accessing instance variables of Visage objects. Exposing public fields is not a common practice in Java API design, so this feature is not used as often as some of the other features.

Calling Java Methods

Once you create a Java object, you can call its methods using function invocation expressions. The parameters are converted from Visage types to Java types on the invocation, and the return values, if any, are converted back to Visage types.

Visage Boolean and numerical primitive types are converted to the corresponding Java primitive types. The Visage String type is converted to java.lang.String. The Visage Duration type is converted to visage.lang.Duration. Visage class types are not converted but are checked for the ability to be assigned to Java class types. Visage sequence types are converted to Java arrays.

In the following example, we create a java.util.HashMap instance and insert several key value pairs into it:

var map = new java.util.HashMap();
map.put("b", true);
map.put("i", 1024);
map.put("n", 3.14159);
map.put("str", "Hello, World");

While support for a built-in Map construct is on the roadmap for Visage, until it is added the java.util.HashMap is a very handy class to have at your disposal.

images Note Visage does not yet support Java generics. Therefore, when you use a Java generic class or method, you have to use it in the raw type form.

Accessing Static Fields and Methods

Static fields in a Java class are shared across all instances of the Java class. Static methods in a Java class do not receive an implicit this parameter when invoked. Visage does not support static variables and static functions in its classes. However, static fields and static methods in Java are very similar to Visage’s script variables and script functions in a module. You can use the same syntax for accessing script-level entities in a module to access static members of a Java class.

In the following example, we access some static members of the java.lang.Math class:

var pi = java.lang.Math.PI;
var x = java.lang.Math.sin(pi/6);

After the calculation, the variable x has value sin(pi/6), which is 0.5.

You can also use the following import directive to access all static members of a Java class:

import java.lang.Math.*;
var pi = PI;
var x = sin(pi/6);

This Visage import directive has the same power as Java’s static import directive.

Quoting Visage Keywords

Visage does not come with its own input/output facilities aside from the println() function that you have seen in our examples. Therefore, it is natural to use Java’s input/output facilities. For example, instead of printing something to the console, you might want to read some user input from the console. The java.lang.System class contains a static field called in that represents the standard input.

However, if you try to compile the following line of code

java.lang.System.in.read();

you will get a compilation error because in is a Visage keyword. Visage allows you to quote such keywords by surrounding them with <<and >>. Thus the preceding line of code could be rewritten as

java.lang.System.<<in>>.read();

Other Visage keywords that are often quoted include insert, delete, and reverse, which are legitimate method names in Java.

Accessing Nested Classes

In Java, a class may contain other classes as members. A static nested class is a class that is declared inside another class with a static modifier. Static nested classes can be accessed from Visage using the same strategy for accessing static fields and static methods. Here is an example of how to access a Java class with a static nested class from Visage:

// Outer.java
package food;

public class Outer {
  public static class Nested {
  }
}
// Main.visage
import food.Outer.*;
var o = new Nested();

An inner class is a class that is declared inside another class without the static modifier. Instances of inner classes must have access to an instance of the containing class. Visage does not provide a way to directly create an inner class. For the odd case where you need to directly create an instance of an inner class from a parent class reference, you can write the code in Java and call it from Visage.

Accessing Java Enums

Java enums can be accessed from Visage the same way as in Java. Here is an example:

// Suit.java public enum Suit {
  CLUBS, DIAMONDS, HEARTS, SPADES
}

// Main.visage
var a = Suit.CLUBS;
var c = Suit.DIAMONDS;
var b = Suit.HEARTS;
var d = Suit.SPADES;

You can also access methods that you define in Java enums.

Extending Java Classes and Interfaces

A Visage class can extend Java classes and interfaces. Visage does not have an implements keyword, as Java does. And the extends keyword is used for both Java classes and interfaces. As far as extending Java classes and interfaces is concerned, Visage counts Java classes (including abstract classes) as regular classes and Java interfaces as mix-in classes. Therefore, the rule for Visage inheritance, which you learned in the “Extending Classes” section earlier in this appendix, can be restated as follows: Visage classes can extend zero or one Java or Visage class, any number of Java interfaces, and any number of Visage mix-in classes.

In Listing A-70, we define a Java interface, Sum, with two methods: addInts, which takes two ints and returns an int; and addDoubles, which takes two doubles and returns a double. We implement addInts and addDoubles in the Visage class VisageSum, which extends Sum. The SumClient Java class holds an instance of Sum and contains methods that exercise Sum’s methods.

Listing A-70. Extending Java Classes

// Sum.java
public interface Sum {
  int addInts(int a, int b);
  double addDoubles(double x, double y);
}

// VisageSum.visage
public class VisageSum extends Sum {
  public override function addInts(a:Integer, b:Integer):Integer {
    a + b
  }
  public override function addDoubles(x:Double, y:Double):Double {
    x + y
  }
}

// SumClient.java
public class SumClient {
  private Sum sum;
  public SumClient(Sum sum) {
    this.sum = sum;
  }
  public int addUpInts(int[] ints) {
    int result = 0;
    for (int i = 0; i < ints.length; i++) {
      result = sum.addInts(result, ints[i]);
    }
    return result;
  }
  public double addUpDoubles(double[] doubles) {
    double result = 0.0;
    for (int i = 0; i < doubles.length; i++) {
      result = sum.addDoubles(result, doubles[i]);
    }
    return result;
  }
}

// ExercisingSum.visage
var visageSum = VisageSum {};
var sumClient = new SumClient(visageSum);
var sumOfInts = sumClient.addUpInts([1, 3, 5, 7, 9]);
var sumOfDoubles = sumClient.addUpDoubles([3.14159, 2.71828]);
println("sumOfInts={sumOfInts}.");
println("sumOfDoubles={sumOfDoubles}.");

When we run the program in Listing A-70, the following output is printed to the console:


sumOfInts=25.

sumOfDoubles=5.859870195388794.

Even though Java classes can be extended by Visage classes, some differences between Java classes and Visage classes remain. One important difference is between Java fields and Visage instance variables. Some of the most powerful things that you can do with Visage instance variables, such as using them on the right-hand side of bind expressions or attaching a trigger using an instance variable override, cannot be done to fields in Java objects because Java lacks the facilities to track the changes made to fields.

Dealing with Java Arrays

Visage supports a native array type that allows it to efficiently handle Java APIs that either require a Java array parameter or return a Java array.

A native array type is declared as the keywords nativearray of followed by a Java class type or a Visage class type or primitive type. To illustrate its usage, we use the java.lang.Package.getPackages() API. This method returns an array of java.lang.Package objects that represent all the Java packages that are already loaded into the JVM.

import java.lang.Package; import java.lang.Package.*;

var b = getPackages();
for (i in [0..b.length - 1]) {
  println(b[i]);
}

The getPackages() call is a static method call on the java.lang.Package class. It returns a Java array of type Package[]. To avoid the conversion to a Visage sequence, it will use nativearray of Package to represent this return type. Since the return value is used as an initializer for the variable b, the type of variable b is also nativearray of Package. Just like with Java arrays, you can access its length field, and you can access the array elements using bracket notation: b[i].

It is possible to declare native array types for multidimensional Java arrays. For example, the Java type int[][] corresponds to the Visage native array type nativearray of nativearray of Integer.

Iterating Through Java Collections

Objects of Java classes that implement the java.lang.Iterable interface can be iterated over using the Visage for expression. Because the Java collection interfaces java.util.List, java.util.Set, and java.util.Queue all implement java.lang.Iterable, objects of any concrete Java collection classes that implement these interfaces (e.g., java.util.ArrayList, java. util.HashSet, and java.util.ArrayDeque) can be iterated over using for expressions, as demonstrated in Listing A-71.

Listing A-71. Iterating Through Java Collections

import java.util.*;

var list = new java.util.ArrayList();
list.add("One");
list.add("Two");
list.add("Three");

for (elem in list) {
  println("index: {indexof elem}, element: {elem}.");
}

When we run the program in Listing A-71, the following output is printed to the console:


index: 0, element: One.

index: 1, element: Two.

index: 2, element: Three.

Visage Reflection

Visage includes a reflection API that allows you to perform certain metaprogramming tasks. Metaprogramming is the act of manipulating a programming facility using a language rather than using the programming facility itself.

The Visage provides its reflection API as a set of Java classes in the Visage runtime library. The fact that the Visage reflection API is written in Java rather than in Visage means that you can use it in either Visage code or Java code. We will show you its usage mostly in Visage.

Because the reflection API is written in Java, we will use Java’s terminology to describe its parts. Therefore, in the upcoming text we will talk about fields and methods instead of instance functions and instance variables.

Mirror-Based Reflection

Visage’s reflection facility follows a design principle called mirrors, which seek to separate conventional programming facilities from metaprogramming facilities and at the same time achieve symmetry between the conventional and metaprogramming facilities.

images Note The Java reflection facility does not follow the mirrors principle. This is mostly manifested through code like employee.getClass(), where employee is an object of an Employee class. Although methods like getEmployeeId() or getName() have a legitimate place in an Employee class, one can argue that getClass() does not.

The interfaces and abstract classes in the visage.reflect package presented in Table A-2 provide the fundamental abstractions of the reflection facilities of the Visage programming language.

images

Concrete subclasses of the VisageType abstract class include VisagePrimitiveType, VisageSequenceType, VisageFunctionType, VisageClassType, and VisageJavaArrayType. This reflects the four kinds of Visage types plus the native array types that we covered earlier.

Implementations of the VisageValue interface include VisagePrimitiveValue, VisageSequenceValue, VisageFunctionValue, and VisageObjectValue. VisagePrimitiveValue has additional subclasses: VisageBooleanValue, VisageIntegerValue, VisageLongValue, VisageFloatValue, and VisageDoubleValue.

Implementations of the VisageMember interface include VisageFunctionMember and VisageVarMember. They reflect instance functions and instance variables as well as script functions and script variables. Script functions and script variables are considered members of the script or module to which they belong.

Concrete subclasses of the VisageLocation abstract class include VisageVarMemberLocation.

Entering the Reflection World

By design, the Visage reflection API can support reflections in a local Java virtual machine as well as remote Java virtual machines. Visage ships with a concrete implementation for local Java virtual machine reflections called VisageLocal. Its getContext() method returns a VisageLocal.Context object, which is an implementation of VisageContext.

The VisageLocal.Context class has a family of overloaded mirrorOf() methods that bridges the conventional programming and metaprogramming world. To get a VisagePrimitiveValue or VisageObjectValue value from a primitive value or an object, you use a version of mirrorOf() that takes one parameter. To get a VisageSequenceValue or VisageFunctionValue value from a sequence or a function, you use a version of mirrorOf() that takes two parameters, the second parameter being the type of the sequence or the function.

The VisageValue objects are where further reflection tasks are performed. Its getType() method returns an appropriate VisageType subclass. Its asObject() method returns the original Visage value.

Listing A-72 shows an example of obtaining and examining the mirror reflections of the four kinds of Visage values.

Listing A-72. Examining the Mirrors of Conventional Visage Types

import java.lang.Math.*;
import visage.reflect.*;

public class Point {
  public var x: Number;
  public var y: Number;

  public function dist() {
    sqrt(x * x + y * y);
  }

  public override function toString() {
    "Point { x: {x}, y: {y} }"
  }
}

public function run() {
  var i = 1024;
  var o = Point { x: 3, y: 4 };
  var seq = [3.14159, 2.71828];
  var func = o.dist;

  var context: VisageLocal.Context = VisageLocal.getContext();

  println("Examining...");
  var mirrorOfI = context.mirrorOf(i);
  print("  original: "); println({i});
  print("  mirror: "); println(mirrorOfI);
  print("  type: "); println({mirrorOfI.getType()});
  print("  back from mirror: "); println({mirrorOfI.asObject()});

  println("Examining...");
  var mirrorOfO = context.mirrorOf(o);
  print("  original: "); println({o});
  print("  mirror: "); println(mirrorOfO);
  print("  type: "); println({mirrorOfO.getType()});
  print("  back from mirror: "); println({mirrorOfO.asObject()});

  println("Exemining...");
  var seqType = context.getNumberType().getSequenceType();
  var mirrorOfSeq = context.mirrorOf(seq, seqType);
  print("  original: "); println({seq});
  print("  mirror: "); println(mirrorOfSeq);
  print("  type: "); println({mirrorOfSeq.getType()});
  print("  back from mirror: "); println({mirrorOfSeq.asObject()});

  println("Exemining...");
  var classType = context.findClass("MirrorsOfValues.Point");
  var funcMember = classType.getFunction("dist");
  var funcType = funcMember.getType();
  var mirrorOfFunc = context.mirrorOf(func, funcType);
  print("  original: "); println({func});
  print("  mirror: "); println(mirrorOfFunc);
  print("  type: "); println({mirrorOfFunc.getType()});
  print("  back from mirror: "); println({mirrorOfFunc.asObject()});
}

Notice that, to obtain the mirrors of the sequence and function values, we have to construct their types beforehand and pass them into the mirrorOf() method as the second parameter. To obtain the type of a Number sequence, we call getNumberType() on context and then getSequenceType() on the result. The VisageLocal.Context class has methods that return the VisagePrimitiveType object of every primitive type. To obtain the function type, we call findClass() on context to get the VisageClassType, getFunction() on classType to get the VisageFunctionMember, and finally getType() on funcMember to get the VisageFunctionType.

When we run the program in Listing A-72, the following output is printed to the console:


Examining...

  original: 1024

  mirror: IntegerValue(1024)

  type: Integer

  back from mirror: 1024

Examining...

  original: Point { x: 3.0, y: 4.0 }

  mirror: visage.reflect.VisageLocal$ObjectValue@37a786c3

  type: class MirrorsOfValues.Point

  back from mirror: Point { x: 3.0, y: 4.0 }

Exemining...

  original: [ 3.14159, 2.71828 ]

  mirror: visage.reflect.VisageLocal$SequenceValue@b32e13d

  type: Float[]

  back from mirror: [ 3.14159, 2.71828 ]

Exemining...

  original: MirrorsOfValues$1Local$1.function<0>

  mirror: visage.reflect.VisageLocal$FunctionValue@182d9c06

  type: function():Double

  back from mirror: MirrorsOfValues$1Local$1.function<0>

Notice that the type of the Number sequence is reported as Float[], which is the backing class of the Visage Number type.

Programming Through Reflection

In this section, we show you a series of small code snippets that can be used to perform some common programming tasks using reflection. Reflection code is always considerably longer than nonreflection code. However, its length depends on the classes and functions being programmed.

The idiomatic way of creating a new instance of a Visage class is as follows:

// CreatingInstance.visage
import visage.reflect.*;

public class Point {
  public var x: Number;
  public var y: Number;
  public override function toString() {
    "Point { x: {x}, y: {y} }"
  }
}

public function run() {
  var context = VisageLocal.getContext();
  var classType = context.findClass("CreatingInstance.Point");
  var classValue = classType.allocate();
  classValue.initVar("x", context.mirrorOf(3.0));
  classValue.initVar("y", context.mirrorOf(4.0));
  classValue.initialize();
  var p = (classValue as VisageLocal.ObjectValue).asObject();
  println(p);
}

Here, classType is of type VisageLocal.ClassType, a subclass of VisageClassType, and objectValue is of type VisageLocal.ObjectValue, a subclass of VisageObjectValue. The allocate() call allocates the memory for an instance of the class. The initVar() call supplies the initial values of instance variables of the object. Another version of initVar() takes an VisageVarMember object as the first parameter instead of a string. The initialize() call performs the actual setting of the instance variables to their initial values and the running of the init and postinit blocks that the class may have.

Getting and Setting Values of Instance and Script Variables

The following code works with instance variables of Visage classes and script variables of Visage modules:

// GettingSettingVariables.visage
import visage.reflect.*;

public class Point {
  public var x: Number;
  public var y: Number;
}

public var a:String;

public function run() {
  var p = Point { x: 3, y: 4 };

  // Working with instance variable x
  var context = VisageLocal.getContext();
  var classType = context.findClass("GettingSettingVariables.Point");
  var xVar = classType.getVariable("x");
  xVar.setValue(context.mirrorOf(p), context.mirrorOf(7.0));
  println("p.x={p.x}.");

  // Working with script variable a
  var moduleType = context.findClass("GettingSettingVariables");
  var aVar = moduleType.getVariable("a");
  aVar.setValue(context.mirrorOf(p), context.mirrorOf("Hello, World"));
  println("a={a}.");
}

Here, we obtain the mirror of an instance variable in a class using the getVariable() call on classType, passing in the variable name. Two other methods exist for getting instance variables. One version of the overloaded getVariables() call takes a Boolean parameter and returns a java.util.List<VisageVarMember>. If the parameter is false, only instance variables declared in the class are included in the list. If the parameter is true, all instance variables, including those declared in the superclasses, are included. Another version of the getVariables() call takes an extra first parameter of type VisageMemberFilter, which allows you to get only those instance variables that pass the filter.

The type of xVar is VisageVarMember. It represents the instance variable x of class Point. The setValue() call on xVar takes two parameters: the first is a mirror of p and the second is a mirror of the new value of x. VisageVarMember has a corresponding getValue() call, which takes one parameter, the mirror of an instance of the class, and returns a mirror of the value of the instance variable.

images Note The Visage compiler compiles Visage modules into Java classes and Visage classes in a module into nested Java classes. Therefore, the reflection facility represents both classes and modules as VisageLocal.ClassType. The getVariable() and getVariables() calls on a module class type will return information related to script variables in the module. The setValue() and getValue() calls on instances of VisageVarMember that represent script variables will ignore the first parameter, and we usually pass in null.

Invoking Instance and Script Functions

The reflection code for invoking instance functions and script functions is very similar to the code for accessing instance variables and script variables, as shown here:

// InvokingFunctions.visage
import visage.reflect.*;

public class A {
  public function f(i:Integer):Integer {
    i * i
  }
}

public function g(str:String):String {
  "{str} {str}"
}

public function run() {
  var o = A {};

  var context = VisageLocal.getContext();

  // Working with instance function f() of A
  var classType = context.findClass("InvokingFunctions.A");
  var fFunc = classType.getFunction("f", context.getIntegerType());
  var fVal = fFunc.invoke(context.mirrorOf(o), context.mirrorOf(4));
  println("o.f(4)={(fVal as VisagePrimitiveValue).asObject()}.");

  // Working with script function g()
  var moduleClassType = context.findClass("InvokingFunctions");
  var gFunc = moduleClassType.getFunction("g", context.getStringType());
  var gVal = gFunc.invoke(null, context.mirrorOf("Hello"));
  println('g("Hello")={(gVal as VisageLocal.ObjectValue).asObject()}.'),
}

Here, we call the getFunction() method on classType to obtain a mirror of an instance function. This method can take a variable number of parameters. The first parameter is the name of the function. The rest of the parameters are mirrors of the types of the instance function. Two other methods are available for getting instance functions. One version of the overloaded getFunctions() call takes a Boolean parameter and returns a java.util.List<VisageVarFunction>. If the parameter is false, only instance functions defined in the class are included in the list. If the parameter is true, all instance functions, including those defined in the superclasses, are included. Another version of a getFunctions() call takes an extra first parameter of type VisageMemberFilter, which allows you to get only those instance functions that pass the filter.

The type of fFunc in the preceding code is VisageFunctionMember. It represents the instance function f of class A. The invoke() call on fFunc takes a variable number of parameters, in this case two: the first parameter is a mirror of o, an instance of A, and the second parameter is the mirror of the instance function parameter. It returns an instance fVal of type VisageValue that represents a mirror of the value of the function invocation expression.

The situation with the script function g is analogous to the situation for instance functions, except that we search for the script function in the module class type, and that we pass a null as the first parameter to the invoke() call.

Other Reflection Capabilities

The reflection API also provides the following capabilities:

  • The VisageLocal.ClassType class has getMember(String, VisageType), getMembers(boolean), and getMembers(VisageMemberFilter, boolean) methods to get at member functions and member variables through the same API.
  • The VisageLocal.ClassType class has a getSuperClasses(boolean) method to get a list of direct or indirect superclasses.
  • The VisageLocal.ObjectValue class has overloaded bindVar(String, VisageLocation) and bindVar(VisageVarMember, VisageLocation) methods to bind member variables to VisageLocations associated to other VisageVarMembers.
  • The VisageVarMember class has a getLocation(VisageObjectvalue) method that gets the location of member variables for use in bindVar() calls.

Resources

The best place for more information about the Visage language is the official Visage compiler home page at http://visage-lang.org.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.11.182