Apama Documentation : Developing Apama Applications : Developing Apama Applications in EPL : Defining What Happens When Matching Events Are Found : Defining actions : Using action type variables
Using action type variables
In addition to defining an action, you can define a variable whose type is action. This lets you assign an action to an action variable of the same action type. An action is of the same type as an action variable if they have the same argument list (the same types in the same order) and return type (if any).
Defining action variables
The format for defining an action type variable is as follows:
action<[type1[, type2]...]>[returns type3]name;
Specify the keyword, action.
Follow the action keyword with zero, one or more parameter types enclosed in angle brackets and separated by commas. The angle brackets are required even when the action takes no arguments.
Optionally, follow the parameter list with a returns clause. Specify the returns keyword followed by the type of the returned value.
Finally, specify the name of the variable. For example:
action<string> a;
action<integer, integer> returns string b;
You can use an action variable anywhere that you can use a sequence or dictionary variable. For example, you can
*Pass an action as a parameter to another action.
*Return an action from execution of an action.
*Store an action in a local variable, global variable, event field, sequence, or dictionary.
You cannot route, emit, enqueue or send an event that contains an action variable field.
You must initialize an action variable before you try to invoke it.
When an action variable is a member of an event the behavior of the action depends on the instance of the event that the action is called on. Consequently, it can be handy to bind an action variable member with a particular event instance. See Creating closures.
Built-in methods are treated exactly the same as user-defined actions. This means you can assign a built-in method to an action variable. For example:
action<float> returns string f := float.toString;
Invoking action variables
The only operation that you can perform on an action variable is to call it. You do this in the normal way by passing a set of parameters in parentheses after an expression that evaluates to the action variable. For example:
monitor Test;
   integer i;
   action<string> x; // Uninitialized global action variable.
   action onload() {
 
      // Invoke the runMe action. The first argument to runMe is an
      // action variable for an action having a single argument of
      // type integer and no return value.
      // Since the printInteger action conforms to the argument
      // expected by runMe, you can pass printInteger to runMe.
      runMe(printInteger, 10);
 
      // Declare a local action variable, g. This action takes one
      // integer argument and does not return a result.
      // The printInteger action conforms to this so
      // assign printInteger to g.
      action<integer> g := printInteger;
 
      // Invoke the runMe action again.
      // Pass g instead of explicitly passing printInteger.
      runMe(g, 20);
 
      // Declare a local dictionary that contains action variables.
      // Each action variable takes a single integer argument and
      // and does not return a result.
      // Add printInteger to the dictionary.
      // Invoke printInteger and pass 30 as the argument.
      dictionary<string, action<integer> > do := {};
 
      do["printIt"] := printInteger;
      do["printIt"] (30);
 
      // Invoke x. Since this global variable was never
      // initialized, the monitor instance terminates.
      x("hello!");
   }
 
   action runMe(action<integer> f, integer i) {
      f(i);
   }
 
   action printInteger(integer i) {
      print i.toString();
   }
}
After injection, this monitor prints
10
20
30
and then terminates upon invocation of x because x was never initialized.
Calling an uninitialized, local action variable causes an error that prevents the correlator from injecting the monitor. While the correlator injects code that contains an uninitialized, global action variable, trying to call the uninitialized variable causes a runtime error and the monitor instance terminates.
Declaring action variables in event definitions
When you define an action as a member field in an event, that action has an implicit self argument as the first argument. (See Specifying actions in event definitions.) You must include this implicit argument when determining whether an action definition conforms to an action variable declaration. For example, the following is illegal:
event A {
   action foo(float) returns string {
      return "Hello";
   }
   action bar() {
      action<float> returns string f := A.foo;
   }
}
In the previous code, you cannot assign the A.foo action to f because f takes a single float argument whereas A.foo has two arguments — the implicit A argument and then the float argument. To correct this example, specify A as the first action argument in the body of the bar action.
event A {
   action foo(float) returns string {
      return "Hello";
   }
   action bar() {
      action<A, float> returns string f := A.foo;
   }
}
Actions in place of routed events
In some situations, you might find it more efficient to use action type variables instead of routing events. For example, suppose you implement a service that takes an action variable as one of its parameters. Now suppose that the service needs a response from an adapter or some other service before it can send a response. When ready, the service can respond with a routed event, but that means you have to set up an event listener for that event. Routing events and setting up event listeners is more expensive than invoking actions. So instead of routing and listening, the service can respond by invoking the action on the event that initiated the service request. For example:
The following sample code uses a routed event. Following this code there is a sample that uses an action on an event.
event ServiceResponse {
string requestId;
...
}
 
event Service {
action doRequest( string requestId, ... ) {
...
// when asynchronous 'service actions' are complete
route ServiceResponse( requestId, ... );
}
...
}
 
