Jump to content

Create methods for reference classes


Recommended Posts

This thread is a continuation of the two discussions of using Create methods for reference classes here and here.

Is this expressed "hatred" a semantic issue (you don't like the word "Init") or the whole idea of a "Create" method something that has fructarted you?

Neither really. The idea of a Create method doesn't bother me, but the requirement of using a Create or Init method on an object constant that looks like it should work fine without one does bother me.

To prevent this confusion I never use the object constant on the BD to 'create' objects.. Basically all objects are created by explicitly placing the constructor method on the BD. I should note I use by-ref classes for the major part of my designs and they come naturally with a create method for construction, but for by-value classes I use the same paradigm. The constructor typically has no input terminal for the class wire.

For most LV users using a create-destroy paradigm is quite natural since there's a lof of that already in LV (instrument I/O, file I/O, Queues, etc..).

Yeah, I've thought about that. The difference is that there's almost* no reason to put a queue constant or file I/O constant on a block diagram and when you do there's a clear indication via the dog-eared corner that you are dealing with a reference. There are valid reasons to put a by-ref object cube on the block diagram (upcasting and downcasting for example) and when you do, it looks exactly like a by-val object.

(*The one corner case I can think of is if you need to wire it into a bundle prim as a place holder.)

The rule-of-thumb for by-val objects is "branching always makes a copy." (We know that's not always true, but it's a useful way to explain the behavior to new users.) The rule-of-thumb for by-ref objects is "branching always refers to the same object." Now we have to throw in the caveat, "unless you haven't initialized your by-ref object." It's as if by-ref object constants on the block diagram aren't objects at all, but are classes instead. Before LV'09 you couldn't put classes on a VI--only objects. Now you can, and they look suspiciously like objects.

It can be particularly perplexing for class users if, like me, you try to simplify the class by initializing the DVR at runtime on an as-needed basis. In those cases, simply reading a property will lead to unexpected results for the new LOOPer.

post-7603-125501775775_thumb.png

Daklus last image seems prefectly normal to me, however it is an "unexpected" result.

I submit that the only reason it seems perfectly normal to you is because I revealed information about the implementation. Specifically, I explained that it is a by-ref object and told you where the DVR initialization was taking place. Had I presented it as a normal by-val object the results would not have made any sense whatsoever.

I think that's part of the problem... By-val classes and by-ref classes are fundamentally different beasts, with different behaviors across a wide range of operations, yet to the average user they look identical (subject to the class designer's icon making skills) and at design-time they behave identically. By-ref object cubes on the BD aren't really "objects" in the sense that they are ready to be used. They behave more like a traditional class definition--the user still has to "create" (i.e. initialize) the object before it can be used.

