Types

Reference types

In addition to the primitive types, EPL provides for a number of object types. These types are manipulated by reference as opposed to by value (in the same way as complex types are handled in Java). These are the following reference types:

  • action
  • any
  • Channel
  • chunk
  • context
  • dictionary
  • event
  • Exception
  • listener
  • location
  • optional
  • sequence
  • StackTraceElement
  • stream

When a variable of reference type is assigned to another one of the same type, the latter will reference the same object as the former, and should one be changed, the other one would reflect the change as well.

If you require a variable of reference type to contain a copy of another one of the same type, that is a completely distinct but identical copy, then you should use the clone() method as described below. This returns a deep copy of the variable, that is, it copies it and all its contents (and their contents in turn) recursively.

The string type is technically a reference type, but unlike all other reference types, the string type is immutable; its value cannot change. The clone() method has no effect on strings, as they cannot be changed. Therefore, string behaves more like a primitive type.

Note that you cannot use an object type for matching in an event template. For example, suppose you have the following event types:

InnerEvent
{
   float f;
}

WrapperEvent
{
   string s;
   InnerEvent anInnerEvent;
}

The following statement is correct:

on all WrapperEvent(s = "some_string")

However, the following statement is not allowed:

on all WrapperEvent(anInnerEvent.f = 5.5)

More than one variable can have a reference to the same underlying data value. For example, consider the following code:

sequence <integer> s1;
sequence <integer> s2;
s1 := [12, 55, 42];
s2 := s1;
print s1[1].toString; // print second element of s1
s2[1] := 99; // change the second element
print s1[1].toString; // print second element of s1 again

Both s1 and s2 refer to the same array, so whichever variable you use, there is only one copy of the data values. So the program’s output is:

55
99

Default values for types

The following table lists the default values for the primitive types and reference types.

Type                Description String form       
action An empty value that throws an exception if you try to execute the action.
any Empty value. any()
boolean false
chunk Contains no state. Each plug-in must define what to do upon receiving a default-initialized chunk as an argument.
context An empty context that cannot be used in any meaningful way. To use this variable, you must explicitly assign a context that was created with a name.
decimal 0.0d
dictionary Empty dictionary. {}
event Instance of the event where each of its fields has the standard default values as per this table. *event\_name* (*default fields*)
float 0.0
integer 0
listener An empty listener that cannot be used in any meaningful way. To use this variable, you must assign a listener to it from within an on statement, from another listener variable, or from a stream listener in a from statement.
location (0.0,0.0,0.0,0.0)
optional Empty. optional()
sequence Empty sequence. []
stream An empty stream that cannot be used in any meaningful way. To use the variable you must assign a non-empty stream to it.
string Empty string. ""

Type properties summary

Apama type properties include the following:

  • Indexable — An indexable type can be referred to by a qualifier in an event template.
  • Parseable — A parseable type can be parsed and has canParse() and parse() methods. The type can be received by the correlator.
  • Routable — A routable type can be a field in an event that is
    • Sent by the route statement
    • Sent by the send...to or enqueue...to statement
    • Sent outside the correlator with the emit statement
  • Comparable — A comparable type can be used as follows:
    • Dictionary key
    • Item in a sequence on which you can call sort() or indexOf()
    • Stream query partition key
    • Stream query group key
    • Stream query window with-unique key
    • Stream query equijoin key
  • Potentially cyclic — A potentially cyclic type uses the @*n* notation when it is parsed or converted to a string. When a potentially cyclic type is cloned, the correlator uses an algorithm that preserves aliases. See Potentially cyclic types
  • Acyclic — An acyclic type is a type that is not potentially cyclic.
  • E-free — E-free types cannot contain references to instances of a particular event type E. This property is used only to determine whether E is acyclic.

The following table shows the properties of each Apama type.

