Skip to content
This repository has been archived by the owner on Sep 10, 2024. It is now read-only.

Cumulocity-IoT/apama-lambdas

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lambdas Build Status Coverage Status

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)

Contents

Installation

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.

1. Installing into Designer

  1. Place the Lambdas folder somewhere safe (somewhere not likely to be moved or deleted)
  2. Run the deploy.bat
  3. Follow the instructions
  4. Restart any running instances of SoftwareAG Designer

2. Adding to a Project

  1. From SoftwareAG Designer right click on your project in Project Explorer
  2. Select Apama from the drop down menu;
  3. Select Add Bundle
  4. Scroll down to LAMBDAS_HOME/bundles and select Lambdas for EPL
  5. Click Ok

When run via Designer, it will automatically inject all of the dependencies.

3. Packaging a project (For use outside Designer)

The Apama tool engine_deploy packages a project so that it can be run outside of designer.

  1. Start an Apama Command Prompt (Start menu, Software AG, Tools, Apama, Apama Command Prompt)
  2. cd to your project directory
  3. Run engine_deploy --outputDeployDir output.zip . <rx_epl_install_dir>/lambdas.properties. You'll end up with a zip of your entire project.
  4. Unzip it on whichever machine you'd like to use the project.
  5. Run correlator --config initialization.yaml --config initialization.properties from within the unzipped directory to run the project.

When to Use Lambdas?

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));

Different Type of Lambdas

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)");

Language Features

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 in floats
  • Operations involving integer and decimal result in decimal
  • Operations involving integer and float result in float
  • Operations involving float and decimal result in float
  • 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.