Package com.singularsys.extensions.chained


package com.singularsys.extensions.chained
Allows chained style notation for function composition. For example:
total= [1..10] . map(x=>x^2) . sqrt() . fold([x,y]=>x+y)
The composition operator, . by default, is created using the ChainingOpPfmc and functions in the middle and end of the chain implement ChainedFunctionI which defines an
apply(Evaluator ev, Node node, Object arg)
method that takes an evaluator, a node representing the current function and the value returned by the previous element in the chain.

A number of adapter classes are provided to allow the higher order functions in the com.singularsys.extensions.lambda package and general Jep functions to be used in a chain. These come in two flavours, Chained functions that support the chained notation and Chainable functions that can either be used in chain or as a regular function. All the adapters implement ChainedFunctionI.

Chained adapters:

Chainable adapters:

These are typically used in conjunction with the higher order functions in the com.singularsys.extensions.lambda package, but can be used with any Jep function. Typically each non-terminal function in a chain will return a list.

Simple set up

The ChainedOperatorTable creates a set of operators for lambda functions, and the range operator. The ConfigurableParserWithRange adds support for parsing the range operator syntax [1..10]. The ChainedFunctionSet adds the various higher order functions and the StandardListProcessor converts results to the standard Jep list format.
var cot = new ChainedOperatorTable("=>", "..", "->", true);
var cp = new ConfigurableParserWithRange("..");
lp = new StandardListProcessor();
FunctionSet lfs = new ChainedFunctionSet();
jep = new Jep(cot, cp, lp, lfs);
Use ChainableFunctionSet to use chainable versions of the function.

Chained set up

A simple set up is

// Create a Jep instance using the standard parser
// and standard list processor from the lambda package.
jep = new Jep(new StandardConfigurableParser(),
          new StandardListProcessor());
// Gets the operator table, here its the standard operator table
var ot = (OperatorTable2) jep.getOperatorTable();

// adds the lambda operator, allowing the x=>x^2 notation
Operator lambdaOp = new Operator("=>", new LambdaFunGenerator(), Operator.BINARY);
ot.insertOperator(lambdaOp, ot.getAssign());
// Change the dot operator so that it behaves as function composition
ot.getDot().setPFMC(new ChainingOpPfmc());

// Adds intermediate functions:
// map, filter are both binary functions that are converted to
// unary chained functions using the ChainedBinaryFunction adapter.
jep.getFunctionTable().addFunction("map",
    new ChainedBinaryFunction(new Map()));
jep.getFunctionTable().addFunction("filter",
    new ChainedBinaryFunction(new Filter()));

// Add a vectorised version of the sqrt function
// which is applied to each element of the list in the chain.
jep.getFunctionTable().addFunction("sqrt",
    new ChainedUnaryFunction(
       new ExternalMap(new SquareRoot())));

// Terminal functions
// fold, and reduce
jep.getFunctionTable().addFunction("fold",
    new ChainedBinaryFunction(new Fold()));
jep.getFunctionTable().addFunction("reduce",
     new ChainedNaryFunction(new Reduce()));

// Construct a terminal sum function
// using the TerminalNaryBinaryFunction adapter to convert
// the Add Jep function to a unary function that sums a list of values,
// Finally use the ChainedUnaryFunction adapter to allow it to be used in the chain.
jep.getFunctionTable().addFunction("sum",
    new ChainedUnaryFunction(
        new TerminalNaryBinaryFunction(
            new Add(), 0.0)));

jep.reinitializeComponents();

Which allows the expression

[1,2,3,4,5] . filter(x=>x%2==0) . map(x=>x^2) . reduce(1,[x,y]=>x*y);
[1,4,9] . sqrt() . sum();
to be parsed and evaluated.

A more complete set of function is provided in the ChainedFunctionSet component. Also see Chained Documentation for more details and examples.

Chainable setup

