Dana promotes the separation of computer programs into behaviour and data, so that components provide behaviour which operates over input data. In this section we introduce Dana's approach to universal data manipulation, which offers simple and powerful re-use of code.
The fields of a data type can be selected as a TypeField by using the notation:
DataType.[fieldName]
Or for functions with:
DataType.[functionName()]
As an example, we can get a TypeField as follows:
data Person {
char name[]
int age
}
// ...
Person p = new Person("Sam", 19)
TypeField tf = Person.[name]
We can then assign values to this field, or access its value, using the notation p:.tf
, such as the assignment:
p:.tf = "hello"
You will see Dana interfaces with functions that expect a TypeField
to be passed into them, which indicates that the operation of the function is done with respect to a particular field or fields that you pass into the function.
An example of this is data.query.Sort
which sorts an array according to a particular field. The interface is defined as:
interface Sort {
Data[] sort(Data list[], TypeField field, bool ascending)
}
Which can then be used as:
Person people[] = new Person[](new Person("Sam", 19), new Person("Alex", 42))
people = sort.sort(people, Person.[name], true)
Note that TypeField
is a type synonym for int
, so you can apply any arithmetic operators to a TypeField or otherwise treat them as integers for all purposes. You can also access fields using a literal integer, for example p:.0
.
Reflection allows you to query a type's structure, and to create new instances of a data type that have a given structure. To query the structure of an existing data type, we use:
Type t = typeof(x)
Where x
is any variable, or any literal type name such as char[]
.
The makeup of Type
itself is defined in lang.Type
; you can also create your own custom type definitions by instantiating Type
and populating its fields as you like, instead of using typeof
.
Once you have a Type
instance, you can then create new data or array instances using the from notation, such as:
Data d = new Data() from t
This will create a new data instance with all of the fields given in the type t
.
For arrays, we can combine type assignment with value construction, using the syntax:
Data d[] = new Data[](a, b) from t
Combined with field selection, reflection allows you to create new data types and populate all of their fields in a completely dynamic way.
A data type is dynamically constructed by using a Type
instance, setting its class field to Type.DATA
, and populating it fields with name/type pairs describing the fields of the data type.
The following type:
data Example {
int alpha
dec beta
}
Can be dynamically constructed as follows:
Type newType = new Type()
newType.class = Type.DATA
newType.fields = new Field[2]
newType.fields[0] = new Field(new Type(Type.INTEGER, 0, 8), "alpha")
newType.fields[1] = new Field(new Type(Type.DECIMAL, 0, 16), "beta")
Data d = new Data() from newType
Primitive field types (either int or dec fields) must have a size specified as part of their type; in the above example we use 64-bit sizes for both types.
The size of the data type itself is automatically calculated by Dana when constructing an instance of the type.Primitive Types
As noted above, Dana only has two primitive type classes: integers and decimal types. These two type classes can have a range of valid sizes specified in bytes, including any base 2 sizes from 1 byte to 512. For decimal types, these sizes are doubled automatically.
All other primitive types are synonyms for these type classes, including byte, char and bool. All three of these types are actually of type integer with a size of 1. To aid in disambiguation, Dana uses attributes on char/bool, for which the flags field of Type
is set to Type.F_CHAR
or Type.F_BOOL
.
An array type is specified by constructing a Type
with its class field set to Type.ARRAY
. For dynamically-constructed arrays, the size field of the type instance is set to zero; for statically-sized arrays (which can only be of primitive type), the size field is set to the number of cells in the array.
In either case, the Type
instance describing the array has a single field in its fields array; this field has no name, and a type set to the type stored by the array.
The following type:
char myArray[]
Is described as follows:
Type newType = new Type()
newType.class = Type.ARRAY
newType.fields = new Field(new Type(Type.INTEGER, Type.F_CHAR, 1))
An object type is specified by constructing a Type
with its class field set to Type.OBJECT
. Object types have three fields, each of type Type.DATA
, describing their attributes, named:
.functions
.events
.state
The .functions field has a type which contains a list of fields describing each function on the object type, including its parameters and return type.
The .events field has a type which contains a list of the event types on the object types (i.e., events that it can emit).
The .state field has a type which contains a list of fields describing the transfer state fields of the interface implemented by the object type.
As with data types, Dana automatically configures the size field of the type and so the programmer does not need to set this field.