Jump to content

Bunch of suggestions for LVOOP


Recommended Posts

Hi,

 

following some suggestions for improvement of LVOOP which i'd like to see in LVOOP in a future version.

 

Please note that all those questions refer to an architect level!

 

1. Constructor:

LV enables the programmer to name a VI equally to the class the VI contains. So it is possible to generate a "constructor", even though we don't necessarily encourage the user to do so. See attached example for this.

The idea is to create a "constructor" VI which creates an object and calls required initialization routines internally.

An example would be configuring an instrument for a specific HAL class.

An often used feature for such constructors is "overloading", so having different sets of parameters while keeping the same function name. Currently this is not supported in LV.

What do you think about this feature when talking about constructor VIs as explained above?

2. Destructor:

This is tricky since most OOP guys would expect this to destroy the object, which in case of LV is obviously most often not the case.

Still, it could be used for de-initialization for the user of the class. See attached example for this.

3. Polymorphism/Inheritance:

In some cases of OOP, it would be beneficial to have polymorphism for class functions, sometimes directly connected to inheritance.

Small example:

Class "DataAcquisition" defines general IO functions. Class "AI" inherits from DataAcquisition, overriding all AI functions. Polymorphism should be used for DataOut since Data could be a waveform, a 2D array, ....

Additionally, polymorphism could be used for inheritance directly if parameter types depend on the specific class. E.g. aggregation collects different class objects.

4. Singleton:

One can create a singleton as seen in attached example or on https://decibel.ni.com/content/docs/DOC-13462.

The problem i got with this singleton pattern is that the object within the DVR (the singleton) cannot be used for dispatching, hence no inheritance override possible. In order to get this to work would require the following change:

The connector pane terminal of type "LV Object" and "DVR to LV Object" shall be configurable to "Dynamic Dispatch Input (Required)".

Currently, this option is only working for "LV Object".

5. Overloading:

As already stated in point 1., overloading would be a nice feature for constructor VIs.

Adding to that, overloading could be a nice feature for inheritance as well. So the override function can add (not remove!) additional parameters without the need to define a generic data container (e.g. variant) in the ancestor class.

 

I know that all features, except 4. Singleton, can be solved differently, best done by appropriate wrappers. But i think that some developers could profit from LV opening up to more a common OOP approach used by other OOP languages.

 

@NI R&D: If some of these are already on schedule, please let me know ;)

@All: Please discuss!

 

thanks,

Norbert

Singleton.zip

ClassConstructor_Destructor.zip

Link to comment

I may be missing something, as I haven’t used other OOP languages, but can't we already create “constructors” and “destructors” and get polymorphism through polymorphic VIs?  Polymorphic VIs could do with an upgrade to interface more nicely with dynamic dispatch methods, but otherwise what are we missing?  “Overloading” is not interesting to me because I don’t specify functions by typing their names.

 

— James

 

BTW, your “Singleton” example isn’t a singleton; I can easily call the constructor multiple times and make multiple instances of the class.  Also, I didn’t understand the “DVR cannot be used for dispatching” issue; I tried making a child class with an override and it worked fine.

Link to comment

1) I'd call it "Initialize.vi" instead of "constructor" for most classes. Save "constructor" for a by-reference class template. You could go ahead and create a scripting tool to create this VI for you. There's nothing special about this VI, and nothing needs to be added to the language to allow you to write one. See white paper for more discussion.

2) No for most classes. Yes for a by-reference class template.

3) There are about six features I can think of off the top of my head that have something to do with what you're asking for. The only one that I think is a good idea -- and is a very good idea -- is output covariance. http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29

4) Check out this post: http://forums.ni.com/t5/LabVIEW/My-way-of-hiding-implementation-of-an-API/m-p/2416536/highlight/true#M746202 for 18+ different ways of coding reference type architectures, many of which are singletons. See if any of them pique your fancy.

And your request makes no sense -- in a Singleton, there is one and only one instance. There's no need for inheritance dynamic dispatch on any public function because there's one and only one object. Put all your dynamic dispatch VIs in protected scope.

5) No. See white paper.

Link to comment

If I can summarize this issue -- hopefully correctly! -- it seems that you essentially want LV to be a "standard" byref implementation of OOP.  It isn't.  It's a byval implementation but, as AQ and djdpowell  point out, there are numerous ways to use it to effect the same kinds of programs, they're just not "pure" classic byref OOP.

 

