Build 424: Functions and Array Operators


Kynetx Logo

The latest build of the Kynetx Rule Language (KRL) provides a significant upgrade in capability with the addition of functions. We've also added some new array operators that take advantage of functions to make using arrays easier.

KRL supports functions as first-class objects in the expression language. KRL supports only anonymous functions, but they can be given names by binding them to a variable in a declaration. Here's an example:

pre {
  add5 = function(x) {
           x + 5
         };
}

Functions are evaluated statically (e.g. the environment they are defined in, not the environment they are executed in determines the binding of free-variables) and can be recursive. Here's an example of a recursive function in KRL:

pre {
  fact = function(n) {
            (n <= 0) => 1
                      | n * fact(n-1)
         }
}

Functions are declared using the keyword function and contain optional declarations followed by a single expression that returns the result of the function when executed. To see this, consider the following example which uses Newton's method to calculate square roots (taken from Section 1.1.8 of Structure and Interpretation of Computer Programs):

sqrt = function(x) {
    average = function(x,y) { (x + y) / 2 };
    good_enough = function(guess, x) {
       v = (guess * guess) - x;
       v < 0.01 && v > -0.01
    };
    improve = function(guess, x) {
       average(guess, (x / guess))
    }
    sqrt_iter = function(guess, x) {
       good_enough(guess, x) => guess
                              | sqrt_iter(improve(guess,x), x)
    };
    sqrt_iter(1.0, x)
}

Functions can return functions as values and functions can be passed as the arguments to other functions and operators in KRL. The following example defined a generalized summation function that sums the numbers from a to b incrementing using inc and applying the function f to each term:

sum = function(f, a, next, b) {
  (a > b) => 0
           | f(a) + sum(f, next(a), inc, b)
};
inc = function(x) { x + 1 };
cube = function(x) { x * x * x };
sum_cubes = function(a, b) {
  sum(cube, a, inc, b)
}

We could define a function that creates incrementor functions. When given a number, it returns a function that increments by that value:

inc_generator = function(n) { function(x){ x + n } };
inc = inc_generator(1);
inc_by_2 = inc_generator(2);
inc_by_25 = inc_generator(25);

Being able to write functions adds significant power. More so with some of the other languages changes we have in mind for the next few months.

Weve also added several new array operators in recent builds. Most notably, array references now work as follows:

a = [1,4,3,6,5];
b = a[1]

This would bind the value 4 to the variable b. Note that array references only work for arrays of one-dimension, so c[1][2] is not allowed (presuming c is an array of arrays).

In addition, there are a number of new operators available for arrays. The following array operators are now available (in addition to length which has been previously available):

  • sort - sorts the array. With no argument, sorting is done in ascending order. The argument "reverse" causes sorting to happen in descending order. The argument can also be a function that takes two argument and returns a boolean value which will be used as the comparison function for the sort.
  • filter - filters an array, producing a new array. The operator takes a function argument that takes a single parameter and returns a boolean value. The return array contains elements for which the function returns true.
  • map - modfies an array from mapping a function to each member of the array. The operator takes a function argument that takes a single parameter and returns any value. The array returned from map is the result of applying the function to each member of the original array in turn, collecting the results into a new array.
  • head - returns the first element of an array without modifying the array.
  • tail - returns an array that is identical to the orginal array except without the first member.

You could use these like so:

pre {
  f = function(x) { x < 4 };
  g = function(y) { y * 2 };
  a = [1,4,3,6,5];

  b = a.sort(); // returns [1,3,4,5,6]
  c = a.filter(f); // returns [1,3]
  d = a.head(); // d has the value 1
  e = a.map(g); // e has the value [2,8,6,12,10]
}

Operators are fairly easy to add and handy to have, so if you have ideas for other operators, on arrays, strings, and so on, just let us know.