Type Indexable Parseable Routable Comparable Acyclic E-free
boolean Yes Yes Yes Yes Yes Yes
decimal Yes Yes Yes  Yes1 Yes Yes
float Yes Yes Yes  Yes1 Yes Yes
integer Yes Yes Yes Yes Yes Yes
string Yes Yes Yes Yes Yes Yes
location Yes Yes Yes Yes Yes Yes
Channel No Yes2 Yes Yes Yes Yes
Exception No Yes Yes Yes Yes Yes
context No No  Yes Yes Yes Yes
any No Yes3 Yes3 Yes3 No No
listener No No No No Yes Yes
chunk No No No No Yes Yes
stream No No No No Yes Yes
action No No No No No No
sequence No Caution Caution Yellow star Caution Caution
dictionary No Caution Caution Yellow star Caution Caution
optional No Caution Caution Yellow star Caution Caution
event E No  Caution4  Caution4 Yellow star Blue star No

Legend:

Symbol

Description

Yes

Yes. This type has the corresponding property. 1 Attempts to use a NaN in a key terminates the monitor instance.

2 A Channel object is parseable only when it contains a string.

3 The any type is treated as parseable, routable and comparable, but it is comparable only if the contained type does not contain aliases. If the type of the value contained within it does not meet these requirements, such operations will throw an exception at runtime. An empty any value is parseable and comparable, and can be routed as a field of an event, but not as an argument to route.

No

No. This type does not have the corresponding property.

Caution

This type inherits the corresponding property from its constituent types, that is, the item type in a sequence, the key and item types in a dictionary, the types of fields in an event. The type has the corresponding property only when all its constituent types have that property. 4 An event defined inside a monitor cannot be received from an external source nor emitted from that correlator. An event defined inside a monitor can be sent or enqueued only within the same correlator.

Yellow star

The type is comparable only when all its constituent types are both comparable and acyclic.

Blue star

An event E is acyclic only when all its constituent types are both acyclic and E-free.

Examples

The following code provides examples of event type definitions and their properties.

// You can do everything with "Tick", including index both its fields.

event Tick {
    string symbol;
    float price;
}

// You can do everything with "Order", except refer to its target or
// properties fields in an event template.

event Order {
    string customer;
    Tick target;
    string symbol;
    float quantity;
    dictionary<string,string> properties;
}

// The correlator cannot receive the next event as an external event,
// but you can send it, route it, or enqueue it to a context.

event SubscriptionRequest {
    string channel;
    context recipient;
}

// You can do very little with this event except access its members and
// methods. It cannot be routed, you cannot sort sequence<TimeParse>,
// trying to group a stream query by TimeParse is illegal, and so on.

event TimeParse {
    import "TimeFormatPlugin" as TF;
    string pattern;
    chunk compiledPattern;
}

// This has all the same restrictions as TimeParse, but is also
// potentially cyclic, so will use the @n format when parsed or
// converted to a string.

event Room {
    string roomName;
    float squareFeet;
    sequence<Room> adjacentRooms;
    sequence<Employee> occupants;
}

Timestamps, dates, and times

Although EPL does not have time, date, or datetime types, timestamp (a date and time) values can still be represented and manipulated because EPL uses the float type for storing timestamps. See currentTime.

Timestamp values are encoded as the number of seconds and fractional seconds (to a resolution of milliseconds) elapsed since midnight, January 1, 1970 UTC and do not have a time zone associated with them. Although the resolution is to milliseconds, the accuracy can be plus or minus 10 milliseconds, or some other value depending on the operating system.

If you have two float variables that both contain timestamp values, subtracting one from the other gives you the difference in seconds.

You can add or subtract a time interval from a timestamp by adding or subtracting the appropriate number of seconds (60.0 for 1 minute, 3600.0 for 1 hour, 86,400.0 for 1 day, and so forth).

See also:

Type methods and instance methods

There are two kinds of inbuilt methods: type methods and instance methods. Type methods are associated with types. Instance methods are associated with values.

Type methods

To call a type method, you specify the name of the type followed by a period, followed by the method name with its parameters enclosed in parentheses. Some methods do not have parameters and for them you must supply an empty parameter list.

Examples:

event someEvent;
{
   integer n;
}
integer i;
i:=integer.getUnique();
print someEvent.getName();

Instance methods

Each type (except action), whether primitive or reference, has a number of instance methods that provide a number of useful functions and operations on instance variables of that type. These methods are quite similar to actions except that they are predefined and associated with variables, not monitors or events.