As AQ suggested, read the white paper: it will help clarify this issue quite a bit for you.

Link to comment

The only one of these that I'm on board for would be constructors.

 

Although it is possible to create a VI that is called something like "initialize.vi" or even "classname.vi" there is no way to guarantee that someone calls it. They can still just drop a class constant. It's possible to enforce something like this if you use a by reference scheme (using the "Restrict References of this class....." checkbox) but then you're doing things by reference. If there was a checkbox that made it illegal to drop a class constant (except for in the class's methods) on the block diagram, then we'd be able to enforce this rule.

 

Since we're adding OO features, my vote would be for abstract classes, interfaces and final methods/classes (I don't know the generic word for this).

  • Like 1
Link to comment
Since we're adding OO features, my vote would be for abstract classes, interfaces and final methods/classes (I don't know the generic word for this).

 

Abstract classes and final methods are supported, a static dispatch VI is very similar to a final method. But interfaces would be really nice.

Another idea: Duck typing. If we could cast between objects that provide the same public methods but are otherwise unrelated we wouldn't even need interfaces :-)

Link to comment
Abstract classes and final methods are supported, a static dispatch VI is very similar to a final method. But interfaces would be really nice.

Another idea: Duck typing. If we could cast between objects that provide the same public methods but are otherwise unrelated we wouldn't even need interfaces :-)

 

They aren't.

 

Abstract Classes: There's no way to say "you're not allowed to drop this class by it's self". Maybe that same checkbox that i suggested for constructors would allow this. Then again, you'd also need a way to make sure that a default value for a tunnel never created a new instance of my abstract class (i'm sure there are a few other special cases that would need to be addressed)

 

Final methods: Static methods are close. If my class has a method that I don't want overriden, i just make it static. However, if my parent(parent.lvclass) has method "doSomething.vi" and it is dynamic dispatch, my new class(child.lvclass) can override. Let's say however, that I do not want anything that inherits from child.lvclass to be able to override "doSomething.vi". In child.lvclass i would mark "doSomething.vi" as final http://stackoverflow.com/questions/797530/c-sharp-is-it-possible-to-mark-overriden-method-as-final

 

I'll vote for interfaces way before i'll vote for duck typing.

Link to comment
BTW, your “Singleton” example isn’t a singleton; I can easily call the constructor multiple times and make multiple instances of the class.  Also, I didn’t understand the “DVR cannot be used for dispatching” issue; I tried making a child class with an override and it worked fine.

James,

 

you are correct. I took the example from the NI community page i linked in the post. Obviously, the page got removed, at least the link is not valid anymore. So i would expect the code also to be removed. Current examples for singletons look different, which really work as a singleton. Sorry for this confusion.

Regarding polymorphism of OOP functions: Of course you are correct that one can use LV polymorphism to implement this. But this only works for class specific functions. So the "constructor" (Initialiize function) is a bad example for this. Therefore, i included feedback point 3....

 

1) I'd call it "Initialize.vi" instead of "constructor" for most classes. Save "constructor" for a by-reference class template. You could go ahead and create a scripting tool to create this VI for you. There's nothing special about this VI, and nothing needs to be added to the language to allow you to write one. See white paper for more discussion.

2) No for most classes. Yes for a by-reference class template.

3) There are about six features I can think of off the top of my head that have something to do with what you're asking for. The only one that I think is a good idea -- and is a very good idea -- is output covariance. http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29

4) Check out this post: http://forums.ni.com/t5/LabVIEW/My-way-of-hiding-implementation-of-an-API/m-p/2416536/highlight/true#M746202 for 18+ different ways of coding reference type architectures, many of which are singletons. See if any of them pique your fancy.

And your request makes no sense -- in a Singleton, there is one and only one instance. There's no need for inheritance dynamic dispatch on any public function because there's one and only one object. Put all your dynamic dispatch VIs in protected scope.

5) No. See white paper.

Thanks for the reply, AQ. But i am honestly a little disappointed by it.

As my inital post indicated, i am well aware that most of the points are already possible by using an overelaborate way of implementation. I wanted to pass feedback to LV OOP R&D for improving the status quo, not discussing possible "status quo approaches".

Common OO-languages provide two approaches for flexible functions (like the constructor) which include enables the developer to work as easy as possible:

a) Overloading:

