|
|
F3 provides four primitive types String, Boolean, Number, and Integer. These types correspond to Java types as follows:
| F3 | Java |
String | java.lang.String |
Boolean | java.lang.Boolean |
Number | java.lang.Number |
Integer | byte,short,int,long,BigInteger |
Examples:
var s = "Hello";
s.toUpperCase(); // yields "HELLO";
s.substring(1); // yields "ello";
var n = 1.5;
n.intValue(); // yields 1
(1.5).intValue(); // yields 1
s.substring(n); // yields "ello"
var b = true;
b instanceof Boolean; // yields true
Coercions are automatically performed on numeric types when passing
arguments or return values to/from Java methods. In addition, implicit
truncating coercions are performed when converting Numbers to Integers.
F3 programs can import Java classes, create new Java objects, call their methods, and implement Java interfaces. For example:
import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.lang.System;
var frame = new JFrame();
var button = new JButton("Press me");
frame.getContentPane().add(button);
button.addActionListener(new ActionListener() {
operation actionPerformed(event) {
System.out.println("You pressed me");
}
});
frame.pack();
frame.setVisible(true);
Running the above program displays the following on the screen:

Of course, this isn't the preferred way of creating GUI's in F3. The following F3 code achieves the same effect:
Frame {
content: Button {
text: "Press Me"
action: operation() {
System.out.println("You pressed me");
}
}
visible: true
}
In F3, the var
keyword introduces a new variable. You may specify the type of a
variable in its declaration, however in F3 that's optional. If you
don't specify the type, the F3 interpreter infers the variable's type
from its use. A variable declaration takes the form
var variableName : typeName [?,+,*] = initializer;You may use one of the
?, +, or * operators to denote the cardinality of the variable, as follows:
| Operator | Meaning |
? | Optional (i.e, may be null) |
+ | One or more |
* | Zero or more |
Example:
var nums:Number* = [1,2,3];
This example declares a new variable named "nums" whose value is
defined to consist of zero or more instances of type "Number" and whose
initial value is [1,2,3].
: typeName, [?,+,*], and = initializer portions of the declaration are optional, so the following is equivalent to the above:
var nums = [1,2,3];
F3 functions represent a pure functional subset of the F3 programming language. The body of a function may only contain a series of variable declarations and a return statement. F3 also provides procedures (called operations, see below) which may contain any number of statements including conditional statements, looping statements, try/catch, etc.
The order in which the functions are given is not in general significant.
Here is a very simple example of a functional program
function z(a,b) {
var x = a + b;
var y = a - b;
return sq(x) / sq (y);
}
function sq(n) {return n * n;}
function main() {
return z(5, 10);
}
There are no mandatory type declarations, although (see later) the language is statically typed.
The most commonly used data structure is the array, which in F3 is written with square brackets and commas, e.g.:
var week_days = ["Mon","Tue","Wed","Thur","Fri"];
var days = [week_days, ["Sat","Sun"]];
Arrays represent sequences of objects. In F3 arrays are not themselves objects, however, and do not nest. Expressions that produce nested arrays (as in the initialization of "days" above) are automaticallly flattened, i.e:
days == ["Mon","Tue","Wed","Thur","Fri","Sat","Sun"]; // returns true
The size of an array may be determined with the F3 sizeof operator:
var n = sizeof days; // n = 7There is a shorthand notation using "
.." for arrays whose elements form
an arithmetic series. Here for example are definitions of the factorial
function, and of a number "result" which is the sum of the odd numbers
between 1 and 100
function fac(n) {return product([1..n]);}
var result = sum([1,3..100]);
The elements of an array must all be of the same type.
Arrays may be indexed as in Java:
var wednesday = days[2];In F3 the
[] operator also expresses selection (similar to its use in XPath). In this case, the expression contained in the []
is a boolean expression. Such an expression returns a new array
containing only those elements that satisfy the predicate contained in
the [].
As in XPath, within the predicate contained inside the [] operator, the context object may be accessed with the dot operator, for example:
var nums = [1,2,3,4]; var numsGreaterThanTwo = nums[. > 2]; // yields [3, 4]
Alternatively a variable may be declared for the context object. For example, this is equivalent to the above statement:
numsGreaterThanTwo = nums[n|n > 2];
The F3 indexof operator returns the ordinal position of an element within an array (like the position() function in XPath).
The car and cdr of a list may be expressed using selection expressions like this:
function car(list) {return list[indexof . == 0];}
function cdr(list) {return list[indexof . > 0];}
Of course car can be expressed more succinctly as simply:
function car(list) {return list[0];}
Examples:
var list = [1..10]; car(list); // yields 1 cdr(list); // yields [2,3,4,5,6,7,8,9,10]
In F3, the empty array [] and null are synonymous, i.e:
[] == null // yields true
sizeof null // yields 0
Modifying Arrays
In addition to the assignment operator (=), F3 provides data modification operators (insert and delete) similar in syntax and semantics to those in the draft XQuery-Update specification as follows:
The insert statement can take any of the following forms:
insert Expression1 [as first | as last] into Expression2
insert Expression1 before Expression2
insert Expression1 after Expression2
The insert statement inserts the items returned by
evaluating Expression1 into the location indicated by remainder of the
statement as follows:
into
Expression2 must refer to an attribute or variable. If Expression2 refers to a single valued attribute then the effect of the insert is the same as if the assignment operator were used.
If as first is specified, the insertion location is before the first element of the list indicated by Expression2. If as last is specified, the insertion location is after the last element of the list indicated by Expression2.
If neither as first nor as last is specified explicitly, then as last is used as the default.
Examples:
var x = [1,2,3];
insert 12 into x; // yields [1,2,3,12]
insert 10 as first into x; // yields [10,1,2,3,12]
insert [99,100] as last into x; // yields [10,1,2,3,12,99,100]
before, after
Expression2 must be a selection expression over an attribute or variable. If before is specified, the insertion location is before the selected elements. If after is specified the insertion location is after the selected elements.
Examples:
var x = [1,2,3];
insert 10 after x[. == 10]; // yields [1,2,3,10]
insert 12 before x[1]; // yields [1,12,2,3,10]
insert 13 after x[. == 2]; // yields [1, 12, 2, 13, 3, 10];
Delete Statement
The Delete statement takes one of the following forms:
delete variable
delete Expression.attribute
delete variable[predicate]
delete Expression.attribute[predicate]
The first two forms remove all elements from a variable or attribute - which is equivalent to assigning [] or null to the variable or attribute. The latter two forms remove only those elements that match the predicate.
Examples:
var x = [1,2,3];
insert 10 into x; // yields [1,2,3,10]
insert 12 before x[1]; // yields [1,12,2,3,10]
delete x[. == 12]; // yields [1,2,3,10]
delete x[. >= 3]; // yields [1,2]
insert 5 after x[. == 1]; // yields [1,5,2];
insert 13 as first into x; // yields [13, 1, 5, 2];
delete x; // yields []
Querying Arrays
F3 supports list comprehensions as in functional languages like
Miranda and Haskell, but with a familiar syntax that should be easily
understood by Java programmers, namely the F3 select and foreach operators.
Here is an example:
class Album {
attribute title: String;
attribute artist: String;
attribute tracks: String*;
}
var albums =
[Album {
title: "A Hard Day's Night"
artist: "The Beatles"
tracks:
["A Hard Day's Night",
"I Should Have Known Better",
"If I Fell",
"I'm Happy Just To Dance With You",
"And I Love Her",
"Tell Me Why",
"Can't Buy Me Love",
"Any Time At All",
"I'll Cry Instead",
"Things We Said Today",
"When I Get Home",
"You Can't Do That"]
},
Album {
title: "Circle Of Love"
artist: "Steve Miller Band"
tracks:
["Heart Like A Wheel",
"Get On Home",
"Baby Wanna Dance",
"Circle Of Love",
"Macho City"]
}];
// Get the track numbers of the albums' title tracks
// using the select operator:
var titleTracks =
select indexof track + 1 from album in albums,
track in album.tracks
where track == album.title; // yields [1,4]
// the same expressed using the foreach operator:
titleTracks =
foreach (album in albums,
track in album.tracks
where track == album.title)
indexof track + 1; // also yields [1,4]
A list comprehension consists of one or more input lists, an optional filter and a generator expression. Each source list is associated with a variable. The result of the list comprehension is a new list which is the result of applying the generator to the subset of the cartesian product of the source lists' elements that satisfy the filter.
List comprehensions give a concise syntax for a rather general class of iterations over lists.
Another simple example of a list comprehension is:
select n*n from n in [1..100]
This is a list containing (in order) the squares of all the numbers from 1 to 100. Note that "n" is a local variable of the above expression.
The use of a filter is shown by the following definition of a function which takes a number and returns a list of all its factors,
function factors(n) {
return select i from i in [1..n/2] where n % i == 0;
}
Expressions
F3 supports the following operators:
| Operator | Meaning | Java Equivalent |
| Relational Operators | ||
== | equality | == |
<> | inequality | != |
< | less than | < |
> | greater than | > |
<= | less than or equal | <= |
>= | greater than or equal | >= |
| Boolean Operators | ||
and | logical and | && |
or | logical or | || |
not | logical negation | ! |
| Arithmetic Operators | ||
+ | addition | + |
- | subtraction; unary negation | - |
* | multiplication | * |
/ | division | / |
% | remainder | % |
+= | add and assign | += |
-= | subtract and assign | -= |
*= | multiply and assign | *= |
/= | divide and assign | /= |
%= | remainder and assign | %= |
| Other Operators | ||
sizeof | array length | n/a |
indexof | ordinal position | n/a |
if e1 then e2 else e3 | conditional expression | e1 ? e2 : e3 |
select | list comprehension | n/a |
foreach | list comprehension | n/a |
new | allocation | new |
op() | function/operation call | n/a |
x.op() | member function/operation call | x.op() |
instanceof | type check | instanceof |
this | self access | this |
. | attribute access, context access | ., n/a |
bind [lazy] | incremental [lazy] evaluation | n/a |
: | eager initialization | n/a |
[] | array selection | [] |
format as | String formatting | n/a |
<<>> | Identifier quotes | n/a |
{} | String expression | n/a |
(expr) | grouping | (expr) |
reverse | reverses a list | n/a |
[number1,next..number2] | numeric range | n/a |
Some Examples:
import java.lang.System;
import java.lang.Math;
var x = 2;
var y = 4;
var a = true;
var b = false;
System.out.println(x == y); // prints false
System.out.println(x <> y); // prints true
System.out.println(x < y); // prints true
System.out.println(x > y); // prints true
System.out.println(x >= y); // prints false
System.out.println(x <= y); // prints true
System.out.println(x + y); // prints 6
System.out.println(x - y); // prints -2
System.out.println(x * y); // prints 8
System.out.println(x / y); // prints 0.5
System.out.println(x % y); // prints 2
System.out.println(a and b); // prints false
System.out.println(a or b); // prints true
System.out.println(not a); // prints false
System.out.println(sizeof [x,y]); // prints 2
System.out.println([x,y][indexof . == 0]); // prints 2
System.out.println(if a then x else y); // prints 2
System.out.println(select q from q in [x, y] where q > 3); prints 4
System.out.println(foreach(q in [x, y] where q < 3) q); prints 2
System.out.println(Math.max(x, y)); // prints 4
System.out.println("abc".toUpperCase()); // prints ABC
System.out.println(x instanceof Number); // prints true
x = 10;
System.out.println(x); // prints 10
String Literals and String Expressions
In F3, a literal character string is specified with single quotes, e.g.
var s = 'Hello';or with double quotes:
var s = "Hello";
In the latter case, F3 expressions may be embedded using {}, e.g
var name = 'Joe';
var s = "Hello {name}"; // s = 'Hello Joe'
The embedded expression may itself contain quoted strings (which, in turn, may contain further embedded expressions), e.g
var answer = true;
var s = "The answer is {if answer then "Yes" else "No"}"; // s = 'The answer is Yes'
Unlike in Java, F3 double-quoted String literals can contain newlines:
var s = "This
contains
new lines";
Quoted Identifiers
In F3, any sequence of characters (including whitespace) contained in french quotes <<>>
is treated as an identifier. This allows you to use F3 keywords (or
other normally illegal identifiers) as class, variable, function, or
attribute names, e.g.
var <<while>> = 100;
It also makes it possible to call Java methods whose names are the same as F3 keywords, for example:
import javax.swing.JTextArea;
var textArea = new JTextArea();
textArea.<<insert>>("Hello", 0);
Range Expression
As mentioned above you can define an array of numeric values that form an arithmetic series using the following syntax:
[number1..number2]Such an expression defines an array whose elements consist of the integers from number1 to number2 (inclusive).
For example:
var nums = [0..3];
System.out.println(nums == [0,1,2,3]); // prints true
By default the interval between the values is 1 but it's also possible to specify a different interval by including the next number in the sequence after number1 separated by a comma. For example, the following expression defines an array consisting of the odd numbers between 1 and 10:
[1,3..10]
If number1 is greater than number2 a descending series is created:
var nums = [3..0];
System.out.println(nums == [3,2,1,0]); // prints true
String, Number, and Date Formatting
F3 has a built-in String formatting operator (format as), which has the following syntax:
expr format as directive
The format as operator supports java.text.DecimalFormat, java.text.SimpleDateFormat, and java.util.Formatter formatting directives. If the formatting directive starts with %, then java.util.Formatter is used, otherwise if expr is of type Number then java.text.DecimalFormat is used, otherwise if expr is of type java.util.Date then java.text.SimpleDateFormat is used. The directive operand is syntactically an identifier, not an expression. This allows the content of directive to be statically checked for correctness at compile time.
import java.util.Date;
100.896 format as <<%f>>; // yields '100.896000'
31.intValue() format as <<%02X>>; // yields '1F'
var d = new Date();
d format as <<yyyy-MM-dd'T'HH:mm:ss.SSSZ>>; // yields '2005-10-31T08:04:31.323-0800'
0.00123 format as <<00.###E0>>; // yields '12.3E-4'
In F3, procedures are declared with the operation keyword, for example:
import java.lang.StringIndexOutOfBoundsException;
operation substring(s:String, n:Number): String {
try {
return s.substring(n);
} catch (e:StringIndexOutOfBoundsException) {
throw "sorry, index out of bounds";
}
}
The above example defines a new procedure called "substring" with two arguments, the first named "s" of type "String", the second named "n" of type "Number", returning type "String". In addition to the assignment, delete, and insert statements mentioned above, the following statements are possible inside the body of an operation :
Expression Statement
A primary expression may be used as a statement, for example:
System.out.println("Hello World!");
If Statement
The F3 if statement is like Java's except that curly
braces are always required around the then and else clauses, unless the
the else clause is another if statement.
Example:
if (condition1) {
System.out.println("Condition 1");
} else if (condition2) {
System.out.println("Condition2");
} else {
System.out.println("not Condition 1 or Condition 2");
}
While Statement
The F3while statement is like Java's except that curly braces are always required around the body.
Example:
var i = 0;
while (i < 10) {
if (i > 5) {
break;
}
System.out.println("i = {i}");
i += 1;
}
Try Statement
The F3try statement is like Java's but with F3
variable declaration syntax. Note: in F3 any object can be thrown and
caught, not just those that extend java.lang.Throwable.
Example:
try {
throw "Hello";
} catch (s:String) {
System.out.println("caught a String: {s}");
} catch (any) {
System.out.println("caught something not a String: {any}");
} finally {
System.out.println("finally...");
}
For Statement
The header of the F3for statement uses the same syntax as the foreach
list comprehension operator. However, in this case the body of the
statement is executed for each element generated by the list
comprehension.
Examples:
for (i in [0..10]) {
System.out.println("i = {i}");
}
// print only the even numbers using a filter
for (i in [0..10] where i % 2 == 0) {
System.out.println("i = {i}");
}
// print only the odd numbers using a range expression
for (i in [1,3..10]) {
System.out.println("i = {i}");
}
// print the cartesian product
for (i in [0..10], j in [0..10]) {
System.out.println(i);
System.out.println(j);
}
Return Statement
The F3return statement is like Java's:
Example:
operation add(x, y) {
return x + y;
}
Throw Statement
The F3throw statement is like Java's. However, any object may be thrown, not just those that extend java.lang.Throwable.
Examples:
import java.lang.Exception;
operation foo() {
throw new Exception("this is a java exception");
}
operation bar() {
throw "just a string";
}
Break and Continue Statements
The F3break and continue statements are like Java's, however labels are not supported. As in Java, break and continue must appear inside the body of a while or for statement.
Examples:
operation foo() {
for (i in [0..10]) {
if (i > 5) {
break;
}
if (i % 2 == 0) {
continue;
}
System.out.println(i);
}
}
operation bar() {
var i = 0;
while (i < 10) {
if (i > 5) {
break;
}
if (i % 2 == 0) {
continue;
}
System.out.println(i);
i += 1;
}
}
Do Statement
The F3 do statement allows you to execute a block of
F3 code in a background thread while allowing the AWT Event Dispatch
Thread to continue processing events, thereby preventing the UI from
appearing to hang. Currently, this is implemented by using java.awt.EventQueue
to pump events while the background thread is executing. Normally, all
F3 code executes in the AWT Event Dispatch Thread. Only code contained
in the body of a do statement is allowed to execute in
another thread. Such code must only access Java objects (and those
objects must handle their own thread synchronization, if necessary).
Example:
import java.net.URL;
import java.lang.StringBuffer;
import java.lang.System;
import java.io.InputStreamReader;
import java.io.BufferedReader;
// in the AWT EDT
var result = new StringBuffer();
do {
// now in a background thread
var url = new URL("http://www.foo.com/abc.xml");
var is = url.openStream();
var reader = new BufferedReader(new InputStreamReader(is));
var line;
while (true) {
line = reader.readLine();
if (line == null) {
break;
}
result.append(line);
result.append("\n");
}
}
// now back in the EDT
System.out.println("result = {result}");
In the above example, the green code which is executing in the EDT
appears to be blocked during the execution of the body of the do
statement (the red code). However, a new event dispatch loop is created
on the stack while waiting for the background thread to complete. As a
result, GUI events continue to be processed while the do statement is executing. Unfortunately,
this isn't a perfect solution, however, since it can cause multiple
event dispatch loops to build up on the stack - in the degenerate case
causing a stack overflow exception.
The do statement has a second form (do later)
that allows for asynchronous execution of its body in the EDT rather
than synchronous execution in a background thread (providing the
functionality of java.awt.EventQueue.invokeLater) Here's an example:
import java.lang.System;
var saying1 = "Hello World!";
var saying2 = "Goodbye Cruel World!";
do later {
System.out.println(saying1);
}
System.out.println(saying2);
Running this code produces the following output:
Goodbye Cruel World!
Hello World!
Classes and Objects
The F3 syntax for specifying a class is the class keyword followed by the class name, optionally the extends
keyword and a comma separated list of the names of base classes, an
open curly brace, a list of attributes, functions, and operations that
each end in a semicolon, and a closing curly brace. Here is an example:
class Person {
attribute name: String;
attribute parent: Person inverse Person.children;
attribute children: Person* inverse Person.parent;
function getFamilyIncome(): Number;
function getNumberOfChildren(): Number;
operation marry(spouse: Person);
}
Attributes are declared using the attribute keyword followed by the attribute's name, a colon, the attribute's type, optionally a cardinality specification (? meaning optional, * meaning zero or more, or + meaning one or more), and an optional inverse
clause specifying a bidirectional relationship to another attribute in
the class of the attributes' type, and terminated with a semicolon.
attribute AttributeName : AttributeType Cardinality inverse ClassName.InverseAttributeName;
If the inverse clause is present the F3 interpreter
will automatically perform updates (insert or delete or replace
depending on the kind of update and cardinalities of the attributes) on
the inverse attribute whenever the attribute's value is modified.
Multi-valued attributes (i.e. those declared with the * or + cardinality specifiers) are represented as arrays, and can be accessed via the [] operator and updated with the insert and delete operators.
Unlike Java methods, the bodies all F3 member operations and member functions are defined outside of the class declaration, for example, like this:
function Person.getNumberOfChildren() {
return sizeof this.children;
}
Parameter and return types are required in the declaration of operations and functions in the class declaration but may be omitted in their definitions.
Attribute Declarations
In F3, it's possible to declare initial values for attributes. The initializers are evaluated in the order the attributes are specified in the class declaration in the context of the newly created object:
import java.lang.System;
class X {
attribute a: Number;
attribute b: Number;
}
attribute X.a = 10;
attribute X.b = -1;
var x = new X();
System.out.println(x.a); // prints 10
System.out.println(x.b); // prints -1
It also possible to declare an incrementally evaluated expression as the value of an attribute using the bind operator:
import java.lang.System;
class X {
attribute a: Number;
attribute b: Number;
attribute c: Number;
}
attribute X.a = 10;
attribute X.b = bind a + 10;
attribute X.c = bind lazy b + 10;
var x = new X();
System.out.println(x.a); // prints 10
System.out.println(x.b); // prints 20
System.out.println(x.c); // prints 30
x.a = 5;
System.out.println(x.a); // prints 5
System.out.println(x.b); // prints 15
System.out.println(x.c); // prints 25
Object Literals
F3 objects may be allocated using a declarative syntax consisting of the name of the class followed by a curly brace delimited list of attribute initializers. Each initializer consists of the attribute name followed by a colon, followed by an expression which defines its value (but see below regarding F3's support for incremental evaluation in this context). Here is an example:
var chris = Person {
name: "Chris"
children:
[Person {
name: "Dee"
},
Person {
name: "Candice"
}]
};
Java object allocation syntax is also supported. In the case of Java classes, you can pass arguments to the class's constructor as in Java:
import java.util.Date;
import java.lang.System;
var date1 = new Date(95, 4, 23); // call a java constructor
var date2 = Date { // create the same date as an object literal
month: 4
date: 23
year: 95
};
System.out.println(date1 == date2); // prints true
In F3, it's possible to declare local variables inside an object
literal. Such variables are only visible within the scope of the object
literal itself. In addition, a variable referring to the object being
initialized may be declared by using the var keyword as a pseudo-attribute, for example:
var chris = Person {
var: me
name: "Chris"
var child1 = Person {
name: "Dee"
parent: me
}
var child2 = Person { name: "Candice" }
children: [child1, child2]
};
F3 classes don't have constructors, and F3 attributes don't have "setters" like Java bean properties. Instead, F3 provides SQL-like triggers that allow you to handle data modification events.
Triggers are introduced with the trigger keyword.
A trigger consists of a header and a body. The header specifies the type of event the trigger applies to. The body of the trigger
is a procedure that executes whenever the specified event occurs.
Inside the body you can use any of the statements available inside the
body of an operation. Triggers also behave like member
functions/operations, in that the context object is accessible inside
the body via the this keyword.
Creation Triggers
You can perform an action in the context of a newly created object by specifying a creation "trigger", like this:
import java.lang.System;
class X {
attribute nums: Number*;
}
trigger on new X {
insert [3,4] into this.nums;
}
var x = new X();
System.out.println(x.nums == [3,4]); // prints true
This example defines a trigger that will be executed whenever a new instance of the X class is created. In this case it just assigns an initial value to the nums attribute.
You can perform an action whenever an element is inserted into a multi-valued attribute by specifying an insert trigger, like this:
import java.lang.System;
class X {
attribute nums: Number*;
}
trigger on insert num into X.nums {
System.out.println("just inserted {num} into X.nums at position {indexof num}");
}
var x = new X();
insert 12 into x.nums; // prints just inserted 12 into X.nums at position 0
insert 13 into x.nums; // prints just inserted 13 into X.nums at position 1
In the above example, "num" is the name of a variable that will contain
a reference to the element being inserted (you can name the variable
whatever you like). The context index of the variable (returned by the indexof operator) corresponds to the insertion point.
You can perform an action whenever an element is deleted from a multi-valued attribute by specifying a delete trigger, like this:
import java.lang.System;
class X {
attribute nums: Number*;
}
trigger on delete num from X.nums {
System.out.println("just deleted {num} from X.nums at position {indexof num}");
}
var x = X {
nums: [12, 13]
};
delete x.nums[1]; // prints just deleted 13 from X.nums at position 1
delete x.nums[0]; // prints just deleted 12 from X.nums at position 0
In the above example, "num" is the name of a variable that will contain
a reference to the element being deleted (you can name the variable
whatever you like). The context index of the variable (returned by the indexof operator) corresponds to the deletion point.
You can perform an action whenever the value of a single-valued attribute or an element of a multi-valued attribute is replaced, like this:
import java.lang.System;
class X {
attribute nums: Number*;
attribute num: Number?;
}
trigger on X.nums[oldValue] = newValue {
System.out.println("just replaced {oldValue} with {newValue} at position {indexof newValue} in X.nums");
}
trigger on X.num[oldValue] = newValue {
System.out.println("X.num: just replaced {oldValue} with {newValue}");
}
var x = X {
nums: [12, 13]
num: 100
};
x.nums[1] = 5; // prints just replaced 13 with 5 at position 1 in X.nums
x.num = 3; // prints X.num: just replaced 100 with 3
x.num = null; // prints X.num: just replaced 3 with null
In the above examples, "oldValue" and "newValue" are the names of
variables that contain references to the previous and current values of
the element being replaced (you can name the variables whatever you
like). The context index of the variable (returned by the indexof operator) corresponds to the ordinal position of the element being replaced.
In F3, attribute initializers can be specified to be lazily and/or incrementally evaluated with the
bind operator. Attributes initialized with bind
are akin to cells in a spreadsheet that contain formulas rather than
literal values. During the lifetime of the object containg the
attribute, whenever any of the objects referenced by the right hand
side of the initializer expression changes the left hand side (the
attribute's value) is automatically updated.
Here is an example:
import java.lang.System;
class X {
attribute a: Number;
attribute b: Number;
attribute c: Number;
}
trigger on X.b = newValue {
System.out.println("X.b is now {newValue}");
}
trigger on X.c = newValue {
System.out.println("X.c is now {newValue}");
}
var x1 = X {
a: 1
b: 2 // X.b is now 2 is printed
c: 3 // X.c is now 3 is printed
};
var x2 = X {
a: x1.a // eager, non-incremental
b: bind x1.b // eager, incremental (X.b is now 2 is printed)
c: bind lazy x1.c // lazy, incremental (nothing is printed yet)
};
System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 2
System.out.println(x2.c); // prints X.c is now 3, then prints 3
x1.a = 5;
x1.b = 5; // prints X.b is now 5, twice
x1.c = 5; // prints X.c is now 5, twice
System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 5
System.out.println(x2.c); // prints 5
In the above example, the b and c attributes of x2 are bound to the b and c attributes of x1. This means that whenever x1's b or c attribute is updated the b and c attributes of x2 will be correspondingly updated. The difference between x2.b and x2.c is that the former's value is immediately updated in its attribute initializer whereas the latter's binding is not evaluated until its value is accessed the first time.
Note: the body of a function is always incrementally evaluated without requiring the bind operator,
however the body of an operation is not. Unlike a function, inside an operation changes to local variables
do not trigger incremental evaluation. Incremental evaluation
is not performed inside the body of an operation except for expressions explicitly
prefixed by bind.
Nevertheless when you call an operation (or a Java
method) from an incremental evaluation context the call itself is
incrementally evaluated.
This means that if the values of any of the arguments to the call
change a new fresh call to that operation or method will be made and a
new value returned.
By contrast, function's are only called once and the result of the evaluation is incorporated into the callers evaluation tree.
Incremental evaluation is one of F3's main distinguishing features that makes it possible to define complex dynamic GUI's declaratively. The lazy evaluation feature is needed to handle recursive data structures like trees and graphs.
ReflectionF3 classes, attributes, and operations are reflected as follows:
public class Class {
public attribute Name: String;
public attribute Documentation:String?;
public attribute Superclasses: Class* inverse Class.Subclasses;
public attribute Subclasses: Class* inverse Class.Superclasses;
public attribute Attributes: Attribute* inverse Attribute.Scope;
public attribute Operations: Operation* inverse Operation.Target;
public function instantiate();
}
public class Operation extends Class {
public attribute Target: Class? inverse Class.Operations;
}
public class Attribute {
public attribute Name: String;
public attribute Documentation: String?;
public attribute Scope: Class? inverse Class.Attributes;
public attribute Type: Class?;
public attribute Inverse: Attribute* inverse Attribute.Inverse;
public attribute OneToOne: Boolean;
public attribute ManyToOne: Boolean;
public attribute OneToMany: Boolean;
public attribute ManyToMany: Boolean;
public attribute Optional: Boolean;
}
F3 supports reflective access to classes, attributes, and member functions and operations, through the class operator, for example:
import java.lang.System;
System.out.println(1.class.Name) // prints "Number"
System.out.println("Hello".class.Name); // prints "String"
class X {
attribute a: Number;
}
var x = new X();
System.out.println(x.class.Name); // prints "X"
System.out.println(sizeof x.class.Attributes); // prints 1
System.out.println(x.class.Attributes[0].Name); // prints "a"
Reflective access to the value of an attribute is provided by the [] operator when its operand is of type Attribute, for example
import java.lang.System;
class X {
attribute a: Number;
}
var x = new X();
x.a = 2;
System.out.println(x[x.class.Attributes[Name == 'a']]); // prints 2
// the above statement is equivalent to this non-reflective code:
System.out.println(x.a);
In F3, the member functions and operations of a class are themselves modeled as classes in which the target class, formal parameters, and return value are represented as attributes. The name of the attribute representing the target object is 'this'. The name of the attribute representing the return value is 'return'. The attributes representing the formal parameters have the same names as the formal parameters.
You obtain such reflected operations from the Class object.
Reflected F3 operations can be called like functions by passing the target object as the first argument and any parameters as subsequent arguments, e.g:
import java.lang.System;
class X {
operation foo(n: Number): Number;
}
var x = new X();
var op = x.class.Operations[Name == 'foo'];
System.out.println(op(x, 100));
// the above code is equivalent to the following non-reflective code:
System.out.println(x.foo(100));
Currently, the bean properties and public fields of Java classes are reflected as F3 attributes. However, Java methods are not reflected as F3 operations. If you want to call a Java method reflectively you can simply use the normal Java API's.
Note that, unlike Java, in F3 the class operator is
applied to an expression rather than to a type name. F3 supports the
following syntax to obtain the reflected class object from a type name:
:TypeName
For example:
import java.lang.System;
System.out.println(:System.Name); // prints "java.lang.System"
System.out.println(:System.class.Name); // prints "Class"
In F3, the extent of a class, i.e. the set of all instances of that class, can be obtained with the following syntax:
*:ClassName
For example, the following code prints out all instances of class String.
import java.lang.System;
for (i in *:String) {
System.out.println(i);
}
Note: this is an optional feature and is disabled by default.
F3 also provides the capability to declare named instances of a class with the following syntax:
objectName:ClassName
For example:
import java.lang.System;
myString:String = "This is a string";
System.out.println(myString:String);
Such named instances are globally accessible but must normally be qualified with the class name. However, In the context of attribute initializers and assignments the named instances of the expression's type are introduced into the lexical scope (with weaker visibility than variables and attributes) and may be referenced with their unqualified names, for example:
Button {
mnemonic: P
text: "Press Me"
}
In the above example, since the mnemonic attribute of Button is of type KeyStroke, I can access the named value P with its unqualified name (elsewhere I would have to refer to it as P:KeyStroke).
The values of Java 1.5 enumerated types may be accessed with the same syntax, for example:
import java.lang.management.MemoryType;
var heap = HEAP:MemoryType;
var nonHeap = NON_HEAP:MemoryType;