To call an instance method, you specify an expression followed by a period and the name of the method, followed by a parenthesized list of actual parameters or arguments to be passed to the method when it is called. Some methods do not have parameters and for them you must supply an empty parameter list.

Examples:

integer i := 642;
float f;
f := i.toFloat ();
print f.formatFixed (5);

Type conversion

EPL requires strict type conformance in expressions, assignment and other statements, parameters in action and method calls, and most other constructs. The only implicit conversion in EPL is to convert concrete types to the any type. This means that:

  • The left and right operands of most binary operators must be of the same type.
  • An actual parameter passed in a method or action invocation must be of the same type as the type of the corresponding formal parameter in the action or method definition, or the parameter’s type must be the any type.
  • The expression on the right side of an assignment statement must be the same type as that of the target variable, or the target variable is of the any type, or an optional of the value type.
  • The expression in a variable initializer must be the same type as that of the target variable, or the target variable is of the any type, or an optional of the value type.
  • The expression in a subscript expression (to locate a sequence entry) must be integer. The expression used as an index for a dictionary must be the same type as the dictionary’s key type (or the dictionary’s key type is any).
  • The expression in a return statement must be the same type as that of the action’s returns clause, or the return type is the any type.

For conversions between concrete types, the inbuilt methods on each type include a set of methods which perform type conversion. For example:

string number;
integer value;

number := "10";
value := number.toInteger();

This illustrates how to map a string to an integer. The string must start with some numeric characters, and only these are considered. So if the string’s value was “10h”, the integer value obtained from it would have been 10. Had the conversion not been possible because the string did not start with a valid numeric value, then value would have been set to 0.

These method calls can also be made inside event expressions as long as the type of the value returned is of the same type as the parameter where it is used. Therefore one can write:

on all StockTick("ACME", number.toFloat());

Method calls can be chained. For example one can write:

print ((2 + 3).toString().toFloat() + 4.0).toString();

Note that as shown in this example, method calls can also be made on literals.

The following table indicates the source and target type-pairs for which type conversion methods are provided.

Source Type

Target Type

any

boolean

decimal

dictionary

event

integer

float

optional

sequence

string

any

assign and clone

cast

cast

cast

cast

cast

cast

cast

cast

cast valueToString() toString()

boolean

assign

assign

assign

toString()

decimal

assign

assign

round() ceil() floor()

toFloat() cast

assign

toString()

dictionary

assign

assign and clone

assign

toString()

event

assign

assign and clone

assign

toString()

integer

assign

toDecimal() cast

assign

toFloat() cast

assign

toString()

float

assign

toDecimal() cast

round() ceil() floor()

assign

assign

toString()

optional

assign

getOrThrow()

getOrThrow()

getOrThrow()

getOrThrow()

getOrThrow()

getOrThrow()

assign

getOrThrow()

getOrThrow()

sequence

assign

assign

assign and clone

toString()

string

assign

toBoolean() parse()

toDecimal() parse()

parse()

parse()

toInteger() parse()

toFloat() parse()

assign

parse()

assign and parse()

