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()
andparse()
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
orenqueue...to
statement - Sent outside the correlator with the
emit
statement
- Sent by the
- Comparable — A comparable type can be used as follows:
- Dictionary key
- Item in a sequence on which you can call
sort()
orindexOf()
- 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 typeE
. This property is used only to determine whetherE
is acyclic.
The following table shows the properties of each Apama type.
Type | Indexable | Parseable | Routable | Comparable | Acyclic | E -free |
---|---|---|---|---|---|---|
boolean |
||||||
decimal |
1 | |||||
float |
1 | |||||
integer |
||||||
string |
||||||
location |
||||||
Channel |
2 | |||||
Exception |
||||||
context |
||||||
any |
3 | 3 | 3 | |||
listener |
||||||
chunk |
||||||
stream |
||||||
action |
||||||
sequence |
||||||
dictionary |
||||||
optional |
||||||
event E |
4 | 4 |
Legend:
Symbol |
Description |
---|---|
Yes. This type has the corresponding property. 1 Attempts to use a 2 A 3 The | |
No. This type does not have the corresponding property. | |
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. | |
The type is comparable only when all its constituent types are both comparable and acyclic. | |
An event |
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:
event.getTime()
for information about when the correlator assigns timestamps to events (see the API reference for EPL (ApamaDoc)).- Using the TimeFormat event library for information about formatting timestamps.
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);
See also
See the descriptions of the built-in types in the API reference for EPL (ApamaDoc) for the methods you can call on types and instances.
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 anoptional
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 anoptional
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’sreturns
clause, or the return type is theany
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 | |
|
assign and clone |
cast |
cast |
cast |
cast |
cast |
cast |
cast |
cast |
cast
|
|
assign |
assign |
assign |
| ||||||
|
assign |
assign |
|
|
assign |
| ||||
|
assign |
assign and clone |
assign |
| ||||||
event |
assign |
assign and clone |
assign |
| ||||||
|
assign |
|
assign |
|
assign |
| ||||
|
assign |
|
|
assign |
assign |
| ||||
|
assign |
|
|
|
|
|
|
assign |
|
|
|
assign |
assign |
assign and clone |
| ||||||
|
assign |
|
|
|
|
|
|
assign |
|
assign and |
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 typeevent
if it contains only comparable typeslocation
sequence
if it contains items that are a comparable typeoptional
if it contains a comparable typeany
if it contains items that are a comparable typecom.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 typeevent
if it contains at least one incomparable typelistener
sequence
if it contains items that are an incomparable typestream
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 thedictionary
does not need to be comparable. - In a
sequence
if you want to call theindexOf()
orsort()
method on thatsequence
. - 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
oroptional
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 toF
, nor does it contain anyaction
variables. However, it does containE
, which is a potentially-cyclic type. Therefore, an instance ofF
might contain cycles.Likewise, a
dictionary
orsequence
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
ise1.seq[0].seq[0]
e2
ise2.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:
|
|
|
|
|
|
|
|
|
|
|
|
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, theparse()
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 quietNaNs
. 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()
(ifx < 0
)x.ln()
(ifx < 0
)x.log10()
(ifx < 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
(ifx > 0
)x/-0.0
(ifx < 0
)Infinity.sqrt()
The following operations return negative infinity:
x/0.0
(ifx < 0
)x/-0.0
(ifx > 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.