Jump to content

Type parameters


Recommended Posts

Hi

I posted a new article to ExpressionFlow regarding type parameters. For me it is the one of the most missed features in LabVIEW. Check out the post.

Introduction to Type Parameters

EDIT: Seems like the links to expressionlfow still don't work from LAVA. Please copy paste from below

expressionflow.com/2009/10/23/introduction-to-type-parameters/

Tomi

Link to comment

Hi

I posted a new article to ExpressionFlow regarding type parameters. For me it is the one of the most missed features in LabVIEW. Check out the post.

Introduction to Type Parameters

EDIT: Seems like the links to expressionlfow still don't work from LAVA. Please copy paste from below

expressionflow.com/2009/10/23/introduction-to-type-parameters/

Tomi

I don't get it :unsure:

Labview IS a strictly typed language (only functions of the same type can be connected and a conscious decision to "cast" must be made to other types) as opposed to (say) PHP which isn't (anything can be assigned to virtually anything). Delphi is another example of a strictly typed language whereas C is loosely typed (I think).

The add function you describe is a strictly typed primitive with polymorphism which, in other strictly typed text based langauges, would be an overloaded function.

So I don't really see what your getting at. What am I missing?

Link to comment

I don't get it :unsure:

Labview IS a strictly typed language (only functions of the same type can be connected and a conscious decision to "cast" must be made to other types) as opposed to (say) PHP which isn't (anything can be assigned to virtually anything). Delphi is another example of a strictly typed language whereas C is loosely typed (I think).

The add function you describe is a strictly typed primitive with polymorphism which, in other strictly typed text based langauges, would be an overloaded function.

So I don't really see what your getting at. What am I missing?

Maybe you are misunderstanding the concepts of static (strict) and dynamic type systems. Static type system is a type system that doesn't allow runtime type errors to occur whereas for dynamic type system runtime type mismatches can occur. Static type system can still be very flexible, indeed almost as flexible as dynamic type system. Type parameters allow creating statically typed functions and classes etc. with polymorphism without actually needing to duplicate code. You can for example write a QueueClass class that behaves exactly the same as queue primitive functions in LabVIEW including similar kind of polymorphism for both queue element type and queue reference type. Unlike with dynamic languages, you could never connect something with incorrect type to QueueClass methods, in exactly the same way as you cannot do the thing with LabVIEW primitives. So what you could get with proposed type parameter bases type system is LabVIEW primitive like flexible polymorphic behavior for custom developed types.

Type parameters is one way to do generic programming. Quoting wikipedia: "Generic programming is a style of computer programming in which algorithms are written in terms of to-be-specified-later types that are then instantiated when needed for specific types provided as parameters." Type parameters is about specifying to-be-specified-later types defining relations between these generic to-be-specified-later types as well as setting limits to generic to-be-specified-later types. So you can do a lot of

Link to comment

So you can do a lot of...

Hmm... perhaps he was dictating?

----------------------------------------

I agree there are times when Labview generics would be useful. How would you implement them in a way that maintains Labview's "everything is data" paradigm?

Link to comment

I agree there are times when Labview generics would be useful. How would you implement them in a way that maintains Labview's "everything is data" paradigm?

I guess you are referring to the fact that VIs using generic types need to be runnable. That is indeed a good question. What is needed is a default value for generic types used as type parameters. For unrestricted type parameters this should be rather easy; there is no need for any particular value. So unrestricted type parameters would behave the same as LabVIEW Object class. The default value would equal to the top most class in the class hierarchy. The default value of a generic type with a single upper bound can be represented with the default value of the upper bound type itself. The default value of a generic type with a single lower bound can be represented with a default value of the parent class of all other classes, which currently is LabVIEW Object class.

The more challenging is the case when multiple upper bounds have been used for a single generic type. The usage of multiple upper bounds from different branches of class hierarchy is possible only if either mixin classes are supported or interfaces are supported (I prefer mixin classes over interfaces), or there is some other kind of support for multiple inheritance. Let's consider the option of mixin classes which would in a LabVIEW implementation have a default value. Let S be any type that inherits from any two classes U1 and U2. For this to be possible, S needs to be a class in type space defined by direct sum of U1 and U2. The same applies for any generic class T that has upper bounds U1 and U2. So the default value of the generic type parameter T that has upper bounds U1 and U2 would be a direct sum of default value of U1 and U2. In less mathematical terms, the default value of T would be a data structure similar to a two element cluster with default value of class U1 and default value of class U2. For more that two upper bounds, this would generalize as a direct sum of upper bounds {Ui}.