In the table above, “assign” means values of the type can be directly assigned to another variable of the same type, without calling a type conversion method. “clone” means a value of the type can be copied by calling the `clone()` method. “cast” means that `any` type values can be cast \(see [Handling the any type](/developing-apama-applications-in-epl/defining-what-happens-when-matching-events-are-found#handling-the-any-type) for more information\).

Comparable types

The following types are comparable, and the operators <, >, <=, >=, =, or != can be used to compare two values of one of these types if both are the same type:

  • boolean
  • decimal
  • float
  • integer
  • string
  • context
  • dictionary if it contains items that are a comparable type
  • event if it contains only comparable types
  • location
  • sequence if it contains items that are a comparable type
  • optional if it contains a comparable type
  • any if it contains items that are a comparable type
  • com.apama.exceptions.Exception
  • com.apama.exceptions.StackTraceElement

The correlator cannot compare the following types of items:

  • action
  • chunk
  • dictionary if it contains items that are an incomparable type
  • event if it contains at least one incomparable type
  • listener
  • sequence if it contains items that are an incomparable type
  • stream
  • any if it contains items that are an incomparable type
  • Potentially cyclic types

For details about how the correlator compares items of a particular type, see the topic about that type.

In EPL code, you must use a comparable type in the following places:

  • As the key for a dictionary. The type of the items in the dictionary does not need to be comparable.
  • In a sequence if you want to call the indexOf() or sort() method on that sequence.
  • As a key in the following stream query clauses:
    • Equi-join
    • group by
    • partition by
    • with unique

Cloneable types

Since variables of reference types are bound to the runtime location of the value rather than the value itself, direct assignment of a variable of reference type copies the reference (that is, the value’s location) and not the value. To make a copy of the value, you must use the clone instance method instead of assignment. The types that have this property are called cloneable types.

The cloneable types are string, dictionary, event, location, optional, any and sequence.

For dictionary, event, any and sequence types, the behavior of the clone() method varies according to whether or not the instance is potentially cyclic.

  • When the instance is potentially cyclic, the correlator preserves multiple references, if they exist, to the same object. That is, the correlator does not create a copy of the object to correspond to each reference. See also Potentially cyclic types.
  • When the instance is not potentially cyclic, and there are multiple references to the same object, the correlator makes a copy of that object to correspond to each reference.

While you can call the clone() method on a stream value, or a value that indirectly contains a stream or listener value, cloning returns another reference to the original stream or listener and does not clone it.

Potentially cyclic types

A cyclic object is an object that refers directly or indirectly to itself. For example:

event E {
   sequence<E> seq;
}
E e := new E;
e.seq.append(e);

When an object is cyclic or contains a reference to a cyclic object, it can be referred to as containing cycles. If it is possible to create an object that contains cycles, the type of that object is referred to as potentially cyclic.

When a type has the potential to contain cycles, and you call parse() on that type, or toString() or clone() on an object of that type, the result is different from when those methods are called on a type, or object of a type that is not potentially cyclic. Consequently, it is sometimes important to understand which types are potentially cyclic and what the string form of these objects looks like.

Which types are potentially cyclic?

A type is potentially cyclic if it contains one or more of the following:

  • A dictionary, sequence or optional type that has a parameter that is of the enclosing type. For example:

    event E {
       dictionary<integer,E> dict;
    }
    event E {
       sequence<E> seq;
    }
    event E {
       optional<E> opt;
    }
    
  • An action variable member. For example:

    event E {
       action<E> a;
    }
    
  • An any variable member. For example:

    event E {
       any a;
    }
    
  • A potentially cyclic type. For example:

    event E {
       sequence <E> seq;
    }
    
    event F {
       E e;
    }
    

    F does not have any members that refer back to F, nor does it contain any action variables. However, it does contain E, which is a potentially-cyclic type. Therefore, an instance of F might contain cycles.

    Likewise, a dictionary or sequence is potentially cyclic if it has a parameter that is a potentially cyclic type. Consider the following event type:

    event E {
       sequence <E> seq;
    }
    

    Given this event type, dictionary<string, E> is potentially cyclic because its parameter is potentially-cyclic. Similarly, sequence<E> is potentially cyclic.

A cyclic object can indirectly contain itself. Consider the following, using the same definition of E as above.

E e1 := new E;
E e2 := new E;
e1.seq.append(e2);
e2.seq.append(e1);

In this example, both e1 and e2 are cyclic:

  • e1 is e1.seq[0].seq[0]
  • e2 is e2.seq[0].seq[0]

Following is another example of an object that indirectly contains a cycle:

E e3 := new E;
E e4 := new E;
e3.seq.append(e4);
e4.seq.append(e4);

In this example, e3 is cyclic, even though it does not refer back to itself. Instead, e3 refers to e4 and e4 refers back to itself.

You can pass objects that contain cycles between EPL and Java.

String form of potentially cyclic types

A potentially cyclic object might have more than one reference to the same object. When you need the string form of a potentially cyclic object, the correlator uses a special syntax to ensure that you can distinguish multiple references to the same object from references to separate objects that merely have the same content.

When the correlator converts a potentially cyclic object to a string, the correlator labels that object @0. If the correlator encounters a second object during execution of the same method, it labels that object as @1, and so on. Whenever the correlator encounters an object that it has already converted, it outputs that object’s @*index* label rather than converting it again. For example:

event E { sequence<E> seq; }

E e := new E;
e.seq.append(e);
print e.toString(); // "E([@0])"

Following is a more complicated example:

event Test {
   string str;
   sequence<Test> seq;
   string str2;
}

monitor m {
   action onload() {
      Test t:=new Test;
      t.str:="hello";
      t.str2:=t.str;
      t.seq.append(t);
      Test t2:=new Test;
      t.seq.append(t2);
      t.seq.append(t2);
      t2.seq.append(t);
      print t.toString();
   }
}

This prints the following:

Test("hello",[@0,Test("",[@0],""),@2],"hello")

The objects @0, @1, @2, and @3 correspond to the following:

@0

Test("hello",[@0,Test("",[@0],""),@2],"hello")

t in the above example

@1

[@0,Test("",[@0],""),@2]

t.seq in the above example

@2

Test("",[@0],"")

t2 in the above example

@3

[@0]

t2.seq in the above example

The following example uses the clone() method and contains action references. The result uses the new string syntax for aliases to the same object.

event E {
   action<> act;
   sequence<string> x;
   sequence<string> y;
}

monitor m {
   action onload() {
      E a:=new E;
      a.x.append("alpha");
      a.y:=a.x;
      E b:=a.clone();
      b.x[0]:="beta";
      print b.y.toString();
      print a.toString();
   }
}

The output is as follows:

["beta"]
E(new action<>,["alpha"],@1)

Note that dictionary keys can never contain aliases so they do not receive @*n* labels for referenced objects in toString() and parse() methods.

Whether you need to do anything to handle this string syntax depends on why you want a string representation of your object:

  • If you are using the string for diagnostic messages, you just need to understand the syntax.
  • If you plan to feed the string into the parse() method, the parse() method will handle it correctly.
  • If you plan to feed the string into some other program, you should either avoid repeated references in an object or make sure the other program can handle the @*index* syntax.

Support for IEEE 754 special values

EPL supports the following IEEE 754 special float and decimal values:

  • NaN — In EPL, these are quiet NaNs. The string representation is “NaN”.
  • +Infinity — The string representation is “Infinity”.
  • -Infinity — The string representation is “-Infinity”.

The correlator returns one of these values as the result of an invalid computation. For example, dividing zero by zero or calculating the square root of a negative number. The correlator returns infinities as the result of computations that overflow, for example, taking a very large number and dividing it by a very small number.

The correlator can receive external events that contain these special values. You can send, route, emit, and enqueue events that contain these values. If the correlator receives an event that contains a floating point value that is too large to be represented as a 64-bit floating point number, the behavior is as if the value had overflowed and the correlator represents the value as infinity.

See the descriptions of decimal and float in the API reference for EPL (ApamaDoc) for more information.

The following operations return NaN:

  • 0.0/0.0
  • x.sqrt() (if x < 0)
  • x.ln() (if x < 0)
  • x.log10() (if x < 0)
  • Infinity - Infinity
  • 0.0 * Infinity

In addition, most operations that accept NaN as a parameter return NaN. For example:

  • NaN.exp() = NaN
  • NaN + 3.0 = NaN

The NaN value behaves differently when compared to other floating point numbers. NaN does not compare equal to any other number, including itself. It is unordered with respect to all other floating point numbers, so NaN < x and NaN > x are both false.

The following operations return positive infinity (note that IEEE 754 has signed zeroes):

  • x/0.0 (if x > 0)
  • x/-0.0 (if x < 0)
  • Infinity.sqrt()

The following operations return negative infinity:

  • x/0.0 (if x < 0)
  • x/-0.0 (if x > 0)
  • (0.0).ln()

The constants that are supported by EPL ensure consistent values, and a few have been provided for convenience. For a list of all available constants, see the descriptions of decimal, float and integer in the API reference for EPL (ApamaDoc).

Operations where rounding is required use the IEEE standard approach of “round half to even” (also known as “banker’s rounding”).

Primitive and string types

Apama supports the following primitive types:

  • boolean
  • decimal
  • float
  • integer

In addition, there is string which is technically a reference type. However, strings are immutable. Therefore, string behaves more like a primitive type than a reference type.