HomeForumSourceResearchGuide
<< back to guide home

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.

Field Selection

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

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.

Type Construction

Data Types

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.

Array Types

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

Object types

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.