So far we have assumed upper bounds are non-overlapping. That is for any two upper bounds U1 and U2, there is no class Q in the inheritance hierarchy for which Q is a common ancestor for both U1 and U2. The easiest solution would simply not allow this kind of type bounds and also disallow multiple inheritance from common ancestors trough two different inheritance branches, unless of course this ancestor class is LabVIEW Object or similar built-in class. There are other options as well, but they are not relevant for the discussion now.

  • Like 1
Link to comment

I think I see what you are saying.

I think the difficulty is that the type philosphy of a language is generally defined at the languages conception as to whether the reponsibilty of type information should be in the compiler or the run-time environment (or both and how much in each). Those that choose "compiler" or "run-time" are pretty much stuck with it and only the hybrids are capable of moving towards natively supporting one or the other. Thats not say though that strict typed systems are unable to "emulate" dynamic typed systems or vice versa. Its just its not natively supported which is I think is what you are suggesting for LV.

I think also its a personal choice as to what a particular programmer programmer is comfortable with. The arguements are stacked equally on both sides. Dynamic typing has the advantages you have outlined. But it is also a lot easier to shoot yourself in the foot. Even with the polymorphic vis that Labview provides, it is considered good programming style not to use this feature and instead explicity cast it to the receivers expected type for this reason. For that (amongst others), I prefer strictly typed languages and (if there is a choice of equivelents) choose the strictly typed every time since I don't have many feet and theres too many holes already :) .

Link to comment

I think I see what you are saying.

Err.. I think you misunderstood. Sorry, I need to try to be more clear. I am talking about only static i.e. compile time type systems. Forget dynamic languages for now, I am not referring to dynamic type systems. I don't think dynamic type systems fit to LabVIEW dataflow paradigm very well.

In a statically typed language like LabVIEW, type checks are done at compile time. In LabVIEW the type checks are done actually at development time, but it doesn't matter for now. The whole idea of static type checking is to prohibit type mismatch at runtime. This far we both agree.

Now generic programming IS NOT the same thing as dynamic type system. Generic programming can be made 100% type safe using type parameters and compile time type checking with a static type system. All type related bugs can be avoided like currently in LabVIEW. The actual type comes into play when you place a VI that uses generic types on a block diagram. When you connect a wire with a specific type to a input that depends on some type parameter T, the type parameter T gets bound to the type you connected. That fixes the type of all inputs and outputs that depend on this particular type parameter. In dynamic programming, the type of the output terminals would be of type Dynamic. With type parameters the type of the output is fixed to something specific instead.

As an example, consider a method creating a LIFO (last in, first out) stack of anything. In dynamic programming you would have an input of type Dynamic. The output terminal returning the stack would be of type Dynamic as well. In type parameter based generic programming the input would be of generic type T and the output would be of type Stack[T] (stack of items of type T). When you connect say integer I32 to the input of the create stack method, then the output would become fixed to a type Stack[i32] (stack of items of type I32).

Link to comment

Nope. Lost me again :P

Err.. I think you misunderstood. Sorry, I need to try to be more clear. I am talking about only static i.e. compile time type systems. Forget dynamic languages for now, I am not referring to dynamic type systems. I don't think dynamic type systems fit to LabVIEW dataflow paradigm very well.

Indeed. But most of your article was about specifying dynamic types.

In a statically typed language like LabVIEW, type checks are done at compile time. In LabVIEW the type checks are done actually at development time, but it doesn't matter for now. The whole idea of static type checking is to prohibit type mismatch at runtime. This far we both agree.

Well. Except for terminology (I think). I was talking about "Strict" type checking rather than "Static" linking. LV doesn't type check at compile time. It does it before compilation to make sure you don't do anything the compiler will object to. Its more like "syntax" checking.

Now generic programming IS NOT the same thing as dynamic type system. Generic programming can be made 100% type safe using type parameters and compile time type checking with a static type system. All type related bugs can be avoided like currently in LabVIEW.

