1. Home
  2. FlexRule Runtime (SDK)
  3. Extending expression

Extending expression

Print Friendly, PDF & Email

◷ Reading Time: 9 minutes

Functions

Extending functionality at the expression evaluation level can happen in two ways:

  1. Implementation of the function
  2. Registering the function

Implementation

You can define functions in two ways:

  1. Static methods on types
  2. Anonymous Func
  3. Static Method

Static Types

To register by static methods:

  1. Create a class (e.g., InExtensions)
  2. Create static methods and mark them by Function

For example, there is a class to define two functions:

  • In: Checks whether an element exists in an array
  • Array: Creates or groups a series of values as an array of values

Let’s first have a simple implementation in our application and then demonstrate how to use this as part of your expression evaluation.

 public static class InExtensions
{
    /// <summary>
    /// Finds value in array
    /// </summary>
    /// <returns>true if value found, otherwise false</returns>
    [Function("in")]
    public static bool In(object value, object[] array)
    {
        if (array == null)
            return false;
        return array.Contains(value);
    }
 
    /// <summary>
    /// Groups a series of objects to one reference
    /// </summary>
    /// <returns>Returns the object that holds the array</returns>
    [Function("array")]
    public static object Array(params object[] array)
    {
        return array;
    }
} 

As you can see, we have decorated the static methods with an attribute named Function which marks the methods with the function name.

Anonymous Func

There are other ways to register a function as well. Not all of the types you want to register to allow you to add Attributes on top of them. So in those cases, one option is to use anonymous delegates.

variable.RegisterFunction("in",
    new Func<object, object[], bool>((a, b) =>
    {
        if (b == null)
            return true;
        return b.Contains(a);
    })
);
variable.RegisterFunction("array",
    new Func<object[], object>(a => a)
    ); 

Static Method

Use any existing static method of any type as a function by its method signature. The signature of a method is:

{namespace}.{type name}.{method name}({its arguments})

For example, a System.Threading.Thread is a type in .Net Framework and it has a method called Sleep.

The signature of the method would be:

System.Threading.Thread.Sleep(int)

And you can register the method as a function.

Evaluation Context

The defined function will be called during the evaluation of the expression. In order to get the context of the evaluation in some advanced scenarios, you can add an extra argument to your method with the type of IDynamicEvaluationContext. During the evaluation, the context will be passed to your method.

[Function("insert", Pipe = true)]
public object Insert(IDynamicEvaluationContext context, object value, object index, object obj)
{
    // context will be passed in to your method.
} 

In the above implementation, we are introducing the monadic operator (function) called insert.

IDynamicEvaluationContext must be the first argument of your method.

Registration

The only thing left to do now is to register the type with static methods as functions. The evaluation engine automatically maps the methods marked by FunctionAttribute to a function with the provided function name.

The registration can be done via:

  1. Static Method
  2. Code
  3. Declaration Section
  4. Import Function (in Excel only)

Static Method

To register a static method as a function, simply put the method signature in the path of Using command.

<DecisionTable name="ClaimProcessing">
    <Declaration>
        <Define name="Claim" direction="In" />
        <Define name="Product" direction="In" />
        <Using function="True" path="System.DateTime.DaysInMonth(int, int)" name="GetDays" />
    </Declaration>
 
<-- the rest of the logic is omitted here --> 
</DecisionTable> 

Note that in the above example, GetDays now is a function that can be used directly in your logic.

Code

Code using RegisterFunction on VariableContainer or RuntimeEngine instance.

Register on VariableContainer:

var variable = new VariableContainer();
variable.RegisterFunction(typeof(InExtensions)); 

Register on RuntimeEngine:

var engine = RuntimeEngine.FromXml(InMemoryFileStore.GetByName("DiscountDecision.xml").AsBytes());
engine.RegisterFunction(typeof(AgeExtensions)); 

Declaration Section

The registration function can also be part of the document definition’s declaration section:

 <DecisionTable name="ClaimProcessing">
    <Declaration>
        <Define name="Claim" direction="In" />
        <Define name="Product" direction="In" />
        <Using function="True" path="FlexRule.UnitTests.RuleUtilities" assembly="FlexRule.UnitTests.dll" />
    </Declaration>
 
