LabVIEW Idea Exchange

cancel
Showing results for 
Search instead for 
Did you mean: 
otto__

Multiple dispatch / Function overloading

Status: New

I like compile time type safety checks. I dislike using variants. Occasionally, and increasingly more often, I find myself going to great lengths to provide compile time type safety. At a point, the type check gets lost in the inheritance hierarchy and I am back to depending on runtime checks for errors. It's not uncommon for me to have a class method that needs to "just work" across the bulk of the base types, but it sure is a pain to make wrapper classes, static inlined methods, and a nasty polymorphic .vi to mimic this behavior. Perhaps I am ignorant to some features of LV (do malleable vi's fit in here somewhere?), but multiple dispatch/function overloading sure seems like the silver bullet for this issue without messy inheritance trees.

 

I'm open to discussion on alternatives. This "problem" has come up in a couple of recent projects of mine, and I always feel dirty using a variant or making a static API for a class that ought to be extensible.

10 Comments
fefepeto_kb
Member

I have also been thinking a lot on this problem. I don't think there is a clean solution in LabVIEW, at least I did not come across one that I would consider simple and elegant.

 

But, what could be done, in a not simple, but still manageable way would be to use static methods in extendable classes with variant input where you expect the type adaptability. Then use malleable VIs to implement the type specialization and handle the correct data type.

My example would be that multiple measurements are made, and the values must be stored in an array. But the data type for one measurement is double and comes from a DAQ device, while the other is an integer and comes from an XNET device. In this case, the malleable VI can be used to adapt to the correct class as long as both classes have the same inputs and outputs for the method that is called the same, like Set Value.vim. A neat detail is, that the classes don't have to be part of the same hierarchy for the VIMs to work, but because I mentioned an array, they must have a common parent in this example.

 

To be a bit more detailed here, since I haven't yet tried this implementation, the Set value.vim could have static dispatch class inputs and outputs (static is a must for VIMs), then a variant input for the Value (don't worry about type conversion, the type specialization structure will adapt the code without data conversion) and the error wires. Obviously, this won't be useable as a property node, or at least I don't know if it should work, but the same method could be used and the correct implementation will be called. The problem still: in this case the VIM needs the correct class to be on the input wire when called during run time, meaning that the dynamic dispatch part is thrown out of the window.

This would mean that a case structure is needed around the place where you want to call the method based on the object that you are about to update. And this is the part I'm not comfortable about, therefore I never implemented this solution.

wiebe@CARYA
Knight of NI

@otto__ wrote:

