LabVIEW Idea Exchange

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

Have Dynamic Dispatching terminals accept Data Value references of the class or any child class.

Status: New

When you’re making a By Reference LabVIEW Object using a Data Value References (DVRs) the user of your class would need to embed each Dynamic Dispatching VI inside an In Place Element Structure (IPE). Or you have to create wrapper VI for each method but this undermines the advantages of LVOOP Inheritance.
 
The idea is that a DVR containing a LabVIEW Object wired to a Dynamic Dispatching Terminal is equal to calling the Method VI inside the IPE structure like illustrated below.

DVR DynamicDispatch.PNG

Message Edited by Support on 01-15-2010 04:39 PM
12 Comments
AristosQueue (NI)
NI Employee (retired)

There are many problems with this idea. It was explictly rejected during the design for data value references.

 

The core of the problem is that LabVIEW cannot read the user's mind. When you do two method calls in a row on an object, LabVIEW cannot know if you intended to have both of them handled in a single Inplace Element structure or in separate inplace element structures. And because the locking behavior is extremely different between these two, it is far better that LabVIEW not try.

 

Then there's the question of whether you intend that the output object actually be put back into the reference. There are situations where you open the reference, call a method on the object that outputs another object of the same type, and you pass that out of the structure and you put the original unmodified object back into the reference. LabVIEW would usually be right to guess that any output goes back into the reference, but not always. 

 

Then there's the problem of subVIs that want to run even if errors occur. We cannot wrap an implied case structure around those nodes for the error handling. 

 

Not every dynamic dispatch subVI even has error in and error out terminals. Where would you put the error information on a subVI that doesn't have those terminals if the reference passed in was invalid?

 

There are other issues, but those are the big ones.

 

The long and short of it is that you should either leave it to the callers of your functions to decide how long to hold the reference lock and under what conditions (ie. explicitly dropping the Inplace Element Structure) or you should have an API on your class that takes references to the class as inputs --  this second choice should be reserved for extremely simple classes where you can be certain of all the aggregated operations that a user would ever want to have (i.e. every possible subset of operations that the user would want to do in a single lock-modify-unlock sequence has to be available through your API to avoid race conditions where the section of code Alpha executes the first modification and then a parallel piece of code Beta executes a completely different modification before Alpha gets a chance to do its second modification, which may leave the object in a bad state). 

KvZ
Active Participant
Active Participant

I agree there are some caveats implementing this, may be more then I realized.
 
I don't see way LabVIEW needs to read the users mind. When calling two methods is a row the default behavior lock - release, lock - release should be fine. If the user likes to maintain the lock for both calls all you need to do is just manuals insert a IPE structure around both method and you are done. Since the methods in this case use the class instead of the reference no IPE structure is "inserted" and locking is maintained for both calls. The method controls the object going back into the reference by wiring this to the Dynamic Dispatching output terminal. As far as I know the IPE Structure always requires you to wire something to the write note.
 
I'm not sure what kind of errors will be generated by the IPE read and write node. The only one that I can think right now is a Asynchronous Delete of the reference. But this could be solved by only allowing reference to be wired only when the Method has error handling.  SubVI that need to run even in a error state can then just ignore error in on the VI, or what I usually do in such a case is merge "error in" with the subVI's error chain at the end of the SubVI code, giving "error in" the highest priority in the merge.
 
It would probably be a good idea that this behaviour can be controlled from the Item settings of the class. This way the developer can indicate how he wants LabVIEW to deal with this method. This would also indicate that the developer is aware of this potential issues. It would also be possible to only allow this setting to be made when error handling is available on the VI.
 
IMHO the ability of dispatching on DVRs will be expand the LVOOP functionality significantly enabling powerful designs. It will maintain inheritance and thus saves the developer from maintaining a ByRef API for each child class. You could always bring everything back to one API using the parrent VI and up casting the objects when required but the casting code will probably make the API unnecessary complex.

Race conditions will probably pop-up here and there but is this because LabVIEW allows dispatching on DVRs or bad Class design? None OO enforcing programming languages can prevent race conditions in this case.

AristosQueue (NI)
NI Employee (retired)

> When calling two methods is a row the default behavior lock - release, lock - release should be fine.

 