<-- the rest of the logic is omitted here --> 
</DecisionTable 

Note that in the above example, all of the Functions on type RuleUtilities will be registered as part of the execution context.

Excel for Decision Tables

As mentioned previously, before using a function you need to register it in the logic document. In an Excel-based Decision Table use, ‘Import Function’ with the ‘type@assembly’ format and the rest will be taken care of automatically.

Usages

Once the functions are registered, you can call them like any other functions. These will be available on the context you have registered (i.e., the logic document). You can use those anywhere in any logic, or use them directly as part of evaluating an expression. Use this technique to simply extend the expression and call a custom code via your logic as a function. The benefit of function is that it will be available globally, which means to execute it you don’t need to use object instance or type name.

Example

As an example, here is a test that evaluates the two new functions we introduced as part of direct expression evaluation.

 [TestMethod]
public void test_extending_expressions_by_functions()
{
    var variable = new VariableContainer();
    variable.RegisterFunction(typeof(InExtensions));
 
    variable.RegisterVariable("age", 40);
    object result = ExpressionEval.Default.Compute(variable, "in(age, array(3,4,56,60,3,10))");
    Assert.IsFalse((bool)result);
 
    result = ExpressionEval.Default.Compute(variable, "!in(age, array(3,4,56,60,3,10))");
    Assert.IsTrue((bool)result);
 
    result = ExpressionEval.Default.Compute(variable, "in((age-30), array(3,4,56,60,3,10))");
    Assert.IsTrue((bool)result);
} 

Logic as Function

It is also possible to extend the expression by other logic that is already modeled. For example, a use case would be in a Natural Language for which there is a need to connect to database in order to retrieve data.

In order to do that:

  1. Model your logic (i.e., data retrieval in this example) in some other type (i.e., a procedural logic)
  2. Register your logic as a function in using as part of Declaration section of your rule (i.e., the Natural Language)

Setting up in Designer

Open the “Type Definition” that is going to use the function in your current model:

Then add the following settings by adding a new item:

And in your actual model (i.e., Natural Language) you can do the following:

Please note, you can accomplish the same thing for any type of logic (e.g., Flow, Decision Table, Tree, etc.) to use any other type of logic as a function.

And in your LoadDataFromDb.xml you have something similar to the model shown below:

<Procedure name="LoadData" enabled="True">
  <Declaration>
    <Define name="list" direction="Out" />
    <Define name="tsql" direction="In" />
  </Declaration>
  <Database connection="Data Source=.\SqlExpress;Initial Catalog=car-insurance;User ID=sa;Password=123;MultipleActiveResultSets=True" type="MsSql">
    <SelectRow command="tsql" return="list" multi="True" dynamicSQL="True" expando="True" />
  </Database>
</Procedure>

Monads

Monads are a specific type of functions that can be chained together using the pipe operator. You can simply create your own monads by defining a custom class like as you would for function using FunctionAttribute the only difference is to set the attribute Piple=true.

 static class CustomPipe
{
    [Function("increase", Pipe = true)]
    public static object IncreaseNumber(IDynamicEvaluationContext context, object value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var number = (int)value;
        return number + 1;
    }
} 

Please note, your monad function signature must have at least the two arguments as shown in the above example:

  • context: the context of expression evaluation
  • value: the left operand of the monad that is passed in for processing

If the monad requires more arguments, you can add them as object parameters.

And the usage will be similar to the code below:

 [TestMethod]
public void test_custom_pipe()
{
    var vc = new VariableContainer();
    vc.RegisterFunction(typeof(CustomPipe));
    var result = ExpressionEval.Default.Compute(vc, "2|increase()|increase()");
    Assert.AreEqual(4, result);
} 

Extension Methods

In the .Net framework, extension methods are compiled time binding. In FlexRule Runtime there is no compilation and models are running at run-time. Therefore, to use extension methods you need to register them as functions. In so doing there are a couple of options:

  1. Create a utility class that wraps your extension method and applies FunctionAttribute on its methods.
  2. Use delegate and register a function that uses your extension methods.
  3. Expose the method as an instance level of your facts layer
Updated on July 25, 2019

Was this article helpful?

Related Articles