JackDunaway Posted April 1, 2013 Author Report Share Posted April 1, 2013 He can use a variant for different datatypes (array of boolean or array of doubles) and therefore won't have a problem with overrides. Gotcha. Yeah, that's a potential code smell -- stringly-typed rather than strongly-typed prototypes. Now, your ctors and methods are subject to run-time errors. 1 Quote Link to comment
GregFreeman Posted April 1, 2013 Report Share Posted April 1, 2013 (edited) Gotcha. Yeah, that's a potential code smell -- stringly-typed rather than strongly-typed prototypes. Now, your ctors and methods are subject to run-time errors. And I could use a polymorphic VI if I wanted to enforce compile time checking...but then I lose the ability to enforce creation of the VI itself. And now we're back at square one. Edited April 1, 2013 by for(imstuck) Quote Link to comment
K-node Posted April 1, 2013 Report Share Posted April 1, 2013 And I could use a polymorphic VI if I wanted to enforce compile time checking...but then I lose the ability to enforce creation of the VI itself. And now we're back at square one. Perhaps if your polymorphic VIs wrapped protected, dynamic dispatch methods that can be overridden by the sub-class. I supppose the protected method could take a variant. Quote Link to comment
Aristos Queue Posted April 2, 2013 Report Share Posted April 2, 2013 I've given some more thought to my "add a straightjacket later" idea. It's a bad idea. ShaunR's comments provide a good starting point for explaining why. Quick summary of the "add a straightjacket later" idea: The idea of the straightjacket was that all classes would be written with no restrictions (every thing public, no requirements for override, an anarchist paradise). Then you would apply, as a separate file, restrictions to the class for a particular project or compilation. "Within this project, this function is private; this one is protected and is Must Override; this one is Must Call Parent; this one must have exactly three Feed Forward nodes; this one must meet O(n) performance characteristics..." and so on. (Note: That last one about O(n) performance constraints, isn't something that any compiler I know of is capable of proving on its own at this time, but the ACL2 language theorem prover is getting close, so in the next 20 years I expect many programming languages will be able to apply such constraints to interfaces). This is also the crux of the Private Vs Protected debate. What is it better to do? Put so many restrictions that they have to edit your code for their use case (and you will get all the flack for their crap code), or make it easy to override/inherit so they can add their own crap code without touching your "tested to oblivion" spaghetti - regardless of what you think they should or shouldn't do. Forget classes for a minute. Every function (aka VI in LabVIEW) has preconditions. The function will not execute correctly unless those preconditions are met. There's an option that a developer faces on every individual function... do you document the preconditions in a comment or do you check them at the beginning of the function?True story: There was a particular math function that only worked on ascending arrays of data, and it checked that the data is ascending and returned an error if the data was not ascending. A developer realized that the work that function was doing was exactly what he needed for a completely different math operation when used on a descending array. He couldn't use the function because it asserted the array must be ascending. And, in this case, he couldn't borrow the code either because it was a compiled DLL he was calling into.Was the author of the original function wrong to check that the array was ascending and return an error? Those of you arguing that everything should be public are making this exact argument, whether you realize it or not. The function was written with knowledge of its calling context. And in that context, it was an error to have any other type of array. Let's take this back to access scope... Take this little snippet of code:This is a member of a class. It finds the first value in an array that is between two range limits. What happens if "Range Limit 1" is greater than "Range Limit 2"? No values get found. So the programmer screwed up, right? It needs a check at the beginning to flip the range around if they are passed in backwards, right? Oh, and a value might not be found in that array... it might be empty. Probably need to add a Boolean output for "found?". Right? No. Turns out that the class limits access to these fields. This array *always* has at least one element in it whenever this VI is called. And the Range Limit 1 is always less than Range Limit 2. How can that possibly be? Because this VI is private. It is only called by callers who have already checked the array is non-empty and has valid refnums in it. This is NOT a case of putting too many restrictions on the code. This is a case of putting exactly the right restrictions on the code. The code for bounds checking and range checking and a whole lot of other pre-condition testing is NOT something you can add later. If you're not going to add the restrictions until later, you have to include handling in the public version for all the possible values of the inputs.But all that pre-condition checking is a performance hit. An *unnecessary* performance hit if the code knows the conditions under which it will be called. This is not some random library function. This is a member of this class. And it is proper to make it a private function to limit the conditions under which it may be invoked. So "preconditions of a function" was one problem with the whole idea that caller requirements are separatable from the code. The next is more interesting. In thinking about the idea of the straightjacket, I kept coming back to the above paragraph. I also looked through various code bases I've worked on over the years, in G, C++, and C# (didn't bother digging into any of my old JAVA code).What I found was this: there are cases where a completely wide-open initial base class is created... or, just as likely, an interface. Nothing private, just the functions that are needed to do the job, sometimes with placeholders for functions that may be filled in later.But then someone will create a "canonical class implementation" of that base class or interface that is for a specific scenario. And that one does have all the restrictions placed onto it, specifically because it is designed to be the canonical implementation that *most* of the children inherit from. It isn't just "Oh, for this project I need to have these limitations". It is actually "I am being implemented in such a way that I only work properly under these limitations." An easy one is the class with an Init and Uninit functionality. The first base class may not do anything in its implementation. The first child -- aka, the canonical base class for most of the other children -- overrides the Init to register the object with another module of the application. This canonical implementation does this in its base implementation so that children derived from it do not have to do that manually themselves. But children might override Init and never override Uninit. The children will have a functionality mismatch if they override Init and fail to call the parent class implementation... the Uninit will throw an error trying to unregister from the core module when it was never registered in the first place. What point is there to leaving this as a runtime error? None. Better instead to mark the canonical Init as "Must Call Parent". And there are many other variations along this theme. In short, yes, classes are designed for the environment in which they expect to be called. You *can* write classes that are fully agnostic of their calling environments, and those classes are completely valid for things like public classes of published libraries. But anytime you have implementation code, you have calling conventions. Every function has its requirements, every class has its higher level requirements. You write code to be independent to the degree that it needs to be or that you want it to be. You complain that the guy downstream will just modify your code and complain when he breaks something? That could happen if you make everything public! The argument is not an argument against putting proper restrictions on code. That's an argument against downstream users of a library modifying the library and then calling it your fault, and that's a discussion to have with your manager not with your architect. So, if you happen to have a library that is *designed* to have all of its functions open for anyone to call, where every function is fully expecting to be called under the full range of its input values, great! But you design for that, and your code looks totally different than if you design for a more limited calling environment. And in those cases, your requirements are better checked by the compiler than by the run time system -- you get better run time performance and downstream developers spend less time figuring out what to code and you have fewer edge case errors springing up in deployed systems. If you aren't properly adding restrictions -- access scope, Must Call Parent, Must Override, etc -- to your functions, you are wasting your developers' time and your end users' time. I stand by my original statement: Usage requirements of a class, whether for callers or for inheritors, are best spelled out in the code such that the compiler can enforce them. Quote Link to comment
Aristos Queue Posted April 2, 2013 Report Share Posted April 2, 2013 for(imstuck): The problem you're having is the same that Jack is having. I'll be honest... I don't get the problem. It seems like a problem I *should* be having in my Character Lineator. In order to support the LabVIEW built-in data types, I either create a single function (for the primitive types) or I create a class that defines the serialization for the compound types (i.e. Waveform, Timestamp, etc which are built out of lesser components). Those classes have to have some functions that are common in all the classes but each with a different types in the conpane. It seems like that is where I should be feeling a need for Must Implement, but I just don't see any difficulties whatsoever. Because I don't see the problem of needing to define any sort of Must Implement, part of me wonders if I've just been working software so long without this concept that I can't actually see the need for it. Or it's possible that LV has something like what you're asking for, and I'm just instinctively using it (because I created it) and you guys are missing it because I named it something weird. Jack has tried to explain it to me, and that didn't work, so maybe you can give it a try. a) You clearly expect the parent class to be able to say something like "a child must implement this VI". But the parent class cannot specify what the conpane of that VI should be... not the types, not even how many terminals it needs. So what exactly does the parent VI specify? Is it simply "children must have a VI of this name" and nothing else? b) Let's say that a child doesn't implement this VI. Is the child class not runnable somehow? Or is it just not usable in its calling context? c) If the child class *does* implement this VI, is it a VI that is somehow invoked by the parent or a framework that uses the parent? I don't see how that can be since the conpane isn't even specified UNLESS part of saying "you must implement it" is also "and you must add a call to it to this case structure" or something like that. Is that the case? Or is this "must implement" VI something that a caller of the child class would use? If it is just something that the user of the class would use, why is it of concern to the parent class whether that VI gets written or not? How is the existence/non-existence of this function something that the parent even knows to specify? Quote Link to comment
GregFreeman Posted April 2, 2013 Report Share Posted April 2, 2013 (edited) for(imstuck): a) You clearly expect the parent class to be able to say something like "a child must implement this VI". But the parent class cannot specify what the conpane of that VI should be... not the types, not even how many terminals it needs. So what exactly does the parent VI specify? Is it simply "children must have a VI of this name" and nothing else? b) Let's say that a child doesn't implement this VI. Is the child class not runnable somehow? Or is it just not usable in its calling context? c) If the child class *does* implement this VI, is it a VI that is somehow invoked by the parent or a framework that uses the parent? I don't see how that can be since the conpane isn't even specified UNLESS part of saying "you must implement it" is also "and you must add a call to it to this case structure" or something like that. Is that the case? Or is this "must implement" VI something that a caller of the child class would use? If it is just something that the user of the class would use, why is it of concern to the parent class whether that VI gets written or not? How is the existence/non-existence of this function something that the parent even knows to specify? Due to the limited time I have to write this, I am going to try to be concise and hope that along with that comes enough clarity for you to refute . a) Lets say I have a DAQ Output class (hey, whadda ya know, I do!). Then I want to create child classes with NI DAQmx DO, or Third party card AO. It doesn't make sense, IMO to have a DAQ Output child class that doesn't have a "Write to DAQ Card" method. So, I want to enforce this, mostly from an it-makes-sense-in-this-scenario point of view. If you want to use my DAQ Output class, you better have some method for actually writing to an output! I don't care if it's a boolean for digital lines or a double for analog lines, but if you aren't writing to an output with the new child class, it most likely shouldn't be a child class of DAQ Output in the first place. Now, I could separate these classes out, scrap the DAQ Output parent class and have a Digital Out class and an Analog out class. This way the connector panes would have a boolean or double respectively. But then I have to add, to each class, a must override "write" method when it seems (again, maybe just to me) that this should be enforced in a higher level DAQ Output class so I am not continually redefining a must override method just due to different connector panes. This may be the point where you say, yes, you should . b) I'm not 100% sure, but maybe you can infer from my description above. Both? c) "Or is this "must implement" VI something that a caller of the child class would use. If it is just something that the user of the class would use, why is it of concern to the parent class whether that VI gets written or not"..........This, except in this case, it seems that it is of concern to the parent, because the parent is a DAQ Output. And why would you create a child class of DAQ Output, that doesn't write to the output of a DAQ, no matter what the data type is? Edited April 2, 2013 by for(imstuck) Quote Link to comment
Aristos Queue Posted April 2, 2013 Report Share Posted April 2, 2013 <blockquote class='ipsBlockquote'data-author="for(imstuck)" data-cid="102360" data-time="1364917921"><p>And why would you create a child class of DAQ Output, that doesn't write to the output of a DAQ, no matter what the data type is?</p></blockquote> Ah. Suddenly the issue makes sense. You might not have a single Write function on the child. It might have a compound way of setting up the write. That's an edge case, granted, but valid nonetheless. But let's ignore that edge: it is hard to see how the parent class gets involved. It can't specify the conpane. It may not be proper to even specify the function name (i.e. one might have "Write Boolean.vi" and another have "Write Double.vi"). Overall, this seems mostly like the child class adding new functionality the parent knows nothing about. The closest it comes to the parent would seem like a tag on the poly VI: "all children must add one function of some name and conpane to this poly VI". Generally this doesn't seem like a real problem since the lack of a Write VI will be caught at compile time... or, rather, at the time of code writing since, as you point out, the class isn't particularly useful without this part. :-) Quote Link to comment
Daklu Posted April 2, 2013 Report Share Posted April 2, 2013 I agree with AQ on the practical difficulties of enforcing a "must implement" requirement, or even trying to figure out what it means. It doesn't make sense, IMO to have a DAQ Output child class that doesn't have a "Write to DAQ Card" method. Now, I could separate these classes out, scrap the DAQ Output parent class and have a Digital Out class and an Analog out class. This way the connector panes would have a boolean or double respectively. But then I have to add, to each class, a must override "write" method when it seems (again, maybe just to me) that this should be enforced in a higher level DAQ Output class so I am not continually redefining a must override method just due to different connector panes. I'm not sure I fully understand your objectives, but I don't think the problem is the lack of a "must implement" flag. There is already a way to force child classes to implement a "Write" method--create it in the parent class and flag it must override. Your objection is that you need the connector pane to be different based on different data types and we can't do that with dynamic dispatching. If you need different connector panes on the input terminals, what you're usually asking for is double dispatching, or the ability to dynamically choose at runtime which vi is called based on two input types instead of just one. In your case, you would like that choice to be made based on both the type of the DAQ Output object and the type of the data to be written. Labview doesn't directly support double dispatching, and the need to do it is indicative of needing to revisit how you've assigned responsibilities to the classes and/or methods. What you are trying to do is a violation of the Single Responsibility Principle. The SRP is usually presented as "a class should only have one reason to change." Unfortunately that's very abstract and it's hard to understand exactly what it means in practice. In your particular case, it means "in an inheritance heirarchy, the runtime behavior (i.e. the block diagram defining the chunk of code that executes) should be completely defined based on a single input terminal." Or in more direct terms, "don't design your methods so they require double dispatching." (Yeah, I know... it's circular. It's one of those things you need to experience to really understand.) There are relatively easy ways to get the effect of double dispatching with languages that only support single dispatching, but it requires splitting your DAQ Output object into multiple classes. There are also ways you can kind of simulate multiple dispatching by using polymorphic vis and carefully managing the separation between how child classes inherit behavior from the parent and the users invoke the behavior. For an example of the latter, check how I implemented the EnqueueMessage behavior in LapDog.Messaging. (Drop an ObtainPriorityQueue method to get the libraries into your dependencies folder.) I wanted the EnqueueMessage method most commonly used to have different connector panes depending on the data type on the message terminal, so I created a set of polymorphic vis to do the necessary type conversions. I also wanted child classes to be able to override the behavior that is invoked when EnqueueMessage is called, so the polymorphics vis delegate that responsibility to a protected _EnqueueMessage vi. Child classes, like PriorityQueue, override the protected method to implement the custom functionality being added by the class. [Edited to fix minor grammatical error] Quote Link to comment
Aristos Queue Posted April 2, 2013 Report Share Posted April 2, 2013 I'm not sure I fully understand your objectives, but I don't think the problem is the lack of a "must implement" flag. There is already a way to force child classes to implement a "Write" method--create it in the parent class and flag it must override. Your objection is that you need the connector pane to be different based on different data types and we can't do that with dynamic dispatching. All of which means there is not already a way to implement the "Write" method as defined by for(imstuck) and Jack. And we're back to the original question. And I do not think they're violating SRP. Each of their classes is doing only one thing. They're just looking for a way for the parent class to specify "you have to do *something*". It's defining that "something" that is elusive in this conversation. Quote Link to comment
Daklu Posted April 3, 2013 Report Share Posted April 3, 2013 Those of you arguing that everything should be public... I don't know if I am one of those you are referring to as arguing "everything should be public," but I don't think I made that claim at all. I assume you're referring to Shaun. I have claimed that putting restrictions on a function reduces it's utility, which I loosely define as the number of places the function can be used successfully, but that's not the same as claiming everything should be public. I've given some more thought to my "add a straightjacket later" idea. It's a bad idea. There are definitely some elements in your argument that contradict my interpretation of the straitjacket's purpose, and given my interpretation the conclusion doesn't follow from the arguments you've presented. By and large I agree with a lot of the stuff you say in the post, but I disagree with your conclusion. Trying to suss out the specific areas of disagreement... The idea of the straightjacket was that all classes would be written with no restrictions (every thing public, no requirements for override, an anarchist paradise). Then you would apply, as a separate file, restrictions to the class for a particular project or compilation. This is the most obvious place where our understanding of straitjackets differ. First, whether the requirements are embedded in source code or applied via an external file isn't the important question in this discussion. The important question is, "who is responsible for declaring and applying the restrictions, the code author or the code user?" The mechanism for declaring restrictions can't be decided until after we figure out who is able to declare them. Second, the nature of the restriction isn't important. A restriction is a restriction. Labview allows us to declare certain kinds of restrictions (input types) and doesn't allow us to declare certain other kinds (execution time.) For this discussion let's assume all restrictions are declarable and verifiable. In the statement above you're setting up a straw man. No useful code can have *no* restrictions, else it doesn't do anything. As soon as you define something the code does, you're also implicitly defining things the code doesn't do, and that is a type of restriction. You go on to give several examples showing where the code author implemented the correct amount of code checking. In all of these cases he had knowledge of the environment where the code is going to be called and could set the restrictions accordingly. This is also where your argument starts to go astray, imo. Was the author of the original function wrong to check that the array was ascending and return an error? Those of you arguing that everything should be public are making this exact argument, whether you realize it or not. The function was written with knowledge of its calling context. And in that context, it was an error to have any other type of array. In the math function example, the author chose to include array checking inside the function. That is a valid design decision, especially if he has complete knowledge of the calling context. As it turns out, he didn't have complete knowledge of all future calling contexts where someone would want to use the function, and, because he included array checking as part of the function, it couldn't be used in those other contexts. The built-in array checking prevented someone from using the exact behavior that function provided simply because it didn't meet an arbitrary requirement. He could have let the caller be responsible for array checking prior calling the function. That trades away a bit of convenience in exchange for increased utility. He also could have written and exposed two functions, one implementing the basic operation without any checking, and another that checks the array and then calls the basic operation. That slightly increases overall complexity to get the utility gains. Was he "wrong" to include array checking in the function? No, not at all. Did it reduce the function's overall utility? Yep, absolutely. Where he was "wrong" was in his assumption that he knew all the contexts where the function would ever be called. Would prior knowledge of the other context been sufficient justification to change the design? I don't know... that's a subjective judgment call. But it did have real consequences for someone and I don't think anyone would argue against him being better prepared to make a good decision if he is aware of other contexts that people might want to use the function than he is by assuming those contexts don't exist. I stand by my original statement: Usage requirements of a class, whether for callers or for inheritors, are best spelled out in the code such that the compiler can enforce them. What you're saying here is a little different than what I remember you saying before. Before you claimed the parent class should have the ability to declare arbitrary restrictions that all child classes must adhere to, because the calling vi might wish to establish some guarantees on the code it calls. Here, you don't explicitly say who should declare the restrictions. Are you still maintaining that all the contractual requirements between a calling vi and a callee parent class should be declared in the parent class? 1 Quote Link to comment
Daklu Posted April 3, 2013 Report Share Posted April 3, 2013 All of which means there is not already a way to implement the "Write" method as defined by for(imstuck) and Jack. And we're back to the original question. Okay... then to answer Jack's original question: Or is this desire for "Must Implement" indicative of poor OO design choices? Yes, insofar as the design choices have led you to need some capability LVOOP, as a single-dispatch OO system, cannot provide for you. If LVOOP supported multiple dispatch it might not be a poor design choice. And to address the question in the topic title... Must Override exists; could Must Implement? I don't know. I see a lot of problems with actually implementing the concept. Regardless, I think there are better ways to address your particular concerns. Must Implement feels a lot like a hack. And I do not think they're violating SRP. Each of their classes is doing only one thing. They're just looking for a way for the parent class to specify "you have to do *something*". It's defining that "something" that is elusive in this conversation. I've never liked the phrasing of the SRP, but I confess I can't think of a better way to say it. In Greg's case, you could define the DAQ Output class' responsibility as "write data to the DAQ output," and that sounds a lot like just "one thing." But you could also say its responsibility is to "check the data type to be written to the DAQ and the type of DAQ card being used, then invoke the appropriate dynamic dispatch vi to handle those data types." That doesn't sound anything like "one thing." When you get right down to it, the SRP (as I understand it) simply means to make sure you are assigning responsibilities to your classes such that the behavioral change implemented in all your subclasses occur along a single axis. Greg's DAQ Output class wants to change behavior along two axes: the DAQ card and the data type to be sent on the DAQ output channel. Clearer? Greg and Jack both want "Must Implement" so they can define some behavior in a child class they currently don't have the ability to define. Why can't they use "Must Override?" Because overriding methods require identical conpanes. Why is that a problem? Because they want the overridden method to dispatch on multiple inputs. Remove the expectation of multiple dispatching (or add multiple dispatching to LVOOP) and the request for "Must Implement" goes away. There may be a valid use case for a "Must Implement" requirement, but I don't think this is it. Quote Link to comment
ShaunR Posted April 3, 2013 Report Share Posted April 3, 2013 I don't know if I am one of those you are referring to as arguing "everything should be public," but I don't think I made that claim at all. I assume you're referring to Shaun. I have claimed that putting restrictions on a function reduces it's utility, which I loosely define as the number of places the function can be used successfully, but that's not the same as claiming everything should be public. I never claimed "everything should be public" either. I argued protected vs private. Quote Link to comment
JackDunaway Posted April 3, 2013 Author Report Share Posted April 3, 2013 If you need different connector panes on the input terminals, what you're usually asking for is double dispatching, or the ability to dynamically choose at runtime which vi is called based on two input types instead of just one. Nope; nowhere near what i was originally looking for. The opposite, rather. Static methods, called by an application that statically constructs a child class. It's all very static on purpose -- no dynamic run-time decisions, because the problem domain does not call for dynamic or double dispatching or dependency injection or any other flavor of dynamic run-time ability. Taking this into the LapDog.Messaging domain: Consider a process that needs to send a string to another process; it's going to construct the message using LapDog.Messaging.v2.NativeTypes.lvlib:StringMessage.lvclass:Create StringMessage.vi. This problem domain would never require run-time selection of another ctor, whether through dynamic dispatch, double dispatch, etc. -- because the caller needs to send a string. Now, taking a look at all of the message ctors in LapDog.Messaging.v2.NativeTypes.lvlib, they all call RenameMessage.vi. -- yet I would suggest it's better if they called Message.lvclass:Create Message.vi, their "prototype", the ctor that exists in the parent -- bear with me; this is not a criticism, it's what I see as a great candidate for "Must Implement": For a moment, consider the calling of this particular function essential to the construction of a Message object for it to be viable within the framework. Further, even though this is just one function, consider it representative of "the collection of all the essential functions" that must be called in the construction of a Message object. (One very real upgrade we might want to perform in this case is timestamping the message, and we would not want to go into each of the concrete implementations to ensure they call Apply Timestamp.vi) What I'm suggesting is that each of the concrete Message ctors calls Message.lvclass:Create Message.vi instead of calling Message.lvclass:RenameMessage.vi. With this strategy, we allow the parent to templatize the sequence of essential constructor functions. If Message.lvclass were able to specify 'Must Implement' and 'Must Call Parent Implementation', we formalize the definition of Create StringMessage.vi as implementing -- which is discretely different but having traits in common with overriding -- the functionality of the parent class method Create Message.vi. Further, we guarantee the parent method called in order to perform the essential tasks that construct a well-formed message object. And we get the unique ConPanes we desire in the concrete implementing method. I've mentioned previously (post #13), and AQ brought up again (post #132: It may not be proper to even specify the function name (i.e. one might have "Write Boolean.vi" and another have "Write Double.vi")), that this "Must Implement" feature is gated by an inheritance relationship defined through a GUID private to the class rather than filenaming conventions of the method; this is evidenced with the desire for StringMessage.lvclass:Create StringMessage.vi to implement Message.lvclass:Create Message.vi yet not have its same filename. With these features and contracts in place, if a developer decides to subclass LapDog.Messaging.v2.lvlib:Message.lvclass by creating myArbitraryStructureMessage.lvclass, we can be guaranteed that it both implements and calls the parent implementation of the essential ctor Message.lvclass:Create Message.vi, yet is able to give it any name deemed fit with any conpane necessary to construct the specific object. The prime motivation here is inverting inversion of control; would one call this taking control? It's an exercise acknowledging that statically composed applications, when the problem domain allows, is superior to dynamically composed systems, in terms of ease of development and troubleshooting and maintenance. I think it would be handy to have language features such as 'Must Implement' that acknowledge the merits of subclassing without necessarily leveraging dynamic run-time abilities. Thoughts? Quote Link to comment
Daklu Posted April 3, 2013 Report Share Posted April 3, 2013 Thoughts? Very briefly... (yeah, as if) That comment was actually intended for Greg. I'd long since forgotten what you were looking for and didn't have the energy to go reread everything. I think my comment is applicable to his situation, even though it is not applicable to yours. In later posts I assumed your situations were similar based on AQ's drawing a parallel between the two. (Like how I shifted the blame there? ) I don't care that you responded to a comment I intended to Greg's situation--you referenced LapDog. You're aces, Jack. <Spur of the moment non sequiter> I just invented a new game named SighJack based on that last sentence. The object of the game is to fit as many references to a deck of cards as possible into a single sentence in the course of a normal conversation with you. Referencing the numbers 1-10 and the colors red and black don't count. Too easy. The words jack, queen, king, ace, spade, heart, diamond, and club are eligible. Why SighJack? because you'll get so tired of the game you'll sigh in resignation every time someone does it. And it sounds very similar to hijack, which is what's going to happen to all your conversations now. Entries are awarded one point for each eligible word with multipliers given for style. Jackpacking--unnaturally cramming eligible words into the sentence out of the context of the conversation--is penalized by disqualification. My entry has two eligible words for 2 points, and a 2x multiplier for smoothness. Oh, and a 1 gajillion mulitiplier for being the first entry in existence. (All future multipliers must be approved by me.) Total score: 4 gajillion. (Yep, it took me all of 1.7 seconds to create the game. Thinking of the name and typing everything out took another 10 minutes.) </resequiter> It's waaaayyyyyy past the stupid hour for me, there's a lot of stuff in your post to cognitize, and I think it will be several days before I can spend sufficient time figuring it out. But I promise I will, because you referenced LapDog. That was a kingly thing to do, Jack. (Booo... DQ for Jackpacking.) Quote Link to comment
todd Posted April 3, 2013 Report Share Posted April 3, 2013 I studied hardware, not software, so take this with salt. If you make a toolkit, users with your abilities (meaning high-level) will be able to use it despite the lack of perfection. Users at my end of the skill range will find a way to mess up our code no matter how much you club us over the keyboard and mouse with contracts. What about including a lint-type script in a dashboard that searches for the non-implemented must-implements? Quote Link to comment
JackDunaway Posted April 3, 2013 Author Report Share Posted April 3, 2013 What about including a lint-type script in a dashboard that searches for the non-implemented must-implements? Yeah; that's the motivation for shipping the parent with a unit test and a well-intended admonition. :-) For now, this seems like the best solution. Quote Link to comment
todd Posted April 3, 2013 Report Share Posted April 3, 2013 Oops - forgot you'd covered that. Quote Link to comment
Daklu Posted April 4, 2013 Report Share Posted April 4, 2013 Thoughts? Quick question: When you say Must Implement, are you using it to mean Must Call or Must Invoke? In some languages, implements is a keyword associated with Interfaces to indicate the class contains the source code to execute the methods defined by the Interface. In other words, putting a must implement requirement on a vi implies (to me) that the developer of the target vi has to write the source code that does some bit of arbitrary functionality. Based on your post above, it seems like you really want to make sure the target vi developer calls some other arbitrary vi on the block diagram of the target vi. (Must Invoke.) Is that accurate? Quote Link to comment
JackDunaway Posted April 4, 2013 Author Report Share Posted April 4, 2013 Quick question: When you say Must Implement, are you using it to mean Must Call or Must Invoke? In some languages, implements is a keyword associated with Interfaces to indicate the class contains the source code to execute the methods defined by the Interface. In other words, putting a must implement requirement on a vi implies (to me) that the developer of the target vi has to write the source code that does some bit of arbitrary functionality. Based on your post above, it seems like you really want to make sure the target vi developer calls some other arbitrary vi on the block diagram of the target vi. (Must Invoke.) Is that accurate? No. Just as Dynamic Dispatch has 'Must Override' and 'Must Call Parent', a static method could specify 'Must Implement' and 'Must Call Parent'. Note that 'Must Call Parent' has essentially the same semantics for Static and Dynamic Dispatch. Yet 'Must Implement' might be loosely analogous to a less-stringent 'Must Override'. Sorry for muddying the waters between 'Must Implement' and 'Must Call Parent' -- i see these going hand in hand in the context of, e.g., the LapDog.Messaging ctors. Quote Link to comment
Daklu Posted April 4, 2013 Report Share Posted April 4, 2013 Just as Dynamic Dispatch has 'Must Override' and 'Must Call Parent', a static method could specify 'Must Implement' and 'Must Call Parent'. There's still some fundamentals I'm missing here, 'cause I'm slow like that. Once I understand them I'll go back and look at how they apply to the LapDog example you gave. My apologies if these questions have been discussed already. I'm trying to organize the ideas so I can process them. With dynamic dispatching a 'Must Override' method imposes the requirement that child classes must have a method of the same name. The static dispatch equivalent, a 'Must Implement' method, imposes the requirement that child classes must have a method not of the same name (since they are sd and can't have the same name) that is somehow marked as fulfilling that requirement. The intent, I assume, is to force the child class to implement a static dispatch behavioral equivalent to the parent class method with the 'Must Implement' requirement. (i.e. Parent ctor is static dispatch and flagged MI, child class has to implement a ctor and tag that method as filling the MI requirement.) On track so far? With dd a 'Must Call Parent' flag on the parent method requires the overriding method to invoke the parent method somewhere on the block diagram. The sd equivalent on the parent method will require the child method that fulfills the MI requirement also invoke the parent method that imposes the MI requirement on the child method's block diagram. Am I getting it? Is the primary use case for this functionality ctors? Quote Link to comment
shoneill Posted April 4, 2013 Report Share Posted April 4, 2013 (edited) Just as Dynamic Dispatch has 'Must Override' and 'Must Call Parent', a static method could specify 'Must Implement' and 'Must Call Parent'. Note that 'Must Call Parent' has essentially the same semantics for Static and Dynamic Dispatch. Yet 'Must Implement' might be loosely analogous to a less-stringent 'Must Override'. "Must Call Parent" won't work with SD because the child doesn't know the connector pane of the parent. Or it does in a given moment, but needs to be manually updated if you ever change the hierarchy of your classes which will almost certainly be a major PITA. This can be done automatically with DD since the guarantee is there that the connector pane is identical. I still prefer to use objects as input parameters instead of going down this route. If the objection to this is the maintenance of the individual classes then the maintenance of the implementation being provided would be (in my opinion) just as problematic. Shane. PS The argument that the class hierarchy is (pun unintended) static and such maintenance problems won't occur, then my counter-argument is that the only constant in software engineering is that nothing is constant. Edited April 4, 2013 by shoneill Quote Link to comment
mje Posted April 4, 2013 Report Share Posted April 4, 2013 I've mentioned previously (post #13), and AQ brought up again (post #132: It may not be proper to even specify the function name (i.e. one might have "Write Boolean.vi" and another have "Write Double.vi")), that this "Must Implement" feature is gated by an inheritance relationship defined through a GUID private to the class rather than filenaming conventions of the method; this is evidenced with the desire for StringMessage.lvclass:Create StringMessage.vi to implement Message.lvclass:Create Message.vi yet not have its same filename. I've done my best to follow this topic, even if I haven't contributed lately. This is mostly because my original confusion/objection still stands and haven't really been able to think of a good way to explain my confusion. Your lapdog example triggered something though... If StringMessage.lvclass:Create StringMessage.vi implements (in your must-implement sense, not the traditional override or interface sense*) Message.lvclass:Create Message.vi, especially to the point where Message.lvclass:Create Message.vi method does not define a connector pane or the StringMessage.lvclass:Create StringMessage.vi changes the connector pane I just don't see how the relationship between the two is at all relevant to someone writing code. Code which operates only on the Message interface can not call the StringMessage "implementation" because the relationship is not dynamic dispatch and there is no way to link to the mutating connector pane: if I only know about Create Message, I can't wire up a string to Create StringMessage because I don't know it needs a string! Maybe I can't wire anything up to it because the original method didn't even define a connector pane. If I do have knowledge of the StringMessage class, I can already just drop the CreateStringMessage call right on my block diagram creating the static link, I have no need for this pre-defined relationship. The only use I can see for something like this is a linkage which might be useful for something like palette building where Message would define a CreateMessage as must-implement and place it on it's palette. Then when you bring up a context menu on an input the correct implementation would automatically be on the palette, or if the menu is brought up with no context, the method would be invalid if it does not have a default implementation. Is this what you're after? Because I still can't see any use case for this on the actual block diagram which isn't already covered by just dropping the right statically linked VI. It seems like it would be really a creative trick for the IDE to play... Quote Link to comment
Daklu Posted April 4, 2013 Report Share Posted April 4, 2013 Sorry for muddying the waters between 'Must Implement' and 'Must Call Parent' Additional follow up question: Do you envision a use case for using these two features independently, or does using one of them automatically entail using the other? I can kind of see how one mould want to use them together as a single feature, but as independent features...? ----------- I believe Jack is on his way to Paris, so may not be able to respond for a couple days. Here's my response to Shane and MJE's comments based on my understanding. (Still haven't gone back to the LapDog example you posted and I'm not trying to put words in your mouth, Jack. Please slap me down if I'm wrong.) If the objection to this is the maintenance of the individual classes... Jack's goal is to help users create the child class correctly in the first place. He wants to help users avoid mistakes in the initial implementation, not necessarily make it easier to maintain an existing code base. If that's true, there may be untapped value in the idea, but I'm not convinced that is how the technology is best applied. One of my fundamental beliefs regarding API development is trying to help end users too much via restrictions or conveniences usually does more harm than good. The best APIs are conceptually simple. If the concept is simple to understand then there's no reason to take extra precautions to prevent them from making a mistake. If StringMessage.lvclass:Create StringMessage.vi implements (in your must-implement sense, not the traditional override or interface sense*) Message.lvclass:Create Message.vi, especially to the point where Message.lvclass:Create Message.vi method does not define a connector pane or the StringMessage.lvclass:Create StringMessage.vi changes the connector pane I just don't see how the relationship between the two is at all relevant to someone writing code. Assuming MI and MCP are always used together and is restricted to a link between one parent method and one child method, there is no situation where Create Message.vi can not define a conpane. If Create Message.vi doesn't exist, then there's no way to flag it as MI, just as there's no way to flag a non-existent method with MO. Code which operates only on the Message interface can not call the StringMessage "implementation" because the relationship is not dynamic dispatch and there is no way to link to the mutating connector pane: if I only know about Create Message, I can't wire up a string to Create StringMessage because I don't know it needs a string! Maybe I can't wire anything up to it because the original method didn't even define a connector pane. The use case is for object injection scenarios where one developer (i.e. Jack) is writing an execution framework, and another developer (i.e. me) is writing subclasses for my app because I want to use that framework. The framework requires all objects to go through some sort of initialization routine during creation. If they do not go through this process then they will not work correctly within the framework. The calling code doesn't need to know about Create StringMessage because the framework never calls that method. It's up to the user to call the method and give the StringMessage object to the framework for processing. The framework itself calls the other methods defined by the Message class. Supposing the Message class had three methods, A, B, and C, and the framework design dictated they were always called in that order, then an easy way to get that guarantee is to create a protected initializing method, I, flag it MCP, and call that first. (I-A-B-C) If the framework design allows A, B, and C to be called in any order, then there are significant design tradeoffs the framework designer is faced with. I can... Insert I in the parent methods, flag them as MCP, and take the run time hit for checking to see if the object is initialized every single time, or Not do anything and hope the end user is smart enough to figure out why his objects aren't working in the framework. Jack doesn't want to do #1 and wants to provide a better user experience than #2. MI is a reminder for the child class developer that the child class 'must implement' the same kind of behavior defined in the parent method carrying the MI flag. It can't guarantee end users will do the right thing, but he hopes it will guide the users toward the correct implementation. Overall I agree with Todd's assessment of the situation (though not of his abilities.) Low level users will still screw it up and high level users don't need it. Quote Link to comment
shoneill Posted April 4, 2013 Report Share Posted April 4, 2013 @Daklu, I responded early in the thread with the option of having a class as an input for a DD call instead of looking to change the connector panes. That idea was thrown out because of excessive maintenance required but personally I find it the "correct" choice. I agree neither strategically nor practically (when you think about HOW this would be implemented) with the idea event hough I fully acknowledge the problem motivating it. Shane. Quote Link to comment
Daklu Posted April 5, 2013 Report Share Posted April 5, 2013 I responded early in the thread with the option of having a class as an input for a DD call instead of looking to change the connector panes. That idea was thrown out because of excessive maintenance required but personally I find it the "correct" choice. My apologies. That's what I get for not rereading the whole thread. Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.