Using the functions

Hyperon util methods

A function is a code that executes logic and rules. It is useful not only for doing math calculations but also for grouping other rules together.
The function has to return something - it can be a simple value like a string or a boolean, or it can also be a more complex object. It all depends on your needs.

Let's talk about the basic elements of a function.

To start coding with Hyperon Studio, you first need to declare a programming language. Currently, there are two available languages: Groovy and Rhino. However, Rhino is deprecated and will be withdrawn from the application soon.

While functions can be constructed using only Groovy commands, nevertheless, there are some Hyperon-specific methods essential to making a fast and efficient algorithm. These additional methods are called util functions and have been added to the basic Java and Groovy methods.

They are the focus of this article.

Util functions in Hyperon allow users to fetch results from all Hyperon-specific elements: context, domain, functions, and parameters. To get you started, we gathered all the essential information here. But there is always more to know! The full documentation about all the methods is displayed in the function editor when a command is recognized:

How to get data from context

As you know, creating an algorithm in Hyperon starts with defining the context. Once the context is created, we use paths to navigate through it and pull data into functions and parameters. 

In Hyperon DEMO (the DEMO configuration is available in every version: https://www.hyperon.io/docs/download), you will find this function: demo.motor.util.drivingLicenceYears.

Let's take a look at a simple example: 

 // driver's current age
def driverAge = ctx.getInteger( 'quote.driver.age')

 // driver's age when obtaining licence
def obtainLicenceAge = ctx.getInteger( 'quote.driver.licenceObtainedAtAge')

 // diff
return driverAge - obtainLicenceAge

This function, like every other function in Hyperon, requires context - it will always be passed as an argument to a function. This is necessary to make all the util functions available in the function editor.

There are two elements pulled from the Hyperon context: driverAge and obtainLicenceAge.

The construction here is simple:

  • ctx.get<datatype>('contextPath')</datatype> is designed for simple elements like strings, integers, data, booleans, etc.
  • ctx.get('contextPath') is used when you want to pull an object.

An object like 'driver' is pulled using the following command:

def  driver = ctx.get('quote.driver')

Next, get selected drivers' age by simply using:

driver.getInteger('age')

Also you can get the same result using just one command:

def driverAge = ctx.getInteger( 'quote.driver.age')

How to get data from attributes

The next step is to iterate through context values. In the DEMO context, we have a root attribute: quote that includes many objects type like Option.

We need to use a path to get those values: 

def options = ctx.get('quote.options')

and then we can iterate through them to perform any necessary operation. Here the attribute code of each option is pulled:

options.each{ it ->                 
	def optionCode = it.code                
	log.info('option code {}', optionCode)            
}

We use Groovy commands to include different conditions.

How to call another function

As you're building an algorithm in Hyperon Studio, you may need to call another function.

The basic command is:

def functionResult = hyperon.call('functionCode', ctx)

This command will perform a singular execution of the function and will pull its results to defined in the main function variable functionResult.

The 'hyperon' command is one of many available in the functions' util methods. Typing the phrase 'hyperon.' in the function editor will display a popup with information about all available matching util methods. 

Functions require additional arguments when called. Their values have to be declared in the functions invocation command like:

def functionResult = hyperon.call('functionCode', ctx, arg1_value, arg2_value)

For example, in the function 'pl.decerto.demo.telco.package.activation.price.calculator':

the variable overallActivationPrice is defined by a function call : hyperon.call("pl.decerto.demo.telco.package.activation.price.sum", ctx, packageCode) -  in the 4th line of the function body

where packageCode is a variable given as an argument to pl.decerto.demo.telco.package.activation.price.sum function.

Why do we need to pass an argument?

Well, because that function requires an argument, as you can see in the function settings:

But it's not always necessary to pass arguments to a called function. However, just note that an argument that's not passed to a function will need to have its value assigned in a function.

Also, arguments have to be passed in the correct order. When a function requires five arguments, you can skip passing only the last listed arguments.

Let's illustrate this with an example.

We have a function named 'exampleFunction' :

C= C? C : 1   
return A*B*C*D

that requires arguments A, B, C and D (all integers), the call can look like this: 

hyperon.call('exampleFunction', ctx, value_A, value_B, value_C, value_D)

or

hyperon.call('exampleFunction', ctx, value_A, value_B, null, value_D)

but when typed like this:

hyperon.call('exampleFunction', ctx, value_A, value_B, value_D)

value_D will be assigned as variable C within the function 'exampleFunction' and variable D will be considered null.

How to call a parameter

Another case is to invoke a parameter. When a parameter has only one OUT level and only one result is expected, then we use a simpler command to declare the result value type: hyperon.getString('parameterCode', ctx), hyperon.getInteger('parameterCode', ctx), hyperon.getBoolean('parameterCode', ctx) etc.

Every parameter can be called using: 

hyperon.getParamValue('parameterCode', ctx)

We then receive a table of values as a result. Keep in mind that you can select OUT level values and iterate through rows of the result.

For example: 

in function pl.decerto.demo.telco.invoice.discount.price.calculator, parameter pl.decerto.demo.telco.package.invoice.discount is called.

It includes one IN level: InvoiceEntryNumber and two OUT levels : discounts and cross-sell promotion.

Firstly, the parameter is invoked:

def pv = hyperon.getParamValue('pl.decerto.demo.telco.package.invoice.discount', ctx)

Then, each result column is assigned to a variable. And only the first row of the result is considered:

def discountCodes = pv.row().getStringArray('discounts')
def includeCrossSellPromotion = pv.row().getBoolean('cross-sell promotion')

For the discounts column, an array of string values is pulled because each returned result is a list of string values. Then, simple boolean values are pulled for the cross-sell promotion column. The type of pulled values declared in the function complies with the parameter level definition.

However, the example above has one main flaw - it doesn't consider all the possible results. From our (programming) point of view, it can be a flaw but from a business perspective, it was an intended action because this parameter in its current state will always return only one result.

Let's see what happens when we make a small alteration to it: add UNION (more about this and other level properties can be found here) to the invoiceEntryNumber level and add a row to the matrix with values: ( 1-12 ; EMAIL, HBO ; false ).  As a result of these minor changes, the same parameter will always return two results. 

To include all pulled result rows, we have to make the following iteration: 

def pv = hyperon.getParamValue('pl.decerto.demo.telco.package.invoice.discount', ctx)
pv.each{ it ->    
	def discountCodes = it.getStringArray('discounts')    
	def includeCrossSellPromotion = it.getBoolean('cross-sell promotion')
}

How to fetch elements from the domain

The last hyperon-specific command is domain.get. To learn more about this command we have to use domain structure (in our example, the domain is from the TELCO demo profile).

If our goal is to extract value from the Available attribute (blue marked) in the HBO discount, then the function should include domain util method with selected profile code domain.get("TELCO"), to get the COMFORT collection in the Packages branch: .get("/Packages[COMFORT]"). Next, we have to run .get("/DISCOUNTS[HBO]") to navigate to the HBO element in the DISCOUNTS collection. And lastly, we run the .getAttrBoolean("Available", ctx) command to pull values from the Available attribute. 

The final function should look like this: 

def comfortDiscountHbo = domain.get("TELCO").get("/Packages[COMFORT]/DISCOUNTS[HBO]")
def isDiscountAvailable = comfortDiscountHbo.getAttrBoolean("Available", ctx)


Another way of getting into the DISCOUNT collection and fetching all the elements therein is by running .getAll("DISCOUNTS"). We then navigate into the DISCOUNT collection and run the .getAttrBoolean("Available", ctx) command to pull values from the selected attribute in the domain. 

def comfortPackage = domain.get("TELCO").get("/Packages[COMFORT]")
for (def discount : comfortPackage.getAll("DISCOUNTS")) {     
	discount.getAttrBoolean("Available", ctx)    
}