This is a library that adds lambdas to Apama.
Lambdas in EPL (Apama's programming language) are closely based on Arrow Functions in JavaScript. They are inline actions that manipulate one or more provided values, implicitly returning the result.
action<any> returns any multiplyBy10 := Lambda.function1("x => x * 10");
multiplyBy10(1.5) = <any> 15.0;
This library is particularly useful with RxEPL (A functional programming library, similar to streams)
First head over to the Release Area and download the latest release.
The deployment script (Inside the release) provides a way to make Lambdas for EPL globally available to all SoftwareAG Designer workspaces.
- Place the Lambdas folder somewhere safe (somewhere not likely to be moved or deleted)
- Run the deploy.bat
- Follow the instructions
- Restart any running instances of SoftwareAG Designer
- From SoftwareAG Designer right click on your project in
Project Explorer
- Select
Apama
from the drop down menu; - Select
Add Bundle
- Scroll down to
LAMBDAS_HOME/bundles
and selectLambdas for EPL
- Click
Ok
When run via Designer, it will automatically inject all of the dependencies.
The Apama tool engine_deploy
packages a project so that it can be run outside of designer.
- Start an Apama Command Prompt (Start menu, Software AG, Tools, Apama, Apama Command Prompt)
cd
to your project directory- Run
engine_deploy --outputDeployDir output.zip . <rx_epl_install_dir>/lambdas.properties
. You'll end up with a zip of your entire project. - Unzip it on whichever machine you'd like to use the project.
- Run
correlator --config initialization.yaml --config initialization.properties
from within the unzipped directory to run the project.
Ever find yourself writing this:
aFunctionThatTakesACallback(callback);
...
// Much further down
...
action callback(float arg1, float arg2) {
// A simple callback
return arg1 + arg2 + 30.0;
}
You can simplify the code by doing this:
aFunctionThatTakesACallback(Lambda.function2("arg1, arg2 => arg1 + arg2 + 30"));
This is particularly useful when used with functional programming (Eg. RxEPL)
Observable.fromValues([1,2,3,4,5])
.filter(Lambda.predicate("x => x % 2 = 0"))
.map(Lambda.function1("x => x * 30"))
.reduce(Lamda.function2("sum, x => sum + x"))
.subscribe(Subscriber.create().onNext(printValue));
Functions - Take a varying number of arguments and return a value
action<> returns any ex0 := Lambda.function0("10 + 5 / 2 + ' = 12.5'");
action<any> returns any ex1 := Lambda.function1("x => x * 10");
action<any, any> returns any ex2 := Lambda.function2("x, y => x + y");
action<sequence<any> > returns any ex3 := Lambda.function("x, y, z => x + y / z");
Predicate - Take a single argument and returns a boolean
action<any> returns boolean ex := Lambda.predicate("x => x >= 10 and x < 20");
Call - Run a lambda just for side-effects
action<any> ex := Lambda.call("x => x.doSomething(10 + 3)");
Lambdas attempt to provide a language very close to EPL but without the need for casting. You will never have to write <sequence<float, boolean> >
within a lambda!
Numbers
All numbers are treated the same and can be operated on without conversion.
Lambda.function1("x => x + 10 + 11.1 + 22.2d")(1.1) = <any> 44.4
This follows the rules of coercion.
Strings
Strings can be defined with either \"
or '
. Generally '
is easier because there is no need for the backslash escape character.
Lambda.function1("x => \"hello\" + 'world'")
Brackets
The usual bracketing syntax applies
Lambda.function1("x => (x + 5) / 12")
Numeric Operators
All of the usual epl operators (%
, /
, *
, -
, +
).
Note: Type coercion may happen.
Logical Operators
All of the usual epl operators (=
, !=
, >=
, >
, <=
, <
, and
, xor
, or
,not
).
Note: Type coercion may happen.
Some non-epl operators:
!
- Same as not
Ternary Operator
Lambda.function1("x => x > 10 ? 'Big' : 'Small'")
Field, Dictionary, and Sequence Access
Lambda.function1("event0 => event0.fieldName")
// or
Lambda.function1("event0 => event0['fieldName']")
Lambda.function1("dict => dict[key]")
// or (If the dictionary has string keys)
Lambda.function1("dict => dict.key")
Lambda.function1("seq0 => seq0[0]")
Special Values
currentTime
- The same as in EPL - Gets the current time in seconds (usually rounded to nearest 100ms)
Constants
Constants can be accessed in the same way as in EPL, except that the event on which they are defined must be fully qualified (regardless of any using
statements).
Lambda.function1("x => com.example.MyEventType.MY_FIRST_CONSTANT")
Event Construction
Events can be constructed in the standard epl form:
Lambda.function1("x => com.example.MyFirstEvent(x, x)")
Event names must be fully qualified (regardless of any using
statements)
Where possible automatic coercion will occur for all fields.
Action Calling
Actions can be called on events as usual in EPL.
Lambda.function1("x => x.round()")(10.2) = 10
There are also some additional utility actions added to some types:
Type | Action | Description |
---|---|---|
float | .toFloat() |
Convert to float |
decimal | .toDecimal() |
Convert to decimal |
integer | .sqrt() |
Calculate the square root |
integer | .round() |
Round to an integer |
integer | .ceil() |
Round upwards to an integer |
integer | .floor() |
Round downwards to an integer |
sequence | .get(i) |
Get the value at index i |
sequence | .getOr(i, alt) |
Get the value at index i or an alternative value if it does not exist |
sequence | .getOrDefault(i) |
Get the value at index i or a default value if it does not exist |
Static Action Calling
Static actions are supported as in EPL.
They can be accessed from an event instance directly, or using the fully qualified event type.
// Accessing from an event instance
Lambda.function2("instance, x => instance.someStaticAction(x)")(new MyEvent, 10.2)
// Access from the type
Lambda.function1("x => com.example.MyEvent.someStaticAction(x)")(10.2)
Sequence Literals
Sequences can be constructed in lambdas in much the same way that they can in EPL, except that they are always sequence<any>
.
Lambda.function1("x => [x, x + 1, x + 2]")(0) = [<any>0, 1, 2]
Spread Operator
The spread operator expands a sequence inside another sequence:
Lambda.function1("x => [...x, 3, 4, 5]")([0, 1, 2]) = [<any>0, 1, 2, 3, 4, 5]
Array Destructuring
When using lambdas (particularly with Observables) you may find that a lambda is provided with a sequence as the argument. Rather than accessing each value using seq[index]
it is easier to assign a name to each item in the sequence:
Lambda.function1("[sum, count] => sum / count")([56, 7]) = <any> 8.0
Type Coercion
Numbers and sometimes values are coerced to sensible types, using the following rules:
- Operations on two values of the same type mostly result in the same type
- Operations on
integers
which may result in fractions result infloats
- Operations involving
integer
anddecimal
result indecimal
- Operations involving
integer
andfloat
result infloat
- Operations involving
float
anddecimal
result infloat
- Operations on any type and
string
will call.valueToString
on the any type
If you need the result to be a particular type (and that isn't possible through explicit typing eg. 22.0d
) then use the .toFloat()
, .toDecimal()
, .round()
, ceil()
, .floor()
, .toString()
actions.
These tools are provided as-is and without warranty or support. They do not constitute part of the Software AG product suite. Users are free to use, fork and modify them, subject to the license agreement. While Software AG welcomes contributions, we cannot guarantee to include every contribution in the master project.