## Extendable

Relapse does not allow the specification of new functions in Relapse itself. Relapse is implemented in Go, so adding your own functions will require you to write some Go.

## Introduction

Let’s look at the implementation of the `contains`

function:

Our function is defined as a `struct`

, which has to implement the following `Func`

interface:

Each function must also have an `Eval`

method which returns a value of type:
`string`

, `[]byte`

, `int64`

, `uint64`

, `bool`

or `float64`

and an `error`

.

Our example is a function of type `Bool`

so it implements an `Eval`

method that returns a `bool`

and an `error`

and also the `Func`

interface.

Finally, the function is registered with its name and a constructor.

The constructor is responsible for:

- placing the parameters in the
`struct`

, for later evaluation, - simplification of the function (optional),
- trimming: calculating functions at compile time, if possible, and
- pre-calculating a hash of the function and whether the function’s parameters has a variable.

Each parameter is of type `Func`

. For our example, `contains`

, both parameters are of type `String`

, which is an interface which inherits the `Func`

type.

Simplification is only done in rare cases like for the functions: `and`

, `or`

and `not`

,
so you won’t need to worry about that.

Trimming tries to evaluate the function at compile time and replace it with a constant.
It can only trim pure functions, whose parameters also don’t have variables.
This is why we calculate the `hasVariable`

value, by checking whether any of the parameters have a variable.
If no parameter has a variable and our function is pure then it can be trimmed to a constant and should return `false`

from its `HasVariable`

method.
There is a `Trim<Type>`

function for each type.

Pre-calculating a hash is done to speed up comparisons.
Comparisons are done as part of simplifications and minimizes the state space required by relapse.
These comparisons are quite expensive for large queries and so we use hashes to speed them up.
This is also why we want to calculate the hash only once.
The `Hash`

helper function expects the function’s name and its input parameters.

Now that we have defined the constructor, we can take a look at the other methods.
The `Hash`

and `HasVariable`

methods simply return the pre-calculated values.
These values are pre-calculated for efficiency reasons.

The `String`

method should return the string representation of the function in its relapse syntax.
Parsing this string as an expression should result in the same function.

The `Compare`

method is used for simplification of patterns and functions.
We first compare the Hash values, because if they differ then we don’t need to do a deep comparison.
If the hash values are the same, then we need to look deeper.
The most likely case is that the function types are the same.
We can then compare the function’s input parameters.
If they are all the same, then we should return `0`

for equal.
If the types and hashes differ, then our last and most expensive resort is to create a string representation and compare these strings.
This is really expensive for large expressions and that is why we first try the hash and deep comparisons.

The `Eval`

method evaluates each parameter and then using the resulting values does the actual function calculation and returns the value.

All function types are defined here.

Finally the `init`

function registers the `contains`

structure as a Relapse function.
The first parameter is the function name.
This can differ from the structure name, which is especially useful when we want to do function overloading.

## Testing

Adding a user function is quite advanced behaviour and it is recommended that each function should be tested. Here is an example test for the contains function:

This will test that registration occurred correctly.

## Handling Errors

Some functions are inevitably going to have possible runtime errors.
Here we see an `elem`

function which returns the element in the list at the specified index.

When we see that the function is trying to access an element outside of the list range, we simply return a plain go error.

### Handling Errors in Boolean Functions

The not function does not return errors. This means that when it receives an error it interprets it as false and returns true.

This means that functions that are opposites of each other need to take this into account. Here we can see the greater or equal function returning false given an error.

This means that the less than function needs to return true given an error.

## Constants and Compile Time Evaluations

There are some functions for which you want to calculate some things only once, for example a regular expression matcher compiles the pattern only once. Lets look at Relapse’s built in regular expression function.

We pre-calculate `r`

in the constructor as a field member of the structure.
Our `Eval`

method can then use the compiled regular expression to match the bytes.

We first check that `expr`

does not have a variable,
since we want to evaluate the expression at compile time.
In the constructor we specify that this parameter is a `ConstString`

.
This is just a `String`

,
but it makes sure that the generated documentation mentions that this function requires the parameter to be calculated at compile time.

## Variables

Variables are values that possibly change with every execution, typically these are fields, but they can also include functions whose values change over time, database versions, etc.

If your function does not evaluate to the same value given the same parameters every time, you should declare it as variable.
Simply return `true`

from the `HasVariable`

method.

Lets look at the `now`

function:

Obviously this function’s value will be different almost every time that it is evaluated.

We make sure that `HasVariable`

returns `true`

, so that this function won’t be trimmed and will return a different value for each evaluation.