Form follows function

The F3 Programming Language

Basic Types

F3 provides four primitive types String, Boolean, Number, and Integer. These types correspond to Java types as follows:

F3Java
Stringjava.lang.String
Booleanjava.lang.Boolean
Numberjava.lang.Number
Integerbyte,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.

Calling Java

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
         }

Variables

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:

OperatorMeaning
?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].

The : typeName, [?,+,*], and = initializer portions of the declaration are optional, so the following is equivalent to the above:

        var nums = [1,2,3];

Functions, Arrays, Expressions, and Operations

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 = 7

There 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:

Insert Statement

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:

OperatorMeaningJava Equivalent
Relational Operators
==equality==
<>inequality!=
<less than<
>greater than>
<=less than or equal<=
>=greater than or equal>=
Boolean Operators
andlogical and&&
orlogical or||
notlogical 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
sizeofarray lengthn/a
indexofordinal positionn/a
if e1 then e2 else e3conditional expressione1 ? e2 : e3
selectlist comprehensionn/a
foreachlist comprehensionn/a
newallocationnew
op()function/operation calln/a
x.op()member function/operation callx.op()
instanceoftype checkinstanceof
thisself accessthis
.attribute access, context access., n/a
bind [lazy]incremental [lazy] evaluationn/a
:eager initializationn/a
[]array selection[]
format asString formattingn/a
<<>>Identifier quotesn/a
{}String expressionn/a
(expr)grouping(expr)
reversereverses a listn/a
[number1,next..number2]numeric rangen/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.

Examples:

        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'

Operations

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 F3 while 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 F3 try 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 F3 for 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 F3 return statement is like Java's:

Example:

       operation add(x, y) {
           return x + y;
       }

Throw Statement

The F3 throw 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 F3 break 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.

do later

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]
       };
         

Update Triggers

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.

Insert Triggers

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.

Delete Triggers

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.

Replace Triggers

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.

Incremental and Lazy Evaluation

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.

Reflection

F3 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"

Extents and Enumerations

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;