Apply and Bind
Apply and Bind are advanced nodes that can be used to manipulate functions and arguments. These nodes are not typically required in most graphs, however understanding their usage provides deeper insights into the use of functions in MCG.
Apply
The first concept to absorb is that functions in MCG can be defined at any point, and are not exclusively bound to nodes like For Each and Combine. In the example below, we show how a node sequence can be converted into a function application.
In this case, we've chosen to convert Rotation X Matrix as a function which takes one argument (recall that the arguments of a function are its unconnected inputs). This function is applied to the angle value with Apply 1. Apply 1 evaluates the supplied one-argument function with the given input. If the function exposed two arguments, we would use Apply 2 with two input values, and so on.
The conversion above can be extended into a more useful application by letting the user choose which function to apply in the graph. To do this, we can collect our functions into an array, and then we use the At operator to pick which function to apply.
Fn1[Float->Matrix]
.Apply and Compound Function Inputs
General-purpose compounds often make use of function inputs like Compound Input: Function 1 to let the caller define a transformation or a filtering function.
In the screenshot below (taken from the MCG 2018 sample pack's Filter Indices compound), the result of the compound is an array of indices for which the filterFn returns True.
To use this compound, the required function type is no different from the one used in the Filter operator, except that the output is an array of integers representing the indices of the original array. This array of indices can then be used to sample aligned arrays (compared to the normal Filter operator which only returns the preserved values, and not the indices).
Bind
The Bind node comes into play when your graph makes use of stateful nodes which advance their own state every time they return a value. The primary example of such a stateful node is the RandomGenerator, which increments its own random state to continuously serve new random values every time it is called.
As of MCG 2018, the preferred way to generate random values is with the Array of Random [Type] operators. However, in cases where the number of random values is unknown, you may require the RandomGenerator and the Bind node.
The main issue surrounding stateful nodes like RandomGenerator is controlling when these nodes are re-used in a function, and avoiding re-creating the same generator on every function invocation. Case in point: in the graph below, the same random generator will be created on each iteration, and will serve the exact same random value. This is not the desired random effect.
This occurs because the random generator is being declared inside the function - it can be reached during the function's traversal from the function connector back towards its incoming nodes.
To avoid creating the generator repeatedly, we must declare it outside the function, so the first step is to disconnect it. When we do that, we notice that the function's type has changed from Fn1[Float->Mesh]
to Fn2[RandomGenerator,Float->Mesh]
, meaning that the function now exposes two arguments (1): RandomGenerator
, (2): Float
. What's more, the red wire indicates that the function's type is incompatible with the For Each node (which requires a Fn1[Float->Mesh]
).
Recall that groups do not affect a function's definition. The groups in these screenshots are only present to help visualize the function's contents.
A Bind node is required to re-use the same RandomGenerator
in each invocation of the function. The function exposes two inputs, and we only need to bind the first one, so we'll use Bind 1 of 2. If the wanted to bind the second one, we would use Bind 2 of 2.
The result of a Bind node is a new function with fewer arguments left to fill. You can imagine that binding an argument is like plugging a hole, and leaving the remaining hole(s) unplugged for later. In the example above, the remaining Float
will be filled by the values in the the array connected to For Each.
To summarize the primary difference between Apply and Bind:
Apply: runs a function on the inputs to create a new output value.
Bind: assigns a function's arguments to the specified values, but does not run the function.
An Extreme Example
The initial example above which rotates a mesh can be re-written it in various ways using different configurations of Bind and Apply.