Home | Quick Start | Documentation | Download | Support | Contribute

Adding A New Vector Computation To AFC

A vector computation is a (typically financial) function that computes a single value from a vector (array) of values. Here, we shall look at how I implemented `NPV()`. So let’s assume `NPV()` has not been implemented yet.

Note: You must have read the topics on adding primitive functions and high-level functions to understand this section.

Docs and Tests

As always, we start with the announcement in the release notes, and the tests:

 A B C D E F G H I J K L M N 1 Expected=IF(Q1,"Expected","FAILED!") Actual Inputs # of Inputs Name Highlight Excel says Skip for 2 1.188 1.188=NPV(C2,D2:G2) 10% -10 3 4.2 6.8 5 NPV NPV 3 1.922 1.922=NPV(C3,E3:I3)+D3 8% -40 8 9.2 10 12 14.5 7 long, bigdecimal

Parsing

Making `NPV()` known to the parser is much like what we did for `ABS()`. Array functions, however, need to accept vector arguments, not just simple values. These are denoted by `shapedRangeExpr()`:

``|	"NPV" open() expr() <SEP> shapedRangeExpr() closeFun( Function.NPV )``

This means:

• `open()` – open parenthesis, start collecting function arguments.
• `expr()` – single-valued expression.
• `shapedRangeExpr()` – vector range expression with shape information (width, height) available to compiler.
• `closeFun( Function.NPV )` – packs collected function arguments into a function expression node.

Mathematical Definition

The function `NPV()` is defined in the Excel help file as follows:

$\sum _{i=1}^{n}\frac{{\mathrm{values}}_{i}}{\left(1+\mathrm{rate}{\right)}^{i}}$

What we need here is a folding function that gives us access to the current index `i`. The `fold` we already encountered does not support this, but the very similar `iterate` does.

Rewriting It

Since `NPV` takes a vector as an argument, we need to apply the fold using `apply... to vectors {v1, ..., vn}` instead of `apply... to list xs`. And we tell the rewrite parser about this by suffixing the vector argument with `#`:

``````rewrite npv( rate, vs# ) =
let rate1 = rate + 1
in
apply
iterate with
r = 0
index i
each vi as
r = r + vi / rate1 ^ i
end
to vectors {vs}``````

Note how I used `let` here to move the common subexpression out of the folding loop.

Multiple Vectors

As the syntax above suggests, `apply... to vectors {v1, ..., vn}` can be used to fold multiple vectors in parallel. This demonstrated by `COVAR`:

``````rewrite covar( xs#, ys# ) =
if COUNT( xs ) <> COUNT( ys ) then NA() else
apply
fold with
sx = 0,
sy = 0,
sxy = 0
each xi, yi as
sx = sx + xi,
sy = sy + yi,
sxy = sxy + xi * yi
with count n into
(sxy - sx*sy/n) / n
when empty err( "#DIV/0! because list doesn't contain numbers in COVAR" ) ::NUMERIC
end
to vectors {xs, ys}``````