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.
Let’s look at the implementation of the
Our function is defined as a
struct, which has to implement the following
Each function must also have an
Eval method which returns a value of type:
float64 and an
Our example is a function of type
Bool so it implements an
Eval method that returns a
bool and an
error and also the
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
Simplification is only done in rare cases like for the functions:
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
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.
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.
HasVariable methods simply return the pre-calculated values.
These values are pre-calculated for efficiency reasons.
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.
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.
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.
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.
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.
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.
r in the constructor as a field member of the structure.
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
This is just a
but it makes sure that the generated documentation mentions that this function requires the parameter to be calculated at compile time.
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.
true from the
Lets look at the
Obviously this function’s value will be different almost every time that it is evaluated.
We make sure that
true, so that this function won’t be trimmed and will return a different value for each evaluation.