OK. I was under the impression that you were promoting dynamic type checking rather than "Generic Programming" (which I've never heard of before) since most of the article was about types. Re- reading it for the 8th time and in that context, your comments in this thread make a bit more sense (hopefully :P ).

The actual type comes into play when you place a VI that uses generic types on a block diagram. When you connect a wire with a specific type to a input that depends on some type parameter T, the type parameter T gets bound to the type you connected. That fixes the type of all inputs and outputs that depend on this particular type parameter. In dynamic programming, the type of the output terminals would be of type Dynamic. With type parameters the type of the output is fixed to something specific instead.

We can already wire a number of types to (taking your example) an add function. Is the only concern that it fixes the output to the same type? Are you saying that we should also be able to wire the inputs of an add function to (say) 2 double numbers and wire directly to a string without converting first?

As an example, consider a method creating a LIFO (last in, first out) stack of anything. In dynamic programming you would have an input of type Dynamic. The output terminal returning the stack would be of type Dynamic as well. In type parameter based generic programming the input would be of generic type T and the output would be of type Stack[T] (stack of items of type T). When you connect say integer I32 to the input of the create stack method, then the output would become fixed to a type Stack[i32] (stack of items of type I32).

Hmmm. Obviously not what I said previously then.

This is what is really confusing :unsure: Don't most of the primitives do this already (numeric functions, comparisons etc)?

Link to comment

I guess you are referring to the fact that VIs using generic types need to be runnable.

That's part of it, but more importantly how do you create a generic type in Labview? What does it look like?

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';  }

I don't have much experience with generics and the syntax is a little confusing, but here are a couple obstacles I see:

  • In your example code you have the variables T and U representing arbitrary data. Labview doesn't have variables, only data. To implement the above code in Labview you have to create some sort of data construct representing T and U that you can drop on a front panel. How do you create a data type that, by definition, has no type?
  • The first two lines in the class define the relationship between T and U based on their type information. How do you establish that relationship in Labview? It can't be in a vi; the relationship has to be enforced at compile-time, not at run-time.

Thats not say though that strict typed systems are unable to "emulate" dynamic typed systems or vice versa... Dynamic typing has the advantages you have outlined. But it is also a lot easier to shoot yourself in the foot.

As I understand it, generics emulate some characteristics of dynamic typed systems by automatically generating the correctly typed code at compile time. Generics in a statically typed language still produce statically typed code. Violating the generic's defined typing rules results in a compiler error, which translates into a broken wire in Labview.

Even with the polymorphic vis that Labview provides, it is considered good programming style not to use this feature and instead explicity cast it to the receivers expected type for this reason.

It is!? blink.gif I've never heard that and strongly disagree with it. (Until someone can convince me otherwise.) It seems plain silly not to use them in places where they are appropriate.

Link to comment

I see I'm a little behind the curve here. Oh well...

LV doesn't type check at compile time. It does it before compilation to make sure you don't do anything the compiler will object to. Its more like "syntax" checking.

Actually Labview does type check at compile time. Labview is always compiling your code behind the scenes while you're in the dev environment. Ergo, in Labview compile time == dev time. (Compiling is part of the process of building an executable, but building an executable != compiling.)

Are you saying that we should also be able to wire the inputs of an add function to (say) 2 double numbers and wire directly to a string without converting first?

No. I imagine a properly implemented generic vi in Labview would behave in much the same way as a polymorphic vi, except that you wouldn't have to write individual sub vis for each data type you want to support. You create a single vi with 'type placeholders' and Labview automatically creates the correct "sub vi" based on what you connected to the input terminal.

Don't most of the primitives do this already (numeric functions, comparisons etc)?

Some prims do, but we can't create classes or vis that do this. That's what Tomi is asking for.

Edited by Daklu
Link to comment

Actually Labview does type check at compile time. Labview is always compiling your code behind the scenes while you're in the dev environment. Ergo, in Labview compile time == dev time. (Compiling is part of the process of building an executable, but building an executable != compiling.)

I'm no expert on compilers (and I avoid knowing about low level stuff like the plague when I can). But i don't think LV continuously compiles. If I remember correctly, it only does it when you click the "run" button. But that may have changed in recent versions.

No. I imagine a properly implemented generic vi in Labview would behave in much the same way as a polymorphic vi, except that you wouldn't have to write individual sub vis for each data type you want to support. You create a single vi with 'type placeholders' and Labview automatically creates the correct "sub vi" based on what you connected to the input terminal.

So all we are really talking about is a "macro" for creating polymorphic vis?

Some prims do, but we can't create classes or vis that do this. That's what Tomi is asking for.

We can certainly make polymorphic vis (like the read from spreadsheet file.vi). The only difference between the prims is you cant see the source. There may be a technical difference since we all know LV is really just a big API with a graphical front end, but from the users view it is the same. Its the same with XControls and native controls.

Link to comment

I'm no expert on compilers (and I avoid knowing about low level stuff like the plague when I can). But i don't think LV continuously compiles. If I remember correctly, it only does it when you click the "run" button. But that may have changed in recent versions.

My apologies. I assumed you were referring the compiling that takes place when building an executable.

In this post AQ describes the compiling process in LV and compares it to C++. I read it as something that happens continuously, not just when the run button is pressed. I could be wrong though.

So all we are really talking about is a "macro" for creating polymorphic vis?

Ehh... not quite. "Super-macro" might be more accurate. Wikipedia has this to say...

"Both macros and templates [The c++ version of generics - Ed.] are expanded at compile time. Macros are always expanded inline; templates can also be expanded as inline functions when the compiler deems it appropriate. Thus both function-like macros and function templates have no run-time overhead.

However, templates are generally considered an improvement over macros for these purposes. Templates are type-safe. Templates avoid some of the common errors found in code that makes heavy use of function-like macros. Perhaps most importantly, templates were designed to be applicable to much larger problems than macros."

(Tomi has a much better understanding of the differences between c++ templates, type parameters, and other generic implementations. He might have more to say regarding this question.)

We can certainly make polymorphic vis (like the read from spreadsheet file.vi).

Yes, but the difference is with polymorphic vis we have to manually implement the code for any type we want the polymorphic vi to support. With generics we can implement the code for a single "generic method" and Labview will automatically generate the required code for any data type we wire into it.* (Provided the real data type meets the constraints defined by the generic.)

Recently I built a Collection class, which was essentially an attempt to implement .Net style collections. It has the standard methods... Add, Remove, Count, Item, etc. However, to really duplicate .Net collections I'd have to create and implement child classes for every possible combination of native and user-generated data type. Not only is that a lot of duplicate code (since, for example, the code behind every child class' Remove method is identical except for the data type,) but there's an infinite number of combinations. Clearly covering all potential inputs is an impossible task. If generics were available in Labview, I could write the class once and use it anywhere. Less code. Less maintenance. Less headache (presumbably.)

