You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Next »

Everything within the C3 AI Suite is managed through or exists as a C3 Type. Broadly speaking, a Type is a Java class-like concept which combines 'fields' and 'methods' with a concept of inheritance. Once you understand how types are defined, you will be able to write you own Types to extend the C3 AI Suite with new functionality perfect for your use case.

All C3 Types must be defined within a .c3typ file stored within the 'src' directory of a C3 Package. (We'll get to C3 Packages a little later). All .c3typ files must contain the definition of a single C3 Type.

First, we'll introduce the C3 Package to download for this example. Then we will discuss the syntax, special keywords, fields, Methods, and Inheritance structure of C3 Types. Finally, we'll end with some examples.

Something to keep in mind, is that

lightbulbAD C3 Package

For this Guide, we use the lightbulbAD C3 package. If you want to follow along with source code, please download the source code for this package following the instructions available here: C3 lightbulbAD Package

Syntax

To start this discussion of syntax, Let's look at the definition of the 'SmartBulb' Type in the lightbulbAD C3 Package:

/*
 * Copyright 2009-2020 C3 (www.c3.ai). All Rights Reserved.
 * This material, including without limitation any software, is the confidential trade secret and proprietary
 * information of C3 and its licensors. Reproduction, use and/or distribution of this material in any form is
 * strictly prohibited except as set forth in a written license agreement with C3 and/or its authorized distributors.
 * This material may be covered by one or more patents or pending patent applications.
 */

/**
 * A single light bulb capable of measuring various properties, including power consumption, light output, etc.
 */
entity type SmartBulb extends LightBulb mixes MetricEvaluatable, NLEvaluatable type key "SMRT_BLB" {

  /**
   * This bulb's historical measurements.
   */
  bulbMeasurements: [SmartBulbMeasurementSeries](smartBulb)

  /**
   * This bulb's historical predictions.
   */
  @db(order='descending(timestamp)')
  bulbPredictions: [SmartBulbPrediction](smartBulb)

  /**
   * This bulb's latest prediction.
   */
  currentPrediction: SmartBulbPrediction stored calc "bulbPredictions[0]"

  /**
   * This bulb's historical events.
   */
  bulbEvents: [SmartBulbEvent](smartBulb)

  /**
   * The latitude of this bulb.
   */
  latitude: double

  /**
   * The longitude of this bulb.
   */
  longitude: double

  /**
   * The unit of measurement used for this bulb's light output measurements.
   */
  lumensUOM: Unit

  /**
   * The unit of measurement used for this bulb's power consumption measurements.
   */
  powerUOM: Unit

  /**
   * The unit of measurement used for this bulb's temperature measurements.
   */
  temperatureUOM: Unit

  /**
   * The unit of measurement used for this bulb's voltage measurements.
   */
  voltageUOM: Unit

  /**
   * A SmartBulb is associated to a {@link Fixture} through a SmartBulbToFixtureRelation.
   */
  @db(order='descending(start), descending(end)')
  fixtureHistory: [SmartBulbToFixtureRelation](from)

  /**
   * The current Fixture to which this bulb is attached.
   */
  currentFixture: Fixture stored calc 'fixtureHistory[0].(end == null).to'

  /**
   * Method to determine the expected lumens of a light bulb
   */
  expectedLumens: function(wattage: !decimal, bulbType: !string): double js server

  /**
   * Returns the life span of this smartBulb
   */
  lifeSpanInYears: function(bulbId: string): double js server

  /**
   * Returns the average life span of all smartBulbs.
   */
  averageLifeSpan: function(): double js server

  /**
   * Returns the id of the smart bulb with the shortest recorded life span to date.
   */
  shortestLifeSpanBulb: function(): string js server

  /**
   * Returns the id of the smart bulb with the longest recorded life span to date.
   */
  longestLifeSpanBulb: function(): string js server

}

We can see the general structure of the .c3typ file at a glance

  • Keywords introduce the name, inheritance, and specify properties of the type.
  • fields are named, and their Types defined
  • methods are named, their types are similar to function signatures.
  • Additional annotations like '@db' can sometimes precede fields or methods.
  • Types can inherit other types.

From a high level, the signature of a C3 Type definition looks like this: (originally from official C3.ai type documentation here: https://developer.c3.ai/docs/7.12.17/topic/mda-types

[remix, extendable] [entity] type TypeName [extends, mixes AnotherType] {
	/* comments */
	[field declaration]
	[method declaration]
}

Everything within square brackets '[]' is optional.

C3.ai resources on Types generally:

Keywords

A Type definition is introduced using a series of keywords, and names. These tell the C3 AI Suite how to construct this type, how to store it internally, and whether it inherits fields and methods from other already defined Types. We describe each:

  • type: All C3 Types use the keyword 'type'. This indicates to the C3 AI Suite that this is a Type definition.
  • entity: The 'entity' keyword indicates that the type mixes in the 'Persistable' type and is stored internally to C3 in a table somewhere. We can mix in Persistable instead but this keyword makes the Type definition shorter and easier to read. Since a large majority of the Types on the C3 AI Suite are persistable, this is a good keyword to have.
  • mixes: A type which specifies `mixes AnotherType` inherits the fields and methods of the other type. Multiple Types can be mixed in, and mixes do not change the way the type is stored internally.
  • remixes: A type which specifies `remix` defines a modification of an existing type.
  • extends: A type which specifies `extends AnotherType` is a special type of of mixin. This mixin subclasses a persistable type in a special way, The original Types along with the new Types are stored together in the same table. required as well, is the `type key` modifier which describes the key to use when storing the new type internally.
  • extendable: A keyword indicating that a Type can be extended. The C3 AI Suite will create the internal table for this type with an additional field called the 'key' which will be used to distinguish the different varieties of this base type.
  • type key: A set of keywords indicating the Key value to use when storing a type which extends another type.
  • schema name: A set of keywords indicating the name of the table to use to store the Type.

Primitive Types

As a basis for many fields, the C3 AI Suite defines many 'primitive' types. These are basic types like 'int' and 'double. Primitive Types are given to the developer by the platform and new ones require backend C3 support to define. Most DTI researchers will not be defining these, but using those that already exist.

We list them here for reference.

  • binary
  • boolean
  • byte
  • char
  • datetime
  • decimal
  • double
  • float
  • int
  • json
  • long int
  • string

C3.ai resources on Primitive Types:

Persistable Types

The most common type a C3 developer will define is a 'Persistable' type. A Persistable type is any type which inherits the 'Persistable' Type. This can be specified either by directly mixing in 'Persistable', or using the keyword 'entity'. All persistable types live within the C3 AI Suite stored in a table backed by some sort of database technology. Usually this is postgres, but depending on the data (Or annotations as will be discussed below), It can also be stored in a file blob or key-value store such as Cassandra.

The C3 AI Suite also needs to know where to store this table. Thus when the user is creating an entirely new persistable type (that is one not extending an existing persistable type), they must use the keywords 'schema name' followed by the table name to define the name of the table to use. (e.g., in the definition of LightBulb, we have 'schema name "LGHT_BLB"'.

C3.ai resources on Persistable Types

Generic Types

Types can also be made generic similar to Java generic Types and C++ generic classes. In fact, it uses the same syntax with angle brakets '<>' defining the generic parameters of the type. When defining a generic type, the Name of the type you're creating will be followed by angle brackets and a comma-separated list of placeholder names to refer to the generic type parameters you want to use. For example:

type TheType<V,U> {
	fieldA: V
	fieldB: [U]
}

Then, when you use your type in other places, you must specify which type you mean in each type parameter slot. So later you might define a new type which has a field like:

type NewType {
	newField: TheType<TypeA, TypeB>
}

C3.ai resources on Generic Types:

Fields

Each field of a C3 Type is defined by first giving the field name followed by a colon ':' followed by a Type.

The name must follow a set of conventions: – Name conventions here –

Depending on the Type of the field, the field is grouped into some broad categories.

Primitive Fields

Primitive Fields use types which are PrimitiveTypes. These are simple data types usually directly supported by database technologies such as SQL. These fields are stored directly within the main type's table.

Reference Fields

Reference Fields use another defined C3 Type. This reference is implemented in the main Type's table as a foreign key into the referenced Type's table, (The 'id' field!).

Collection Fields

Collection Fields are groups of Types. There are four broad categories of collections which can be used.

  • array: denoted by `[MyType]`
  • map: denoted by `map<int, MyType>` (the left argument is the key which can be any primitive type, while the right can be any Type.)
  • set: denoted by `set<MyType>`
  • stream: denoted by `stream<MyType>`. (This is a read-once type of collection).

When we define a field which is a collection of MyType, we can also specify which MyTypes get added by using Foreign Key Collection Notation. If for example, we're trying to define the new type TheType, and we want a collection to have type [MyType], but we don't want all MyTypes to be in this collection, we can use the notation `(thetype, id)` which specifies that C3 Should only use MyTypes whose 'thetype' property matches TheType's id property. Please see the section 'Foreign Key Collection Notation and Schema Names' in the following C3.ai documentation where this concept is explained better: https://developer.c3.ai/docs/7.12.17/topic/mda-fields

Calculated Fields

These fields are derived from other fields. Calculated fields are specified as follows

fieldName: FieldType [stored] calc "field_formula"

So, like the other types, we first specify a type for the field, then we must use the keyword 'calc' followed by an expression formula. (e.g, in the SmartBulb example above, this is 'fixtureHistory[0].(end == null).to').

You may also specify the keyword 'stored'. With the stored keyword, the value of the field is stored in the Type's Table, while without it is calculated at runtime.

C3.ai Developer Resources on Fields:

Methods

Each method of a C3 Type is defined by first giving the method name followed by a colon ':' followed by a function signature, (e.g., function() or function(wattage: !decimal, bulbType: !string)), followed by a colon ':', followed by a return Type, along with a language and environment specification.

Function Signatures

Instead of a Type name following the name as with fields, methods have a function signature. the syntax looks like:

function(parName1: [!] parType1, parName2: [!] parType2):

we see 'function' followed by a comma separated list of argument definitions. An argument definition consists of a parameter name followed by a colon ':', followed by a Type name. Optionally, you can also include an '!' before the parameter type to indicate that that argument is required.

Return Types

After the function signature we have a return type. This must be a C3 type. The function must return an object of the appropriate type in the .

Language/Environment Specification

Finally, we end with a language and environment specification.

Language Specifications

  • py: Use the 'py' keyword to indicate this is a Python function.
  • js: Use the 'js' keyword to indicate this is a JavaScript function.

Currently, these are the only two languages which the C3 AI Suite supports for native method definitions

Environment Specification

Then, this is followed by an environment specification. for JavaScript the only two environments are:

  • server: This JavaScript function is executed within the C3 AI Suite by a worker node.
  • client: This JavaScript function is executed at the client browser.

For Python, we need to specify a valid 'ActionRuntime' for the python function to be executed in. (ActionRuntimes are defined within the C3 AI Suite, and new ones can be defined as part of a C3 Package. They are essentially conda environments.) For example, 'server' will use the 'py-server' ActionRuntime.

Function Definitions

Finally, the function definition goes in a different file from the Type definition .c3typ file. It should have the same base name as the .c3typ definition file, but have an extension appropriate for the language of implementation (e.g., 'py' for Python, 'js' for JavaScript). Within, the file must contain functions whose name matches the method name from the Type definition exactly. For example, if we have a Type defined like so:

entity type NewType {
	field: double
	funcA: function(num: !int) : float js server
    funcB: function(num: !int, name: string) : double py server
}

This type will be in a file named 'NewType.c3typ',  There will also have to be two other files, 'NewType.js' and 'NewType.py'. Here's a peek inside:

'NewType.js'

function funcA(num) {
    ...
}

'NewType.py'

function funcB(num, name='default') {
    ...
}


C3.ai Developer Resources on Methods:

Inheritance

Like many modern languages, Types can also inherit fields and methods from other types in an object oriented fashion. The keywords which signal inheritance are `mixes` and `extends`. These follow the name of the new type.

Where C3 Type inheritance differs from other languages, is that these two keywords signal two different types of inheritance.

Mix-ins

This most basic type of inheritance is signaled by the 'mixes' keyword. When a type 'mixes' another type, it inherits all of the fields and methods of that type. The new type gets its own schema and table, and the user can go on to use it as they normally would.

type ParentType {
    fieldA: int
    funcA: function(): ReturnType
}

type ChildType mixes ParentType {
    fieldB: double
    funcB: function(): ReturnType
}

In this example, ChildType also includes the field and method defined in the parent type.

Persistable Inheritance

This second type of inheritance is signaled by the 'extends' keyword. When a type 'extends' another type, it inherits its fields and methods, but also resides in the same schema and table as the original type. In fact, a type can only extend a type which has been marked as `extendable`.

extendable entity type ParentType schema name "PRT_TYP" {
    fieldA: int
    funcA: function(): ReturnType
}

entity type ChildType extends ParentType type key "CHLD" {
    fieldB: double
    funcB: function(): ReturnType
}

In this example, ParentType is an extendable Persistable type stored in schema 'PRT_TYP'. ChildType extends ParentType and uses type key 'CHLD'.

C3.ai developer resources on Inheritance:

Annotations

C3.ai developer resources on Annotations:

Examples

Look through the lightbulbAD package and find .c3typ files. Look at these to see the range of possibilities

  • No labels