This enables a very specific setup of equally named functions in a class with a specifc set of parameters per implementation. So datatypes can be provided specifically. Overloading is incorporated in the language, hence creating a wrapper is not necessary.

b) Using felxible datatypes:

Most often, variants are used for this.

Advantage: i have only a single function (unique name) which provides all parameters, mandatory or optional as required.

Disadvantage: The caller, so the user, has to know how to unwrap the data for return values. ActiveX is very prominent example on this approach and i very much dislike it as you require additional documentation on the function prototypes on how to wrap/unwrap parameter data (which often lacks for APIs)

 

In LVOOP, one can only work with manually implemented wrappers or by choosing different approaches for implementation. YES, you can do it, but this often seem overelaborate.

 

Especially the necessity to write a wrapper for dynamic dispatching when using DVRs seems overelaborate as it is always the same approach. So it would be nice if LV itself would already handle this and supply the user with dynamic dispatching DVR terminals just like with class wires.

 

Also, i know that not every suggestion is valuable for every programmer. These are just feedback points which i collected during discussion with different LVOOP users (or newcomers) which i find valuable as additional/modified functionality.

 

Norbert

Link to comment

Essentially, i refer very much to this idea on the NI Idea Exchange Forum:

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

 

Reading the comments, especially yours, AQ, you make valid points. True.

But on the other hand, LV could provide a default behavior for handling object references in dynamic dispatching, naturally on a per-function-call-basis (speaking of locks).

Why is that?

I would expect this behavior to match the majority of use-cases if a LVOOP developer choses to use DVRs (by reference passing).

And, the most important thing:

This basic functionality (which is then incorporated) does not prohibit different approaches just as the stated exceptions like locking a set of functions (instead on a per-function-call basis) or passing the reference after possible modification instead of the original one as copy (as most examples you linked obviously do) are still implementable just like nowadays.

 

You are correct that given the fact that depending on the language we have by reference vs. by value calls. And that LV provides BOTH approaches.

What i do dislike is that the by reference way is very worksome and requires well defined wrappers for re-usability.

 

Norbert

Edited by Norbert_B
Link to comment
But on the other hand, LV could provide a default behavior for handling object references in dynamic dispatching, naturally on a per-function-call-basis (speaking of locks).

Why is that?

I would expect this behavior to match the majority of use-cases if a LVOOP developer choses to use DVRs (by reference passing).

I would *not* expect that behavior to match the majority of use cases, and I've actually been looking at classes that users submit to me for which ones would work like that. Take the very simple case of the data accessor VIs -- when folks lobbied very hard for the Property node to support DVRs of classes, they insisted that the single lock should be held for the duration of all the properties being set within the node. When I look at more complex setter functions -- ones that require parameters and thus cannot be used within the property node -- they also tend to need to hold the lock for a block of operations. Also, most classes written as by-value classes will have get and set functions but no modify function, and once people start having DVRs, they expect to read a value, increment or otherwise modify that value, and then set it back. They do not necessarily think about the need to drop an IPE node to hold the lock across the function set.

 

In point of fact, it appears to me to be far MORE common that if you are calling multiple methods in a row, that the lock needs to be held across those methods. The problem is that there might be computation operations inserted between those nodes, so LV could never be programmed to interpret the VI and figure out where the IPEs need to go, short of having an artificially intelligent compiler. It gets worse if those methods are in subVIs -- I'll leave it to readers to play with all the ways that the locks cannot be intuited.

 

When a class is written as a by value class and is then stored in a DVR, it is necessary for the class to transition across a barrier into the by value world, be modified in whatever operations it has, and then be restored to the DVR. There's no way the compiler can *ever* (short of AI) do this for you.

 

When a class is written as a by reference class, then the methods are added by the original developer to support all the modification operations that are necessary -- with the then limitation that the user of the class cannot compensate for any failures on the part of the author without getting some Semaphore refnums involved. (If you don't understand this, try to write an Enqueue Mutliple Items VI for the LabVIEW queues that guarantees that if two Enqueue Multiple calls are made in parallel that all the items from each call are added to the queue in a single block. You'll find you have to package the queue refnum with a semaphore refnum AND you have to prevent the queue refnum from ever being exposed on its own for a plain Enqueue Element call -- you have to wrap every single existing primitive so that you acquire the Semaphore first.)

 

What i do dislike is that the by reference way is very worksome and requires well defined wrappers for re-usability.