monitor Client {
Service service;
action onload() {
...
string id := ...;
ServiceResponse r;
on Response( requestId=id ): r {
...
}
service.doRequest( id, ... );
}
}
The following sample code uses an action on a Client monitor:
event Service {
action doRequest( action< ... > callback, ... ) {
...
// when asynchronous 'service actions' are complete
callback( ... );
}
...
}
 
monitor Client {
Service service;
action onload() {
...
string id := ...;
service.doRequest( onServiceResponse, ... );
}
action onServiceResponse(...) {
...
}
}
Creating closures
When an action is a member of an event the behavior of the action depends on the instance of the event that the action is called on. Consequently, you might want to bind an action member with a particular event instance. When you bind an action member to an event instance you are creating a closure. The advantages of creating a closure are:
*Simpler syntax for executing the action
*Greater flexibility in making assignments to action variables
Consider the following event definition:
event E {
   integer i;
   action foo() { print "Foo "+i.toString(); }
   action times(integer j) returns integer { return i*j; }
}
With this definition, E(1).foo() would print "Foo 1", while E(42).foo() prints "Foo 42". The action E.foo always has a specific instance of E to work with. You can achieve this by specifying the action's implicit self argument when you call the action, as described earlier in this topic. When you use this technique you identify the event instance when you call the action variable.
Alternatively, you can create a closure that binds an action member with an event instance. You store the closure in an action variable. The action variable and the action member must be of the same action type. That is, they must take the same argument(s), if any, and return the same type, if any.
When you use this technique you identify the event instance when you assign the event's action member to the action variable.
The following code shows an example of binding an event instance to an action member by storing the closure in an action variable.
monitor m {
   action <> a;
   action onload() {
      E e := E(42);
      a := e.foo;
      a(); // Prints "Foo 42"
   }
}
In this example, e.foo denotes E.foo called on e. That is, when you assign the action e.foo to the a action variable you are identifying which instance of E to use when you call the a action. This closure binds a reference to E to the E.foo action and stores it in the a action variable. After you create a closure, you can call an action on an event as though it is a simple action. This gives you considerable flexibility in what you can assign to an action variable.
More about closures
EPL performs its own garbage collection. Consequently, you do not need to consider how long a bound object must last. This is handled automatically.
A closure binds by reference. Consider the following example, which uses the same event E as above:
monitor m {
   action <integer> returns integer a;
   action onload() {
      E e := E(3);
      a := e.times;
      print a(2).toString(); // Prints "6"
      e.i := 5;
      print a(2).toString(); // Prints "10"
   }
}
In a portion of code, you can define multiple action variables that contain closures for the same object. For example:
event Counter {
   integer i;
   action increment() { i := i+1; }
   action output() { print i.toString(); }
}
event Increment {}
 
event Finish {}
 
monitor m {
   action <> incrementAction;
   action <> outputAction;
   action onload() {
      Counter counter := new Counter;
      incrementAction := counter.increment;
      outputAction := counter.output;
      on all Increment() and not Finish() { incrementAction(); }
      on all Finish() { outputAction(); }
   }
}
In an event type, when an action member refers to another action member in the same event type a closure happens implicitly. For example:
event E {
   action <integer> returns integer a;
}
 
event Plus {
   integer i;
   action f(integer j) returns integer { return i+j; }
   action setA(E e) { e.a := f; }
}
Here, the f in e.a := f is equivalent to self.f, just as it would be if setA had called f instead of assigning it to an action variable. This creates a closure. After setA is called on some instance of Plus, e.a will call f on that same instance.
Other ways to specify closures
You can create a closure using any value and any action on that value. Thus, it is possible to:
*Bind a built-in method to a value.
*Bind actions to primitive types and other reference types instead of to events.
*Bind actions to a literal or a function's return value instead of a variable's value.
For example:
// Print "E(42)"
E e := E(42);
action <> printE42 := e.toString;
 
// Print "Foo 12345"
action <> printFoo12345 := E(12345).foo;
 
// Take a floating-point number and return e to that power:
action <float> returns float eToTheX := 2.718282.pow;
 
// Return a random integer from 0 to 9 inclusive.
// (The brackets around 10 are needed so that "10." is not treated as a
// floating-point number.)
action <> returns integer randomDigit := (10).rand;
 
// Return the strings in a sequence, separated by colons.
action <sequence<string> > returns string j := ":".join;
Restrictions
You cannot route, enqueue, emit or send an event that contains an action variable field. It is okay to route, enqueue, emit or send an event that contains an action definition.
An action variable cannot be a key in a dictionary. An event that contains an action field cannot be a key in a dictionary.
JMon
In a JMon application, you cannot declare event types that have action type members. Consequently, events that contain action type fields are invisible to JMon applications.
Copyright © 2013-2016 Software AG, Darmstadt, Germany.

Product LogoContact Support   |   Community   |   Feedback