To allow the same functions to be used in a chain or as regular functions, the chainable adapters can be used. There use the AlternateFunctions and OverloadedFunctionByStructure classes from the com.singularsys.jep.misc.overloadedfunctions package to allow two different functions to be used depending on number of argument or structure of the parse tree. ChainableUnaryFunction ChainableBinaryFunction ChainableBinaryFunctionSwapped take a single Unary or Binary function when used in a a chain a ChainedUnaryFunction or ChainedBinaryFunction is used, otherwise the original function is used.

For Nary function or a general PostfixMathCommand the ChainableGeneralFunction takes a ChainedFunctionI and a PostfixMathCommandI and a predicate that tests the structure of the parse tree to determine which function to use.

A simple chainable set up is

// Modify the StandardOperatorTable2
var ot = new StandardOperatorTable2();

// Adds the range operator, allowing the [1..10] notation
Operator rangeOp = new RangeOperator("..", "[", "]", new Range(), Operator.BINARY + Operator.NOT_IN_PARSER);
ot.insertOperator(OperatorTable2.SpecialOperators.RANGE, rangeOp, ot.getAssign());

// adds the lambda operator, allowing the x=>x^2 notation
Operator lambdaOp = new Operator("=>", new LambdaFunGenerator(), Operator.BINARY);
ot.insertOperator(lambdaOp, ot.getAssign());

// Modify the dot operator so that it behaves as function composition
// when the second argument is a ChainedFunctionI, and regular dot product otherwise.
OverloadedOperator.extendOperator(
    ot,
    ot.getDot(),
    new ChainingOpPfmc(),
    n -> n.jjtGetChild(1).getPFMC() instanceof ChainedFunctionI);

// Modify the standard parser to allow the range operator
var cp = new StandardConfigurableParser();
// Add a new symbol token for ".."
var stm = cp.getSymbolTokenMatcher();
stm.add(new SymbolToken(".."));

// Remove the existing list and array access matchers and replace with a
// new ListOrRangeGrammarMatcher matcher that allows both lists and ranges,
// and an array access matcher that allows the same syntax as before.
// This ensure the ListOrRangeGrammarMatcher is tested before the array access matcher
var g = cp.getGrammarMatchers();
g.removeIf(m -> m instanceof ListGrammarMatcher);
g.removeIf(m -> m instanceof ArrayAccessGrammarMatcher);
cp.addGrammarMatcher(
    new ListOrRangeGrammarMatcher(
        cp.getSymbolToken("["), cp.getSymbolToken("]"),
        cp.getSymbolToken(","), cp.getSymbolToken(".."), rangeOp));
cp.addArrayAccessMatcher("[", "]");

// Create a Jep instance using the modified parser and operator table,
// and the standard list processor from the lambda package.
jep = new Jep(cp,ot,
    new StandardListProcessor());

// Adds intermediate functions:

jep.getFunctionTable().addFunction("map",
    new ChainableBinaryFunction(new Map()));
jep.getFunctionTable().addFunction("filter",
    new ChainableBinaryFunction(new Filter()));
jep.addFunction("sqrt",
    new ChainableUnaryFunction(
  	   new ExternalMap(new SquareRoot())));

// Adds terminal functions
jep.getFunctionTable().addFunction("fold",
    new ChainableBinaryFunction(new Fold()));
jep.getFunctionTable().addFunction("reduce",
    new ChainableGeneralFunction(
        new ChainedNaryFunction(new Reduce()),
        new Reduce(),
        n -> n.jjtGetNumChildren() ==2 ));

jep.getFunctionTable().addFunction("sum",
    new ChainableUnaryFunction(
        new TerminalNaryBinaryFunction(new Add(), 0.0)));

jep.reinitializeComponents();

Which allows expressions like:

[1..5] . filter(x=>x%2==1) . map(x=>x^2) . reduce(1,[x,y]=>x*y);
[1,4,9] . sqrt() . sum();
reduce(1,[x,y]=>x*y , map(x=>x^2, filter(x=>x%2==1, [1..5])));
sum( sqrt([1,4,9] ));

A more complete set of function is provided in the ChainableFunctionSet component.