Unfortunately, I do not believe this is a shortcoming of LabVIEW. I believe this is a fundamental limitation of any parallel by-reference architecture in any language. The hoops that you have to jump through to make references parallel safe in C# or C++ or JAVA are nothing short of awe inspiring. And most programmers DO NOT JUMP THROUGH THOSE HOOPS. They'll tell me it is so easy (i.e. JAVA programmers who say "you just put the synchronized keyword on the class!") and then show me a chunk of code that has race condition bugs systemically. 

 

The DVRs aren't the problem. By reference programming is the problem. DVRs are the best solution out there for that problem that I know of. The "giant amount of work" that everyone complains about when using them is only giant when compared to the amount of work needed to do by reference programming **badly**. Doing by ref programming right is even more work than DVRs.

 

Stop using references to data. In a parallel environment it is a bad idea. Sharing data between two processes is like sharing hypodermic needles between two addicts -- dangerous and likely to spread disease.

 

 

As my inital post indicated, i am well aware that most of the points are already possible by using an overelaborate way of implementation. I wanted to pass feedback to LV OOP R&D for improving the status quo, not discussing possible "status quo approaches".

 

 

Yes, I got that you were passing that feedback.  I was trying to answer your feedback by saying that, except for #3, nothing you were suggesting was a new idea. All were ideas that have been considered, generally at great length by multiple people and rejected because they are either logically impossible in LabVIEW (i.e. a mandatory constructor/destructor for a by value type) or would actually be detrimental to the status quo (i.e. overloading with same name). #3 is a good idea in one particular form, and I strongly agree with your wish that we had that kind of polymorphism.

 

And, in case you didn't already know it, a mandatory constructor and destructor are *already possible* if you write your class as a by reference class (i.e. every public method is one that takes a DVR of the class as input and no public method takes the by value object directly). Some of your later comments made me think maybe you weren't aware of that fact.

Link to comment
Stop using references to data. In a parallel environment it is a bad idea. Sharing data between two processes is like sharing hypodermic needles between two addicts -- dangerous and likely to spread disease.

Lol, I don’t share needles, but objects from time to time.

I still use reference objects in my designs, and I do share them in parallel loops when needed, but that is objects, not the data itself.

See you in 2 weeks AQ :-)

Link to comment

Thanks for your long answers, AQ.

That really sets things in a better light ;)

 

I concur with most of your points and thanks for sharing the discussion about "atomic property nodes" when using classes/class DVRs.

BUT: When talking about "atomic" (encapsulated and protected ranges of code i mean by that), LV does already provide good mechanisms, also with LV classes. Each VI is automatically locked as long as it isn't reentrant.

So coupling my suggestion with classic OOP concepts, IF the developer of the class requires a couple of functions to be atomic, he has to create a "master function" calling into the collected subfunctions. Due to the "master function" being a single LV VI in LVOOP, we already got the lock!

 

You are correct that this behavior (if NI was to provide it) would lead to issues for some users as they do not read, check out things and only rely on "their way how to do things". But this kind of customer is always an issue for a company, except you work on turn-key solutions for this specific customer....

I think that a good documentation and some good examples would be enough for most developer to understand pitfalls and to avoid them.

 

I am confident that providing a "complete" by-reference handling of objects does not contradict the very basic paradigm of LV: dataflow programming.

 

thanks again, (and hopefully, R&D re-discusses this ;) )

Norbert

Link to comment
Lol, I don’t share needles, but objects from time to time.

I still use reference objects in my designs, and I do share them in parallel loops when needed, but that is objects, not the data itself.

See you in 2 weeks AQ :-)

Yes, but you share them in a hospital/clean room setting. Most users don't have that kind of research facility. :-)

 

 

So coupling my suggestion with classic OOP concepts, IF the developer of the class requires a couple of functions to be atomic, he has to create a "master function" calling into the collected subfunctions. Due to the "master function" being a single LV VI in LVOOP, we already got the lock!

 

 

Yes! And *if* the class-creator programmer designed their class to be a reference class, then he/she will write this VI. The entire class must be designed with that thought in mind. If the class-creator programmer designed their class as a by value class which is then stored in a DVR, then the locking must be specified by the class-user programmer, i.e., by scoping the usage with the IPE. This is exactly the situation as it exists today. This is why it is unlikely to change.

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.