Are there any other BD constants in Labview that have to be initialized at run time before you can use them? (Honest question... I can't think of any off the top of my head.)

Ummm. VI, TCPIP, UDP. IMAQ, Bluetooth, IRDA?

Nope. Static VI references are configured at design time. TCPIP, UDP, Bluetooth, and IRDA constants can't be initializied at run-time. You have to create a new one. (I don't have IMAQ installed so I can't check it.) Same with queues and notifiers. Functionally, those constants (minus the Static VI Ref) on a block diagram are only useful for their type information. There are no prims you can wire through that magically convert it into a valid refnum. Contrast that with a by-ref object constant. It's useful only for it's type information** unless you wire through its Init method, which magically converts it into a fully functioning object.

(**Depending on how it's written, certain methods may be functional before creating the internal DVR; however, I think this confusion only compounds the problem.)

Certainly using a Create method (class output only) rather than an Init method (class input and output) works in some situations. But it's not always a viable solution. Suppose I have a reusable by-ref class that is intended to act as a parent for other developers to extend for their own purposes. They'll need to have their own Create method to initialize the parent object, but they can't do dynamic dispatching on the parent's Create method because there's no class input. Work arounds, such as making the Create method external to the class and wrapping it all in a library to take advantage of namespacing, are bulky and cumbersome.

Furthermore, sometimes developers do their LOOP work primarily out of the project window rather than from the palette. There's no way I'll drag a queue constant from the project window onto the block diagram and then wonder why it's behaving oddly. That can very easily happen with by-ref classes.

I know I'm kind of all over the place with this discussion. I'm having a hard time figuring out exactly what I'm objecting to. I've just had a long-running sense that using objects with references introduces a lot of inconsistencies in the user experience (which leads to confusion for new users) and that it could be so much simpler.

Link to comment

Copied from a different thread

Doesn't it come down to the fact that by-reference objects are really references to by-value objects and all references in LabVIEW basically behave the same?

In my mind I prefer to think of DVRs as reference to something and sometimes those things happen to be (by-val) objects; I don't think of by-ref objects to be a unique type of data type in LabVIEW.

I think we're mixing meanings of 'by-ref' object. It sounds like you're referring to a DVR wrapping an entire object. I'm referring to the 'Lava-style' by-ref object developed by SciWare, where the class' private data is wrapped in a DVR. So the answer to your question, IMO, is no. By-ref objects don't, and shouldn't, behave like references. They should behave like objects. The question that then needs to be answered is, how should objects behave? I'm sure AQ and his team have answers for this but here's my take on it:

In general, a class user can expect one of three kinds of behaviors from a class. These are the things the user needs to know about the class to use it correctly.

  1. By-val. Any branch or independent data source creates a new object.
  2. By-ref. Branching creates a reference to the original object. Independent data sources create new objects.
  3. Singleton. Any branch or independent data source refers to the same object.

You can also categorize classes based on how they are implemented. The user should not have to know these details to use the class correctly.

  1. Reference class. A class that contains a resource that must be initialized at run-time.
  2. Non-reference class. Does not contain resources that must be initialized at run-time.

Part of the problem as I see it is the class user needs to know implementation details to know how to use the object with respect to Init methods. The only thing the class user can be sure of is that a by-ref object must be initialized before it can be used.* Does a by-val class need to be initialized? Maybe... depends on how it was implemented. Does a singleton need to be initialized? Maybe... depends on how it was implemented. What happens if I branch either of those objects before initializing it? Dunno... depends on the implementation--does the Init method actually initialize something or is it an empty wrapper? Requiring the user to know all these details clearly violates encapsulation.

(*Most of the time... sometimes it will work just fine.)

My frustration mainly stems from the inability to adequately hide the complexities of references from the class user. They not only need to know that the class uses references, they must also be aware of the state of the internal refnums; is it valid or is it 0? I've experimented with implicitly initializating run-time resources in class methods on an as-needed basis, but it turns out this is not a very good solution. It often leads to results that are inconsistent with the class' expected behavior.

Also consider the situation where I have deployed a non-reference class but for some reason or another an update requires adding an internal refnum. Even if nothing in the public api has changed, I've just broken backwards compatibility and have to go back to every application that uses that class and add Init methods. This should not be necessary.

The static dispatch create method described by Tomi here addresses the issues with dynamic dispatching I outlined in my previous post and is the best solution to that particular problem I have seen so far. But it does not address the fundamental problem that object cubes can still be placed on the block diagram in a "nonfunctional" state and manipulating object cubes directly on the block diagram is a natural way to work with them. The same is not true for refnum constants.

Edited by Daklu
Link to comment

didn't see this thread. Why did you start a new one?:blink:

Nope. Static VI references are configured at design time.

I wasn't refering to static VI refs.

TCPIP, UDP, Bluetooth, and IRDA constants can't be initializied at run-time. You have to create a new one. (I don't have IMAQ installed so I can't check it.) Same with queues and notifiers. Functionally, those constants (minus the Static VI Ref) on a block diagram are only useful for their type information.

Indeed. Constants are, well, constant :P Immutable, unchangeable! If you can change it is not a constant, its a variable.

There are no prims you can wire through that magically convert it into a valid refnum. Contrast that with a by-ref object constant. It's useful only for it's type information** unless you wire through its Init method, which magically converts it into a fully functioning object.

Indeed. Constants are very often used for type information especially with polymorphic vis.

I looked at your "confusion" image and it seemed to me to be syntactically identical to:

So I didn't think it unusual or erroneous since I use something similar with self initialising functional globals.

Edited by ShaunR
Link to comment

Note the distinction I make in the previous post between a 'by-ref' class and a 'reference' class.

didn't see this thread. Why did you start a new one?blink.gif

Because two different threads had turned into discussions on Create methods and neither thread reflected that subject in the title. I thought it better to create a dedicated thread.

Indeed. Constants are, well, constant tongue.gif Immutable, unchangeable! If you can change it is not a constant, its a variable.

True. (Strictly speaking LV doesn't even have variables. Everything is a constant.) However, I wasn't using the term 'constant' to describe the R/W attribute of a piece of data; I was using the term to describe the graphical representation of the data type on the block diagram. There are no cases I'm aware of in LV where you drop a refnum constant on the block diagram and have to send it through an Init method before you can use it.* You have to create them. (Though I believe the preferred LV terminology is obtain.) Yet with LV reference classes that's exactly what you have to do--drop the constant and wire it through your Init method.

(*Before you object and point to your snippet as an example of this, I believe opening a reference to an external resource, such as a vi, is a distinctly different operation than creating a reference to a resource that is entirely internal, such as a queue.)

Wrapping the class constant and Init functionality in a Create method isn't an adequate solution for several reasons:

  1. As The Decisions Behind the Design points out, LV classes do not need creators. They are created as soon as you drop the cube on the block diagram. Requiring a class to be "created" (via the Create method) before it can be used directly contradicts that.
  2. There is no way to prevent the user from dropping an reference object cube on the block diagram and attempting to use it directly without sacrificing some of the advantages of OOP. There's not even a way to discourage them from doing so. Wrapping the class in a library and making it private stops users from putting the object cube on the block diagram, but it also prevents users from inheriting from that class.
  3. Dropping constants directly on the BD is in fact the normal way to program in Labview. Using creators is abnormal; there are only a few native data types that require them. Having a mix of classes, some of which require creators and some do not, is very inconsistent and non-intuitive. How do you explain to the casual LV user (and non-programmer) why some objects need to be created and some do not--especially when the need for a Create method appears completely arbitrary from the outside?

I looked at your "confusion" image and it seemed to me to be syntactically identical to:

That's the point. An object should behave like an object, not a reference. Any reference class I create starts behaving like a reference instead of an object. If I want my object to behave like a reference, I'll wrap it in a DVR. But I want my reference classes to behave like objects, and there's no way for me to do that.

Since in my example I defined that class to be a by-ref class, both equality checks should be true. The only reason it does make sense to you is because I told you when the internal references, which you should not need to worry about, are being initialized. Replace the object cube with a FP control and what will the results be? Unknown, unless you can discover the state of the internal references (you know... those things in the private data) when they are passed into the sample.

So I didn't think it unusual or erroneous since I use something similar with self initialising functional globals.

I'm not saying the current behavior is incorrect. This isn't a bug I'm complaining about. Whether or not the behavior is ususual depends fully on how well you understand references in Labview and how well you know the class' implementation. It does, however, cause behavior that can be very non-intuitive and difficult for class users to comprehend.

In my first post I concluded with the statement, "I'm having a hard time figuring out exactly what I'm objecting to." Having thought about it a lot over the past week, I think I hit on the problem with my post from yesterday. The core issue is there's no way for a class developer to properly encapsulate the complexities of references from class users. All the other problems are a result of this. I've been wishing for implicit class initializers for a long time, but a solution might also be found in an automatically initialized queue (and notifier, etc.) control that can be dropped on the class control. I'm sure there are other potential solutions as well.

Link to comment

Wrapping the class constant and Init functionality in a Create method isn't an adequate solution for several reasons:

  1. As The Decisions Behind the Design points out, LV classes do not need creators. They are created as soon as you drop the cube on the block diagram. Requiring a class to be "created" (via the Create method) before it can be used directly contradicts that.
  2. There is no way to prevent the user from dropping an reference object cube on the block diagram and attempting to use it directly without sacrificing some of the advantages of OOP. There's not even a way to discourage them from doing so. Wrapping the class in a library and making it private stops users from putting the object cube on the block diagram, but it also prevents users from inheriting from that class.
  3. Dropping constants directly on the BD is in fact the normal way to program in Labview. Using creators is abnormal; there are only a few native data types that require them. Having a mix of classes, some of which require creators and some do not, is very inconsistent and non-intuitive. How do you explain to the casual LV user (and non-programmer) why some objects need to be created and some do not--especially when the need for a Create method appears completely arbitrary from the outside?

Great thread that I need to reread a few times. I see a few different topics:

One is naming convention. By-reference classes include a create/obtain and destroy/release (obtain having the unique functionality of obtaining another reference to the existing object data that must also be subsequently released). So let's keep that same convention.

By-value: By it's very nature, as it has been stated above and in Decisions behind the Design, does not require the creation of the reference for the object. Don't use the create/obtain terminology here. I like what Endevo has done. When their toolkit generates a by-value class, the term is simply init. I would concur that "init" is not technically necessary. However... I run in to many OOPophobes. :wacko: And if the "cube" causes confusion and consternation, then I will just bury it in an init VI. That is just a style preference in certain circumstances.

Second... much has been said about initializing either type of class. I'll pass on commenting on that for now....

Third is the distribution of the class. Yes, I am a project junkie. I do everything from the project. However, I might considerer distributing some classes through that antiquated thingy called the "functions palette." It's at least a modest attempt to hide intricacies.

Link to comment

One is naming convention.

Got it. Create/Obtain for by-ref classes. Init for by-val classes.

(How do you distinguish between Init methods that modify an input object and Init methods that simply spit out a preinitialized object? Or is it best practice that Init methods always/never have a class input terminal?)

And if the "cube" causes confusion and consternation, then I will just bury it in an init VI.

It's not the cube itself that causes confusion. It's the inconsistent behavior of the object on the wire coming out of the cube.

Second... much has been said about initializing either type of class. I'll pass on commenting on that for now....

I look forward to your comments. I realize I'm in the minority with this viewpoint. I'm also aware that there have been lots of discussions about initializing classes and I suspect many at NI are tired of talking about it. The discussions I've seen focus on constructors or the improved convenience auto-initializing classes provide. The convenience is nice, but I'm more concerned about having consistent use patterns for classes with different behaviors and properly hiding the implementation from the class user. I have not seen any responses from NI people on these two questions with respect to this issue. (Granted I don't browse the NI forums very often.) Nor have I seen an explanation of why we shouldn't be able to create classes that automatically initialize runtime resources. The Decisions Behind the Design kind of skirts around the issue but it doesn't directly answer the question.

Third is the distribution of the class. Yes, I am a project junkie. I do everything from the project. However, I might considerer distributing some classes through that antiquated thingy called the "functions palette." It's at least a modest attempt to hide intricacies.

The functions palette is useful for reusable code designed to be used across different projects. It is much less useful for project-specific reusable code. Also, the palette only hides the organizational complexity of the class. It does not 'hide' the effects a reference in the class has on class users.

By the way, feel free to speak openly and tell me I'm a bonehead if that's how you see it. I opine a lot on Lava and I'd much rather have people come out and tell me I'm wrong than respond with platitudes or ignore the issue altogether. I won't be offended, I promise. smile.gif

Link to comment

I run in to many OOPophobes. wacko.gif And if the "cube" causes confusion and consternation, then I will just bury it in an init VI. That is just a style preference in certain circumstances.

ohmy.gif I definately fall into this catagory ... Keep those ugly cube things away from me.

(How do you distinguish between Init methods that modify an input object and Init methods that simply spit out a preinitialized object? Or is it best practice that Init methods always/never have a class input terminal?)

IMO the Init method should simply spit out a fresh object. A method used to put an existing object back to its initial state would be Resetting the object and I'd refer to it as such.

~Dan

Edited by Dan DeFriese
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
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
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.