Type

A Type is like a thing with pre-defined properties and/or methods. When an instance of a Type is created, all defined properties are guaranteed to exist with a value matching the Type definition.

Definable properties

definition default description
'str' "" requires type str (values of type str should contain valid UTF-8 characters).
'utf8' "" requires type str and the value must contain valid UTF-8 characters.
'raw' "" requires type str or bytes.
'bytes' bytes() requires type bytes.
'bool' false requires type bool.
'int' 0 requires type int.
'uint' 0 requires a non-negative integer (type int, >= 0).
'pint' 1 requires a positive integer (type int, > 0).
'nint' -1 requires a negative integer (type int, < 0).
'float' 0.0 requires type float.
'number' 0 requires type float or type int.
'thing' {} requires a thing.
'X' X{} requires a instance of Type X, or a member of enumerator X. The value X should be replaced with the Type / enum name.
'[]' [] requires a list.
'{}' set() requires a set.
'any' nil any type is valid.

Each definition can be made optional by adding a question-mark ? to the definition. If a property is made optional, then the value nil is allowed instead of the given type and nil will also be the default if the property is missing.

For example

// Create a new type `User` with an optional property `name`.
set_type('User', {name: 'str?'});

// Create a instance of type `User` without a name
unknown = User{};

// ..or explicitly set name to `nil`
user_nil = User{name: nil};

// ..a `str` is also ok
iris = User{name: 'Iris'};

// ..but another type than `str` or `nil` is not allowed
assert(iserr(try(User{name: 0})));

// Return the results
[unknown, user_nil, iris];

Return value in JSON format

[
    {
        "name": null
    },
    {
        "name": null
    },
    {
        "name": "Iris"
    }
]

When using a list '[]' or set '{}' definition, it is also possible to make the list or set restricted to a certain type. In this case only items of the given definition are allowed as members. For example '[int]' requires the members of a list to be integers and '{User}' is a restricted set which only allows things of type User.

Both the list and/or the members can be made optional. For example, this '[str?]?' is a valid declaration. Since a set does not allow for nil values, it is not possible to make members of a set optional.

This is an example using a restricted list:

// Very simple `Note` type
set_type('Note', {
    text: 'str',
    timestamp: 'uint'
});

// Book type with `notes` of type `Note`
set_type('Book', {
    title: 'str',
    notes: '[Note]'
});

// Create a new book
book = Book{
    title: "hitchhiker's guide to the galaxy",
};

// Add a note to the book
book.notes.push(Note{
    text: 'the answer is 42',
    timestamp: int(now())
});

// It *must* be a `Note`, something else is not allowed
assert(iserr(try(book.notes.push({test: 'not a Note'}))));

// Return the book, 2 levels deep to see the note
return(book, 2);

Return value in JSON format

{
    "notes": [
        {
            "text": "the answer is 42",
            "timestamp": 1573894579
        }
    ],
    "title": "hitchhiker's guide to the galaxy"
}

Get instance of a type

The thing(..) function may be used to get an instance of a given ID. This does not guarantee that the thing is truly of the type you expect. Instead of adding a type_assert(..) on the thing, it is also possible to call the type as a function with the ID as it’s first argument.

For example:

// Suppose we have a user_id and want to get the user
user = User(user_id);

Possible errors:

  • A lookup_err() will be raised if no thing with the given user_id exists.
  • A type_err() will be raised if a thing is found, but the thing is not of the User type,

Methods

A method is a closure attached to a type. Methods are defined when creating a type by attaching a closure instead of a definition. When a method is used on an instance of a type, the first argument will be the instance itself. It is therefore common to name the first argument this.

For example:

set_type('Person', {
    name: 'str',
    age: 'int',
    whoami: |this| `My name is {this.name} and I am {this.age} years old.`
});

.iris = Person{
    name: 'Iris',
    age: 7
};

.iris.whoami();

Return value in JSON format

"My name is Iris and I am 7 years old."
Function Description
del_type Delete a Type.
mod_type Modify an existing Type definition.
new_type Create a new Type.
has_type Determine if the current scope has a Type.
set_type Set property definitions on a Type and creates the Type if it did not exist.
type_info Return the Type definition.
types_info Return all Type definitions in the current scope.

Mutation format

A mutation format is only required to understand if you manually want to parse events when watching for mutations on things. While most values have a pretty straightforward format when packed in a mutation, a Type instance is a bit more complex to understand.

An example of a Type mutation:

{
    ".": 10,
    "#": 123,
    "": [
        "hitchhiker's guide to the galaxy"
        4.2
    ]
}
Key Description
"." The type_id of the Type.
"#" The #ID which is assigned to the Type instance.
"" Array with fields;

In order to correctly parse the mutation you require the types_info of a collection. Note that the types info is included inside an init event. Below is an example with an entry of types_info. This shows type Book which has ID 10 from the mutation example above.

...
{
    "created_at": 1581454041,
    "fields": [
        ["title", "str"]
        ["rating", "float"]
    ],
    "modified_at": 1581455876,
    "name": "Book",
    "type_id": 10
}
...