Perhaps I am ignorant to some features of LV (do malleable vi's fit in here somewhere?), but multiple dispatch/function overloading sure seems like the silver bullet for this issue without messy inheritance trees.


Yes. You can give the parent a .vim that, in a type specialization structure, calls the appropriate method (w/ DD).

 

This new .vim might be a bigger problem, not a solution (YMMV)...

 

@fefepeto_kb wrote:

But the data type for one measurement is double and comes from a DAQ device, while the other is an integer and comes from an XNET device. In this case, the malleable VI can be used to adapt to the correct class as long as both classes have the same inputs and outputs for the method that is called the same, like Set Value.vim


It's actually more relaxed (less strict).

 

The CPs don't have to match! They don't even need to be compatible.

 

A VI will use the class's method iff the new method's CP doesn't break the .vim.

 

(EDIT: In hindsight this might be what you mend with "the same inputs and outputs". Not exactly the same, but inputs and outputs on the same positions, iff they're wired). 

 

In this example, you could make a method Get Value.vi, and give each child a different return value. Of course, Get Value can't be in the parent or you'd get an illegal overwrite.

 

Back to the idea (this has come up before):

The naming of the VIs would be a problem. Overwriting is based on VI name, so how would naming work with multiple overloads?

 

raphschru
Active Participant

@otto__ wrote:

I like compile time type safety checks. I dislike using variants.


Fair enough, I do too 🙂.

 


@otto__ wrote:

Occasionally, and increasingly more often, I find myself going to great lengths to provide compile time type safety. At a point, the type check gets lost in the inheritance hierarchy and I am back to depending on runtime checks for errors. It's not uncommon for me to have a class method that needs to "just work" across the bulk of the base types, but it sure is a pain to make wrapper classes, static inlined methods, and a nasty polymorphic .vi to mimic this behavior.


If I understood correctly, with the same dynamic dispatch method, you want multiple versions with different inputs/outputs?

You could have a protected-scoped dynamic dispatch method with generic inputs/outputs, which is wrapped by a public malleable VI from the base class. With Type Specialization Structures and type assertions, you can limit the set of allowed types. The only downside with public malleable VIs is that they do not allow the library to be compiled in a Packed Project Library (PPL). Polymorphic VIs are an alternative to malleable VIs too.

 

This might be helpful to give an actual example (e.g. screenshots) where you have the pain point you describe.

 

Regards,

Raphaël.

avogadro5
Active Participant

A lot of the times I've run into this family of issues I've felt it could be fixed with a malleable class so that idea might be a more concrete feature that could be implemented.

 

Lacking a malleable class I usually fall back to something like a cluster of a group of related types, for instance if you want to represent a "variable" that might be a cluster where the 1st element is an object representing metadata about the variable (name, scope...) and the 2nd element is the datatype/value. Then you can write a family of .vims designed to work with that cluster that accept any type for the 2nd element. The thing a malleable class would help with here is enforcing usage for the entire lifetime of the class wire: what I just described can't do anything about using the same metadata with different variable types so it isn't truly safe.

wiebe@CARYA
Knight of NI

@avogadro5 :

I don't think that will give you compile time type checking. You're type casting at runtime. Type overloading would give us compile time type checking.

 

I think technically you could get compile time type checking with interfaces.

 

But the costs are high and the gain is practically non existed. AKA impractical.

 

You might as well forget about polymorphism altogether and make static methods that return specific types... At least that's simple.

avogadro5
Active Participant

 I don't really follow; this is the pattern I'm talking about in cluster form, my understanding of the malleable class idea is it would just let me move that data value reference into the class wire while allowing the VIMs to adapt correctly. The class wire would then have the static type class<DVR<T>> at compile time.

avogadro5_2-1751387895254.png

 

This example would let me create statically typed DVR wrappers that have limited scope if the DVR could be private.

 

otto__
Member

I am providing a stripped down current value table (CVT) implementation as an example of where multiple dispatch would help from my own travels. I migrated the code I have into a git repo real quick, so apologies for any unexpected issues. Any generic test or read .vi at the library level should mostly be ignored, I am assuming those that care enough will inspect the class methods and structure.

 

https://github.com/ottoandr42/CVT.git

 

There are two components that make this thing work: 1. A CVT class that wraps a DVR of a map of tag name (string) to CVT item (an interface or abstract class), and the corresponding CVT item interface .lvclass. The CVT item is yet another wrapper, containing a type-specific notifier reference (this is a lossy, broadcast, "it's my data and I want it now!" type of CVT, useful for almost-real-time stuff). For any type you want to be held in the CVT (which is really just a registry and an API at the same time), you need to provide a concrete class that wraps that data type. What I (and we, as LV users) are unable to do, is put a "read" or "get" method in the base/abstract class, because the output data type depends on the concrete implementation, even though every CVT item ought to have this method (and will, if we do a thorough job making each concrete class... but this isn't enforced at the top level, hence the loss of compile time checks).

 

From a developer's perspective, I just want to plop down a "read' or "cached read" method from the CVT class that expects both a tag name and tag type and outputs the data for that tag as the same type as the provided tag type. The cached read would not require the tag name or a registry lookup (refer to the provided example). Very similar to how the "get variant attribute" method works, at face level. To allow this interaction in LabVIEW, as far as I know, you need to have a set of static methods in the CVT class that operate on each concrete CVT item, calling its corresponding "get" method (mentioned in the above paragraph), and then assemble a polymorphic .vi that works off of each of those static methods. A good deal of setup is involved if you want this CVT to work for first-class objects that your team or organization is using. Function overloading would allow an arbitrary read on an arbitrary CVT item (if you had cached access to one), and/or it would reduce the amount of static methods you needed to craft by hand to enable a polymorphic VI to provide a convenient API for developers.

 

Another area I have battled something very similar to this (yearning for the compile time type checking fjords) is attempting to make a more generic message data class to integrate into something similar-but-different than actor framework without causing an explosion in the number of classes required and avoiding the (dreaded by me) to-more-specific-class node (it feels the same as variant-to-data). The visitor pattern and dependency injection can seriously upgrade actor framework's messaging scheme, but there is still opportunity to reduce boilerplate code if multiple dispatch were a thing.

 

Could reflection apply to some of these situations? My understanding is weak, but it feels like it would still push type checks to runtime but potentially help with the "don't repeat yourself" principle.

wiebe@CARYA
Knight of NI

 


@otto__ wrote:

 I don't really follow; this is the pattern I'm talking about in cluster form, my understanding of the malleable class idea is it would just let me move that data value reference into the class wire while allowing the VIMs to adapt correctly. The class wire would then have the static type class<DVR<T>> at compile time.

wiebeCARYA_0-1751469279738.png

This example would let me create statically typed DVR wrappers that have limited scope if the DVR could be private.


But now you' have a cluster in stead of a class, so you'll loose polymorphism?

avogadro5
Active Participant

No actually the polymorphism part is there, malleable class would just make it a bit safer by enforcing consistent usage (can't cheat by bundling in a different DVR type later).

 

Regarding the example provided, I really think the existing malleable functionality means you don't need to use polymorphic VIs or define specific VIs for each type.

 

 

avogadro5_6-1751644730497.png

 

avogadro5_1-1751644322988.png

 

Malleable classes would also be able to replace the pattern of making a new class per type needed, but if you don't mind losing encapsulation you can just use a raw notifier wire and that should be adaptable.

 

Now the provided example isn't "safe" for all definitions for instance there's potential for trying to read a double as a Boolean and getting a type cast error in other words the CVT is not type safe. However I think that's an unavoidable consequence of using a generic data structure to contain data of variable types and trying to get specific types back out -- not really a LabVIEW thing.

Intaris
Proven Zealot

Malleable classes you say?

Old idea HERE.