I could say that "when calling two methods in a row, the default behavior lock-do-both-operations-release should be fine" with equal probability of being right, and users who didn't want that could compensate by dropping two explicit IPE structures. In fact, I could say the same thing about code that has a method call, then some unrelated code, then a method call -- perhaps the reference needs to be held through that entire sequence, so why shouldn't LV just decide to unlock the reference only once per VI and hold it until the VI returns? I know many VIs for which that would be a reasonable default. The point is that a race condition is so difficult to debug -- indeed, it is hard sometimes to even know such a bug exists until code is already out in the field -- that having a situation where either the API designer or the API user must consciously think aobut "do these operations need to be paired or not" seems prudent. Having LabVIEW pick a default, even if we can determine which one is most common, is a bad idea simply because of how problematic those other cases are. 

 

Picking any once case would lead, I believe, to this situation:

Programmer A: "Can I do this?"

Programmer B: "Sometimes, but not always. It's better if you just never do it so we don't have to worry about it."

I do not want references to become a feature like global VIs -- newbies use them because they're so easy to set up and do not require understanding the intricacies of data storage, but experienced users live in fear of them and the performance problems/race conditions they create. I believe the same situation would arise with this feature, with many users constantly asking "Do I need an IPE here or not?" and lots of discussion on forums, leading eventually to experienced programmers answering "I don't know, but I always put one in just in case I do." 

 

> I'm not sure what kind of errors will be generated by the IPE read and write node.

 

The only one is "invalid reference". The Close Reference node  actually waits for all current IPE structures, so there is no async delete to worry about, but you can have a Not A Refnum or a refnum that has already been Close, or a refnum that was created by someone casting some random integer into a refnum -- any case where the unlock tries to unlock an invalid refnum creates an error that needs reporting. 

 

But this could be solved by only allowing reference to be wired only when the Method has error handling.

<snip>

> It would probably be a good idea that this behaviour can be controlled from the Item settings of the class.

 

These two suggestions are the types of things that create confusion in a programming language. "Sometimes I get a broken wire, but sometimes I don't, and the distinction between them is the presence or absence of this completely unrelated terminal elsewhere on the conpane." If we add a per class config, I bet very few classes would ever set it because most programmers would see it as just making the API harder to use as opposed to seeing it as something key to the design of their class. 

 

> It will maintain inheritance and thus saves the developer from maintaining a ByRef API for each child class.

You don't have to maintain a by-ref API for each descendant class. Write a method on the parent class that takes a reference to the parent class. You can pass a reference to a child class to that terminal.

The thing you have to decide for each class today is whether you intend this class to be used predominantly by value or predominantly by reference and then build the appropriate API for that class. The place where you will exhaust yourself is trying to write a class that you intend to be used both by value and by reference. But if you pick a paradigm and design for it, I *think* you'll avoid most of the difficulties. Having said that...

> IMHO the ability of dispatching on DVRs will be expand the LVOOP functionality significantly enabling powerful designs.

Although I am doubtful, I concede you might be right. A few years down the road, we'll check on this and see what has developed.  I'm hypothesizing a lot in this post, but to a large degree, that's my job -- to add features that will make LV more powerful without making it harder to use, and to do that, I have to judge from the usage patterns that I observe, both in LV today and in other programming languages. At the moment, the design is what it is, and I've laid out the reasons it works the way it works. Please keep using it and feeding back to us the issues you encounter, and if it becomes clear that the safety-vs-power tradeoff isn't worth it with the current design, we'll look into adjusting it.
KvZ
Active Participant
Active Participant
Thanks, it definitely make a difference considering this form point of view of LabVIEW and all it's users or my own (limited 😉 ) view of projects and experience with text based OOP.
 
> The place where you will exhaust yourself is trying to write a class that you intend to be used both by value and by reference. But if you pick a paradigm and design for it, I *think* you'll avoid most of the difficulties.
 
I agree, maybe I need to re-think my approaches and try and resist to fall back on my past OOP experience. It at least requires further investigation.
 
It will be very interesting to see how this will develop.
 
- Karsten
Nate_Moehring
Active Participant

> The place where you will exhaust yourself is trying to write a class that you intend to be used both by value and by reference. But if you pick a paradigm and design for it, I *think* you'll avoid most of the difficulties.

 

