Introduction to generic programming with type parameters
Many modern programming languages implement generic programming support with the concept of type parameters. Generic programming using type parameters is the feature I have been missing the most in LabVIEW. Generic programming is about writing reusable blocks of code in terms of types that are specified in later time. In LabVIEW terminology type parameters would specify the type of wires and terminals of generic type. Type parameters can either be implicit or explicit. Implicit type parameters are determined by the compiler automatically where as explicit type parameters are specified by the developer. Furthermore type parameters can have variation limits, specifying what kind of type are accepted. Type parameters could be in principle applied to a VI, to a class, to a library, to a mixin class (trait), to an interface or any other kind of module that includes executable code or front panels. (EDITED Introduction & title 10/25/2009 9:36 GMT)
Type parameters in function specifications
To understand type parameters better, let’s think of LabVIEW subtraction (-) primitive. It has two inputs and a single output. The type of the output terminal is a function of the types of the inputs. In a text based programming language you could specify the LabVIEW addition primitive a s follows.
function subtract(x : T, y : T) -> ('x-y' : T)
Now T above is a type parameter that specifies that the type of output terminal ‘x-y’ is of the same type as x and y. But the above specification for addition is not complete. Consider we wire two strings to addition primitive. This is an illegal usage of the addition primitive and will result in a broken input wire. So we don’t want to allow the type T to be anything but want to limit is somehow. One way to limit the type to certain set of types is to write addition function to every type separately.
function subtract(x : I32, y : I32) -> ('x-y' : I32)
function subtract(x : U32, y : U32) -> ('x-y' : U32)
...
But this is awful lot of work. Another way is to allow compiler determine the type variations automatically based on the code.
function subtract(x, y) -> ('x-y') {
'x-y' := x + (-y);
}
Now a smart modern compiler can specify the type limits of x and y and ‘x-y’ to be the same as those of primitive ‘+’. That is, if a user would write the above function, a compiler would allow to use it with the same type combinations as the primitive ‘+’.
Third way to specify the limits of the type variation is to allow user to specify the limits explicitly.
function subtract(x : T <= Numeric, y : T) -> ('x-y' : T)
Above we have specified a upper type bound with expression T <= Numeric. That means that T can be any subtype Numeric type or type Numeric itself.
In addition to upper bounds we can also define lower bounds. This doesn’t make sense with subtract operation but consider the operation of adding an element to an array. If we add a real element to a integer array, we would end up with a numeric array with both integer and real numbers. We can specify the signature of adding element to an array as follows
add_element('array in' : Array[T], elem : U >= T) -> ('array out' : Array[U])
That is adding element elem of type U to an array ‘array in’ of type T would result in an array of type U. The type of the element elem can only be a super type (ancestor type) of T or type T itself. In addition to lower bounds and upper bounds we could specify combinations of multiple bounds. For example input parameter of a function needs to be subtype of type Connection and of type Loggable ( T <= Connection && T <= Loggable). An example of such type would be a specific connection class that derives from class Connection and that implements mixing class Loggable. Or we can specify the input parameter of a file I/O function to be either a path or a string (T = String || T = Path).
Type parameters in class specifications
So far we have only considered function prototypes or VI connector panes in LabVIEW terminology. However we used a unspecified notation Array[T] to specify an array of type T. Custom types can be defined by specifying classes, mixin classes or interfaces. So let’s consider how we can make our classes utilize type parameters. Let’s consider our class Array. We want to be able to make any kinds of arrays. So we can use type parameters to specify the type of an array.
class Array[T] {
typedef T;
typedef U >= T;
param head : T;
param tail : Array[T];
create_array('array in' : Array[T], elem : T) -> ('array out' : Array[T]) {
'array in'.head = elem;
'array out' = 'array in';
}
add_element('array in' : Array[T], elem : U) -> ('array out' : Array[U]) {
'array in'.tail = 'array in';
'array in'.head = elem;
'array out'='array in';
}
In the class Array[T] above the type of the array elements is specified by the type parameter T. To be able to reuse the generic type definition T in multiple functions of the class, we have created a typedef for T that does not restrict the type T at all. We also have created another typedef U that restricts the type U to be a supertype type T or type T itself. Rest of the class utilizes these two typedefs.
So how do we bound the type parameters. Let’s use the create_array method to create an instance of this class. The ‘array in’ will be not specified and the elem will be specified.
create_array(5 : Int32);
When number 5 of type Int32 is passed to create array method, the compiler can fix the element type T of the array. In LabVIEW terms create_array output terminal ‘array out’ would then be of type Array[Int32], which is a concrete type. Naturally there could be more than one type parameter in a class. Consider class 2dPoint[X,Y] that would specify a point in two dimensional space where the first co-ordinate would be of type X and the second co-ordinate of type Y.
Inheritance of type parametrized classes
There is one more thing to cover about type parameters, that is the inheritance of types that depend on type parameters. If type I32 is a subtype of type Numeric, then is Array[I32] subtype of Array[Numeric]. In English the question can be written as ‘Is integer array a numeric array?’. Integer array obviously is a numeric array, so there needs to be a way to tell the compiler this kinds of relations. The way to do this is by specifying a class type parameter to be either covariant or contravariant.
class Array[+T]
specifies an array of type T that is covariant to the changes of type T. For covariant type parameters we get a covariant subtype relationship regarding this type parameter. For our example this means Array[T] is a subtype of Array[S] if T is a subtype of S. The opposite holds for contravariant type parameters that are tagged with a -.
Conclusions
LabVIEW does not support type parameters in the way described in this post. However some built-in primitives act as if they were using type parameters.
- Addition primitive can accept multiple types and the output type is specified by the input terminal types. Furthermore the the primitive act as if it had lower type bounds; primarily subtypes of numeric types can be used as inputs.
- The element type of output of build array primitive is upper bounded (ancestor type) of the input elements.
- Obtain queue primitive accepts anything as inputs and returns a Queue of that type as output. It behaves in similar way as the create_array method in Array[T] class in our example above.
- Data value references of class types have covariant behaviour; DVR[T] is subtype of DVR[S] is T is subtype of S.
- Controls, constants and indicators of Queue and DVR types hold an explicit type parameter that must be explicitly specified. The type parameter is shown by right clicking the control and selecting show control.
- Backwards fully connected terminals of LVOOP method VIs support automatic upcasting as if the output type and input type both were specified by type parameter T with a lower bound of the particular class type.
The lack of type parameter support forces LabVIEW developers to write the same code multiple times to support different types of parameters. From my opinion this is the largest obstacle against code reusability in LabVIEW. For R&D the lack of type parameter support means higher software development costs and more bugs and more difficult maintainability. National Instruments uses type parameter like behavior for the LabVIEW primitives but for some reason the type parameter support for developers does not exist. Type parameter covariance and contravariance have some problems with mutable states but LabVIEW being a dataflow programming language could mostly avoid these difficulties. I think type parameters could quite easily be supported in LabVIEW and in the future post to my blog I try to explain how type parameters could be implemented in LabVIEW development environment.
2 Comments
Make A CommentComments RSS Feed TrackBack URL
Leave a comment
You must be logged in to post a comment.


October 25th, 2009 at 7:27 am
Isn’t it something like template keyword in C++? I remember a post on NI forum by somebody from NI R&D team that they consider templates a low priority update for now.
October 25th, 2009 at 9:07 am
Templates in C++ and type parameters are both a way to do generic programming and in that sense similar. Templates in C++ are rather old form of generic programming. Modern statically typed languages use type parameters with much better static type checking features. In a sense it’s a pity C++ is used for LabVIEW development as most of LabVIEW R&D engineers gain no experience on modern languages. I think we should not use term templates in communication with NI. Nobody wants such decades old programming concepts as C++ templates and nobody should request templates. It is generic programming we want instead and type parameters is the way to do it in modern statically typed languages like LabVIEW.