[*This leads me to another question for Tomi. As far as I know generics in other languages don't produce new source code--there is a clear distinction between pre-compiled code and post-compiled code. In Labview that distinction is muddied... in a sense all our block diagrams are post-compiled code. Would the code generated by a generic be saved as a separate vi, in which case the feature behaves almost like a toolkit, or do you create it dynamically on an as-needed basis?]

Well. Its not the only reason. rolleyes.gif It also creates a copy of the data.

Meh... not a good enough reason for me. If you're trying to optimize a tight real-time loop, then yeah... but my apps run on desktop computers on a preemptive multitasking os. I have bigger things to worry about than an extra data copy here and there.

Edited by Daklu
Link to comment

So all we are really talking about is a "macro" for creating polymorphic vis?

Something similar. We are talking about some technique of creating code acting similar to polymorphic VIs, without the need of duplicating the code. But not only that. We are also talking about custom _polymorphic parametrized types_. An example of a polymorphic type in LabVIEW is array. Array can contain any datatype imaginable and array type includes the information of the array element type. Even though LabVIEW has polymorphic parametrized types such as arrays and queues, developers are currently not allowed to develop new such types.

Tomi has a much better understanding of the differences between c++ templates, type parameters, and other generic implementations. He might have more to say regarding this question.

Unlike with C++ templates, with generics the possible runtime types are not known at compile time. Of course the all possible types are known, but that type space is much too large, so a binary code cannot just be generated to all possible types. Generics are not replaced by the exactly typed counterparts at compile time like C++ templates. Instead a generic binary code is generated, or alternatively binary code is generated just-in-time before at execution time.

[*This leads me to another question for Tomi. As far as I know generics in other languages don't produce new source code--there is a clear distinction between pre-compiled code and post-compiled code. In Labview that distinction is muddied... in a sense all our block diagrams are post-compiled code. Would the code generated by a generic be saved as a separate vi, in which case the feature behaves almost like a toolkit, or do you create it dynamically on an as-needed basis?]

As I mentioned above, a generic binary code for generic functions is generated. So for generic classes there would be a single generic class. For generic VIs there would be a single generic VI with a single binary code. As LabVIEW VIs have a state (controls, uninitialized sift registers), this is the only way to implement generics in LabVIEW I can think of. In LVOOP method VIs with dynamic dispatch input terminals, the method call is bind to actual method VI at runtime. In a similar manner in generic programming, the method calls to primitives or other subVIs are bind at runtime depending on the runtime type of the generic. As the type checking is happening at compile time, there always exists a method or primitive to be called. Runtime type checks can never lead to a situation where the generic code cannot be executed.

EDIT: I was checking details of C# implementation of generics. When primitive types like Float and Int are used for type parameter, a separate code is generated at runtime. When class types are used, then code is shared betwen different types. That is List[Float] and List[int] would have their own binary code but List[string] and List[List[Float]] would share the binary code. In all cases, the code is generated at runtime, when first needed.

Link to comment
  • 7 years later...

Yeah VIMs feel like they solve at least some of the features you want in the linked idea.  But in the current implementation I think the math still might have coercion taking place.  NI is investing in VIMs, and I have hopes we'll see something official from NI.  But until then you can make and use VIMs with the understanding that the technology is private, and may change with future version of LabVIEW.

Link to comment

Generics without type parameters don't really solve the main programming problem I want to solve: Create class libraries that can be used with different type data. As a simple example creating a class library for a linked list of data of any type would not be possible with VIMs be cause they couldn't output a type parameterized type "Linked List<T> " where T is the datatype and Linked List is a class having T in the private data. Now T is a type parameter so we need to represent it somehow. VIMs don't provide a representation for type parameters in data structures.

Link to comment
1 hour ago, Tomi Maila said:

VIMs don't provide a representation for type parameters in data structures.

Oh but they could.  Look at my circular buffer XNode example.  It is an XNode but it could still be used as a VIM.  It bundles a DVR with some information, and the data type you want, and then the other functions change the data type of a control or indicator, based on the DVR.  So what you have is an Init function where you define the data type (like obtain queue) and then the read returns that same data type (like dequeue) 

Link to comment
10 hours ago, Tomi Maila said:

Generics without type parameters don't really solve the main programming problem I want to solve: Create class libraries that can be used with different type data. As a simple example creating a class library for a linked list of data of any type would not be possible with VIMs be cause they couldn't output a type parameterized type "Linked List<T> " where T is the datatype and Linked List is a class having T in the private data. Now T is a type parameter so we need to represent it somehow. VIMs don't provide a representation for type parameters in data structures.

Not sure what you are trying to achieve here. The "List" is an example of a class that can be used with different type data (that's what variants are for) and VIMs give you a simple method to "adapt to type".

Link to comment

ShaunR, the List type in your example is not strongly typed but generic. That means the type of the list wire for a list of Strings is not different from the type of the list wire for the list of DBLs. That's the exact problem type parameters would solve. 

Hooovahh, although the data type is strongly typed , the data type of the circular buffer is not encapsulated into a "Circular Buffer of type String" class but instead it exposes the private data to outsiders. Further one cannot create a type definition or a class that one can use in a VI front panel to tell that this VI expects a "Circular Buffer of type DBL" as input and returns a "DBL". 

Link to comment

1le3ta.jpg

But yes your points are valid about the classless nature of my example.  VIMs (which can work better with classes) might open this up to a few more possibilities where XNodes fail.  And as for the UI I had never thought of that, and never thought I needed it, but I admit I could see a few places where that type of functionality could be useful.

Link to comment

Again, we're not talking about a non-strictly-typed language here. All the type checking can be done at compile time. Have you done any programming in F# or another ML derivative? I've mostly seen this in those functional languages. For example, generics allow you to write a sorting algorithm that takes a list, and a comparison function that operates on elements of that list. The compiler verifies that the comparison function matches the list element type, so you can't have a run-time type mismatch. Right now you can't do this in LabVIEW. You can either have your list contain variants, in which case you could get a run-time type mismatch, or you could wrap your data in a class that implements a comparison function, but since LabVIEW doesn't allow implementation of interfaces, this only works if your data class inherits from some Comparable class, which won't work if you actually want to inherit from some other class that might not allow comparison.

I would love to see this in LabVIEW.

  • Like 1
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.