Sometimes logic behind a node does not depend on actual types of input values provided. Take, for example, the if-else
node:
It takes a boolean condition COND
and outputs either T
or F
value to R
depending on the condition. It should not matter what the actual types of T
, F
, and R
are. The only important thing is that they should be the same: all numbers or all strings, for example.
The generic nodes mechanism helps to support such polymorphic nodes.
You can understand that a node is generic by looking at its pin colors. The generic pins on such node are grey. If you open a helpbox for such node, you’ll see the pins have types like t1
, t2
, or t3
rather than concrete number
or string
.
If an input pin has type t1
that means it potentially can accept any data type. Once you link or bind, for example, a number to it all other t1
pins on that node are considered to be numbers too. The same for t2
and t3
. Note, the generic types t1
, t2
, and t3
do not depend on each other. So t1
may be a number while t2
is a string and so on.
Usually, you use generic nodes like regular ones and don’t think of logic behind type deduction, and resolution process. Although to handle some edge-cases you need to understand principles described below.
Whenever you transpile a XOD program which contains generic nodes, the system resolves them to concrete specializations, so that a particular C++ implementation can be found in the final turn.
The system tries to replace in-place each generic node with another node called a specialization node which is chosen based on deduced input types.
A generic node can be a composition of other generic nodes, or it can be abstract. Abstract nodes can’t translate to C++ because they contain no implementation and serve as placeholders for the transpiler (hence called abstract). If XOD can’t find a required specialization for an abstract node, it shows an error.
When dealing with a generic composite node, the system searches for a matching specialization, and if not found, it expands the composition and reruns resolution. The process repeats recursively until all specializations are found or only unresolved abstract nodes left, which is an error.
Let’s elaborate on each of concepts mentioned.
A specialization is a regular patch either having a distinctive name containing types in parentheses or having the same basename as an abstract patch if it’s a specialization for a type defined in the same library.
For example,
if-else(number)
if-else(string)
The name is made up of the abstraction patch name (if-else
in this case) and type specialization for t1
enclosed in parentheses.
If a generic node has two or more generic types, the types in parens should be comma-separated, e.g., foo(string,number,pulse)
.
In most cases, you should not bother on the specialization patches and nodes as you’re interfacing with the base abstraction (if-else
), and that’s enough. The specializations don’t even appear in the quick-search suggester by default.
For example, xod-dev/w5500/open-tcp
is a specialization for xod/net/open-tcp
, which uses a xod-dev/w5500/w5500-inet
type for t1
.
If such specialization use types from other libraries or base types, it should have a type specification in the name, like open-tcp(w5500-inet, number)
.
However, sometimes you will want to be specific and use a specialization directly as a regular node. That’s called manual resolution. To access a particular specialization, use one of the following methods:
if-el(
).To be successfully resolved later a generic node should match a specific type to each of t1
, t2
, and t3
. It is called type deduction.
The deduction is done based on actual types of links and values bound to the generic pins. XOD will show an error if types can’t be deduced unambiguously, for example when one pin of type t1
pretend to be number and another t1
pin wants to be a string. In such cases you can place cast nodes like format-number
before the inputs explicitly.
A slightly more complicated process takes place when multiple generic nodes link in a chain with their generic pins. In such cases two subsequent passes of type deduction are performed:
The strong pass overrides any results produced by the weak pass.
In IDE you’ll see the deduction results in real time. The pins deduced are zebra-colored with gray and the color which corresponds to the inferred type.
The weak resolution process exists only to deliver better developer experience in IDE when you’re embedding generic nodes into your patches.
The final program is guided only by the results of the strong pass.
On the last stage of transpilation, when abstract nodes have to be replaced with particular specializations, the specializations are chosen based on the types deduced previously.
All matches for t1
, t2
, and t3
are known at this point, so the remaining task is to find the proper specialization patch in the project. XOD looks for a patch with name <basename>(<t1>,<t2>,<t3>)
in the project and all libraries installed locally. For example, if the system is about to resolve an abstract if-else
node with t1
deduced to number
, the following patches will be found:
xod/core/if-else(number)
bob/stuff/if-else(number)
Now, if no specialization patches found XOD will show an error about unresolved abstract node. You have to create the specialization by yourself or install a library which includes proper specialization.
If two or more specializations were found, that’s an error too because the system cannot choose one of them unambiguously. In that case, you have to resolve the node manually using Inspector.
The process of creating own generic patches is documented in a separate article.