A datamixer function is a container iterator
that gets its argument values from its nested iterators. With each
iteration, it gets the next value from each nested iterator, applies an
operator to all the values, and returns the Value object
returned by the operator. For example, the add function returns the sum of
values as an IntValue.
A function tries to make a reasonable decision about the returned value's
datatype. In general, it returns the datatype with the greatest precision.
For example, if two integer values are added, then the result is an integer.
If an integer and a double are added, then the result is a double. Datatype
can be explicitly coerced by calling setDataType(). If the
datatypes of the nested iterators are not compatible, then a TBD exception
is thrown. The following table lists datatype compatibility: (TBD).
This example adds two doubles, and generates 0.0, 0.2, 0.4, ...
<dm:doubles name="a" range="[0.0]0.1"/>
<dmf:add>
<iterator collection="a"/>
<iterator collection="a"/>
</dmf:add>
Functions can be nested. The next example generates a straight line with slope 3 and y-intercept -22, whose x values range from -5 to 4. This example shows that functions can use constant values as well as iterators. The result is:
-37 -34 -31 -28 -25 -22 -19 -16 -13 -10
<!-- f(x) = 3x - 22 for -5 <= x < 5 -->
<dmf:add datatype="int">
<dmf:multiply>
<dm:constants id="m" value="3"/>
<dm:ints id="x" range="[-5,5)"/>
</dmf:multiply>
<dm:constants id="b" value="-22"/>
</dmf:add>
This example can be rewritten with the line function. In
general, complex functions can be customized for simplicity and to increase
performance.
<dmf:line slope="3" intercept="-22">
<dm:ints id="x" range="[-5,5)"/>
</dmf:line>
A new function can be created by writing a class that derives from
FunctionStrategy
.
When a
FunctionIterator
generates its next value, it calls operator() on its
strategy. So one way to write a custom strategy is to override
operator().
ForEachStrategy
does this, as an example.
But FunctionStrategy itself implements operator(),
to provide convenience methods for some kinds of strategies. If a custom
strategy only needs to operate on the values returned by the function's
nested iterators, then this implementation of operator() may be
enough. In this case, the custom strategy only needs to implement the
operate() method. The arithmetic functions (add, subtract,
multiply, divide) use this approach.
FunctionStrategy's operator() takes these steps to
generate a next value:
initResult() to set an initial value into its
StrategyResult
object. The add and subtract strategies, for example, set an initial value
of 0, while the multiply and divide strategies set an initial value of 1.
getArguments() on its function iterator, to get the
next value from each nested iterator. Then it calls
handleArguments() to operate on these values. This method
does a recursive descent of a value that contains a
value list.
handleArguments() calls operate() on
each value that is not a values collection.
operate() can be overridden by derived strategy
classes, to do the actual work of implementing the strategy. For
example, in the add strategy, operate() simply adds
the value to the cumulative result in its
StrategyResult object.
toValue() on
its StrategyResult.