This is where I fundamentally disagree.  To me, based on my experienced with C++, Java, and VB, (albeit relatively limited to you A

ristos), the author of a class should have little to no concern about how objects of the class will be used, By Value or By Reference.  Calling a method on an object should be exactly the same as calling a method on a reference to an object.  The difference between a '.' and a '->' is acceptable because this makes the code in the consumer more self-documenting, however less desirable because it also complicates the syntax increases the chance of compiler error in a textual language, however graphical programming greatly alleviates syntax errors.  But the take-away point is the class itself shouldn't care how it will be used in the future.

 

Currently, implementing a LabVOOP class that supports a By Reference interface requires multiple tips and tricks.  I think this is unfortunate.  Some of it might be understandable given that G is a By Value paradigm, but I don't think G precludes the use of references, and in fact references have been a part of LabVIEW for many years in varying flavors.

Nate Moehring

SDG
AristosQueue (NI)
NI Employee (retired)

Nate: C++ programmers are generally intimately aware of how the class will be used -- from providing zero public constructors and forcing the use of static construction for singletons, to mapping to lookup tables, to inclusion of internal mutexes because of the expectation of parallel access. JAVA and C# programmers have no choice in the matter because all classes are by reference. VB is by reference too, though I'm lots less familiar with its options.

Charles_CLA
Active Participant

I am particularly interested in this idea after writing scripting code to create ByRef wrappers for all of my Dynamic Dispatch code. I don't really understand the reasoning for turning it down. The compiler should not try to be smarter than it needs to be. This is what I would see as a good solution, would love to see more discussion on it:

  1. Allow the Dynamic Dispatch VIs to accept either a ByVal Class or a ByRef Class
  2. Require error terminals
  3. If the user passes a ByRef Class, lock and unlock within the VI
  4. If the user wishes to have a single lock with mutiple operations, they use an IPES around the code that they wish to run in a single lock
Charles Chickering
Architecture is art with rules.

...and the rules are more like guidelines
Charles_CLA
Active Participant

The more I dig into this, the more I see precedent. Properties of a class that are made available via the property node already accept the class input or the DVR'd class input, they require error terminals and presumably creates the IPES around the access when passing the DVR class.

Charles Chickering
Architecture is art with rules.

...and the rules are more like guidelines
AristosQueue (NI)
NI Employee (retired)

I opposed implementing this feature for the property node, also. It's one of the places where using a single propety node vs using two property nodes suddenly makes a big difference. Even WORSE: the property node leads to situations where people read a property, modify the value, then write it back with another property node. Yeah, this is a danger with any by reference type. But it doesn't *have* to be a danger with any by reference type -- if the class is designed to be used by reference or you make people open up the reference themselves to call methods. If you watch people program, you'll note that they tend to only drop one IPE node, do all their work -- read, modify and write -- within that one node. They don't drop separate IPE nodes. That's a good pattern. With the property node accepting references, they don't bother to lock the reference across the entire read/modify/write operation. Indeed, if you're creating a class intended to be used by reference, you probably *should not have* property write methods for any property that may be incremented, decremented, appended, or otherwise modified. Instead you should have increment, decrement, append, etc as individual methods so that they may be atomically performed. This is one of those "design for the intended use case" issues I mention earlier in the thread.

 

The property node was allowed to take the references in order to make some hardware API teams happy. I wouldn't call it a precedent.

 

Oh, and the required error terminals? Those are a substantial performance hit for property accessors. You can find various posts from me over the last year explaining in detail. I advise not using error terminals whereever possible -- the LV compiler can substantially optimize code that has no case structures in it (because it knows the code will always execute) that it cannot do with conditional code. You lose that with accessing through a DVR -- no memory inplaceness whatsoever.

 

LabVIEW may in the future do something to improve something like by-reference programming, but I swear to you and all who read this thread: automating the decisions about reference handling by teaching LabVIEW to automatically add IPEs behind the scenes and other tricks to make handling references easier can only lead to inefficient and buggy code. This is why you should try to break yourself from using by reference data. Really. I mean it.

 

Thus endeth today's homily. You may now return to your sinful ways. 😉

fabric
Active Participant

AQ: It feels a bit like You vs. The Rest Of The World here... Smiley Tongue

 

Yes, you make some resonable arguments for not implementing this idea, and yet none of them are technical brick walls. Fundamentally, I disagree with the position "this will be abused so let's not do it". DVRs are anyway an advanced feature and people would do well to read the help before using them.

 

This idea is both possible and powerful. PLEASE, give us enough rope to hang ourselves!