Fabula has a powerful, primarily static, type system, which combines elegance with practicality. It supports the following groups of data types:
- Numbers, including integers
- Time values
- Time intervals
- Composite objects (a.k.a. structures)
- Variant objects (a.k.a. algebraic data types)
- XML nodes
- Dynamic data
The data type of a value determines where it can or cannot be used. For example, an applet’s view expression must return a string, a function’s argument must conform to the function’s definition, etc.
It’s important to understand that, although every particular value in Fabula ultimately belongs to a particular data type, the data types are not completely isolated, in the sense that a value that belongs to one type can also belong to another type. For example, every integer value also belongs to the number data type. Such relationship between two data types, when every value of one data type always belongs to the other data type, is called covariance. Fabula has strict and precise covariance rules.
The reverse relation – that is, when all the values of the second type belong to the first type – is called contravariance. If two types are neither covariant nor contravariant, they have no common values.
Unless pointed specifically, data types from different groups are neither covariant nor contravariant. Any data type is covariant to itself.
Any data type in Fabula has a specific XML notation, such as
Let’s consider the data types one by one.
<integer/> represents integer numbers. There are the usual binary operators defined on integers that return integers:
/ always returns a number (not integer). Integers can be compared using the following relations:
'ne'– not equal
'lt'– less than
'le'– less or equal
'gt'– greater than
'ge'– greater or equal
The result of a relation is of type null (
<com/>, see below), since we are only interested in the fact whether it succeeded or failed. Relations are normally used in
<is> statements (see our post Fabula’s Computation Logic).
<number/> represents real (floating point) numbers. All the arithmetic operators on numbers always return numbers. In mixed integer-number arithmetics, the integer operand is simply considered a number (the covariance rule is applied). When the divisor is zero, the result of division is either infinity or NaN (not-a-number); there are core functions that detect if a number is infinity or NaN. There are also other core functions that operate on numbers, such as the exponent or trigonometric functions. The same relations are applicable to numbers as to integers.
<string/> represents character strings. A string can be specified as a quoted literal (in single quotes) or as a
<text> expression, possibly with substitutions. A substitution is in the format
%], where formula is an elementary expression (i.e. an expression without XML tags, like
a + b ) that returns a string, integer or number. There are also core functions/constants that return strings. The same relations as above can be applied to strings too; strings are assumed to be ordered lexicographically.
<time/> stands for date-time values (a point in time), and
<interval/> stands for time intervals. There are certain rules as to applying the arithmetic operators to time and interval values. For example, time minus time gives an interval, interval plus interval gives an interval, but it’s not possible to add up time to time. An interval, but not a time value, can be multiplied or divided by a number, resulting in an interval. There are core functions/constants that return time or interval values; however, there is no function that returns ‘current time’ – this would be semantically misleading in a declarative language. Instead, at certain places of an applet the developer can access a special variable that holds the time of a specific event, such as the time when the applet instance was initialized. The same relations as above can be applied to time and interval values too.
Each of the above types is covariant to <dynamic/>.
</arrayof> means an array of items of given type. An array is a collection of items of the same type, accessible by their indexes (offsets), from zero to the array size minus one. The array size is the number of items in the array; in Fabula, the size of array A is denoted A#. The notation for an item at given index is array
]. Fabula has high-level expressions for creation of and manipulation with arrays.
There is a simple covariance rule for arrays: array types are covariant when and only when their item types are covariant; arrays of <dynamic/> are also covariant to <dynamic/>.
</dictionaryof> stands for a dictionary of items of given type. A dictionary is a collection where items are accessible by string keys. To get a dictionary item, one needs to apply the following operation: dictionary
@ key. High-level expressions for creation/manipulation of dictionaries are also available.
The covariance rule for dictionaries is the same as for arrays.
<function> argument-type result-type
</function> represents functions mapping argument-type to result-type. The only way in Fabula to define a function is using a closure – a special expression that returns a function.
Two function types are covariant when, and only when, their result types are covariant and argument types are contravariant.
Objects are complex values that consist of properties – name-value pairs. Object values are created using so-called object literals, listing the property names along with their values. There are two kinds of object types: composite and variant object types. Every value of a composite object type consists of a fixed number of properties; every value of a variant object type consists of only one of a fixed number of properties. An object type is fully determined by the names of the properties and their values’ types.
The syntax for a composite object type is:
<com> property . . .
where each property specification is
<prop name="name"> type </prop>
A property specification declares a property with given name and type.
A composite type with no properties (i.e.
<com/>) is called null.
The covariance rule for composite types is: two composite types are covariant when and only when for each property from the second composite type there is a property in the first composite type with the same name whose type is covariant to the other property’s. In addition, an object type is covariant to <dynamic/> when each of the properties is covariant to <dynamic/>.
The syntax for a variant object type is:
<var> property . . .
where each property specification is either
<prop name="name"> type </prop>
The second form specifies a property with given name of type null.
There must be at least one property in a variant type.
The covariance rule for variant types is: two variant types are covariant when and only when for each property from the first variant type there is a property in the second variant type with the same name whose type is contravariant to the other property’s.
<XMLnode/> is a special type for processing XML data. There are core functions for parsing XML text and for accessing and manipulating XML nodes (e.g. getting the children of a node, determining the node type or value, etc.).
<action/> represents an action to be performed by the system, such as sending data to a channel or starting an asynchronous AJAX request. Note that actions from the application’s point of view are just data and not “statements” that cause side effects. This is a major difference from the imperative languages.
In Fabula, it’s not needed to declare variables; a variable automatically gets the data type of the associated expression (type inference). Explicit specification of the data type is necessary only where it is essential:
- a function’s argument type
- a channel’s message type
- an applet’s model type
When Fabula Project Builder saves the code of a Fabula program to the database, it automatically checks the code for semantic errors. The described type system is the basis of the semantic rules that a program must satisfy.