Functions

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>
    
  

Custom Functions

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.

functions

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:

  • It calls 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.


  • It calls 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.


  • Finally it returns the next value, by calling toValue() on its StrategyResult.