shoneill Posted January 15, 2013 Report Share Posted January 15, 2013 And using a class instead of input fields doesn't work because....... 1 Quote Link to comment
Rolf Kalbermatter Posted January 15, 2013 Report Share Posted January 15, 2013 Well. I think you mentioned something earlier about varArgs and PHP (which admittedly may be easier because it is interpreted at run-time) has the capability to accept variable length arguments (func_get_args). Like most of these sorts of things, I don't care how it's implemented; that's not my job. However, it would be nice if it did. I didn't mention PHP, but instead Lua. Lua is not interpreted but compiled into byte code, just like .Net, Python and others. I don't think it makes that much difference if a language is interpreted, compiled or byte coded, in terms of vararg implementation. Some have a more elegant way of defining it, some implement it in a truely hackish way (most vararg implementations in C used to rely in fact on some low level compiler features that went as far as inline assembly) and some chose to not support it to not give the user another way to shoot themselves in their foot. The most close vararg analogy in LabVIEW would be Variants. Ohh you say that it is a pain to implement and maintain such an interface? But so are all vararg type interfaces! In terms of typesafety, stack boundery handling and all that, you can't have the cake and eat it too! Quote Link to comment
ShaunR Posted January 15, 2013 Report Share Posted January 15, 2013 I didn't mention PHP, but instead Lua. Lua is not interpreted but compiled into byte code, just like .Net, Python and others. I don't think it makes that much difference if a language is interpreted, compiled or byte coded, in terms of vararg implementation. The most close vararg analogy in LabVIEW would be Variants. Ohh you say that it is a pain to implement and maintain such an interface? But so are all vararg type interfaces! In terms of typesafety, stack boundery handling and all that, you can't have the cake and eat it too! I mentioned PHP because it is one I'm fairly competent in rather than as a reference to your examples. My grammar is shocking sometimes I'm actually forever [intentionally] circumventing LabVIEWs type safety. It was useful to stop me shooting myself in the foot when I was many moons younger but as the grey hairs rampage to my neck-line (and lower) I've come to view it more as a hindrance than a feature. I expect if you took it away I'd also bitch about that too , but it does sometimes prevent some truly elegant solutions and compact generic code. Variants (the feature that never was...lol) aren't really the same as variable length argument lists. I consider that the com-pane is the labview equivalent of a function prototype (one terminal for each argument). I have been down the route of just having a single variant input instead of a number of terminals, but the amount of code you have to write and tricks you have to pull to make it useful, is just not worth it. Readability also becomes non existent. I'd rather have someone elses cake and eat mine Quote Link to comment
Rolf Kalbermatter Posted January 15, 2013 Report Share Posted January 15, 2013 I mentioned PHP because it is one I'm fairly competent in rather than as a reference to your examples. My grammar is shocking sometimes I'm actually forever [intentionally] circumventing LabVIEWs type safety. It was useful to stop me shooting myself in the foot when I was many moons younger but as the grey hairs rampage to my neck-line (and lower) I've come to view it more as a hindrance than a feature. I expect if you took it away I'd also bitch about that too , but it does sometimes prevent some truly elegant solutions and compact generic code. Variants (the feature that never was...lol) aren't really the same as variable length argument lists. I consider that the com-pane is the labview equivalent of a function prototype (one terminal for each argument). I have been down the route of just having a single variant input instead of a number of terminals, but the amount of code you have to write and tricks you have to pull to make it useful, is just not worth it. Readability also becomes non existent. I'd rather have someone elses cake and eat mine It truely depends how good the cake is. If it is delicious I might also go for seconds. And I didn't mean variants to be the same as varargs, but it's the closest you can get to in LabVIEW. Unless of course you use LabVIEW objects as Vi conpane parameters, then you can truely put anything in the parameter, never mind that you might have to design a class hierarchy and accompanying code that makes Variant handling look trivial! As to typesafety I mostly consider it very beneficial. Sometimes it can be a bit hindering, but overall it is a real life saver for me. Maybe that is also because if I think I will need some truely horrible ways to be able to shoot in my foot, I usually go and write C code instead! Quote Link to comment
Aristos Queue Posted January 15, 2013 Report Share Posted January 15, 2013 I agree with your analysis on the polymorphism. Jack's idea works better if we get interfaces first. No idea when that will be. I don't work on that any more. Quote Link to comment
ShaunR Posted January 15, 2013 Report Share Posted January 15, 2013 And using a class instead of input fields doesn't work because..............it creates a shedload of code that you have to maintain Quote Link to comment
JackDunaway Posted January 16, 2013 Author Report Share Posted January 16, 2013 I have actually come across this instance before where it was frustrating that I couldn't have different terminals for an override. It forced me to have "dummy" terminals which were only functional in certain overrides which (to my mind) was unnecessarily confusing and meant more documentation. .Untitled.png Aha! This is a perfect example where Dynamic Dispatch 'appears' to be the correct solution, yet it's not. Any override with unused or incoherent inputs on its interface is better replaced by simple static methods in the concrete classes. The parent should not define the superset interface of potential child implementations, where concrete class callers "just should be aware" of which inputs they should be wiring (that makes for a weak API) Your example is a canonical example for 'Must Implement' -- the Static Concrete Implemented Method is a more appropriate construct than 'Must Override' with Dynamic Dispatch. Yet you, and me, and others make the compromise (or make the mistake) of sometimes using Dynamic Dispatch purely for the Must Override and Must Call Parent contract. And I'm *totally* not ragging on your code, Shaun -- rather, happy to see another respected developer show code and admit what we've created "felt wrong". I bet we're not the only ones, and I'm determined to hash out a resolution for this, whether a new ability to 'Must Implement', a design pattern to account for this, or just better personal coding practices. Finally, a few common instances where Dynamic Dispatch is often the incorrect choice where Must Implement 'feels right': the initialize() function of concrete object types that requires unique sets of parameters to construct the object; a Serializer, Logger, Messenger, or DisplayNotifier class who expects its children to serialize(), yet understands and allows for their need for strongly-typed unique inputs; basically, *any* time you ever find yourself considering a variant or stringly-typed input or output to accomodate for unique interface requirements Quote Link to comment
ShaunR Posted January 16, 2013 Report Share Posted January 16, 2013 (edited) "Read" Yet you, and me, and others make the compromise (or make the mistake) of sometimes using Dynamic Dispatch purely for the Must Override and Must Call Parent contract. Nope. It's not as effervescent as that in this case. I care not for architectural astronauticsThe override is used because of "re-use" and to avoid code replication which is not a pre-requisit for override which is generally used either to modify the object or as a placeholder. The override enables me to put operations that are identical in all the child classes (error translation for example) into a single location. The dynamic dispatch is still, however, required as the child (in the example, the protocol) is still determined at run-time. This is no different from the parent calling the "Read" from within a case-statement (where each case is a different protocol) and then carrying on to process the result of the call. However, with the case statement scenario I could indeed have different terminals for each protocol. Edited January 16, 2013 by ShaunR Quote Link to comment
JackDunaway Posted January 16, 2013 Author Report Share Posted January 16, 2013 I care not for architectural astronautics Agreed, a Spolsky fan also, and keeping software engineering on the lithosphere. The override is used because of "re-use" and to avoid code replication which is not a pre-requisit for override which is generally used to modify the object. The override enables me to put operations that are identical in all the child classes (error translation for example) into a single location. Yep; this shows the power of 'Must Call Parent', which also would exist for 'Must Implement' as it does today for 'Must Override'. This type of code re-use contract is not unique to Dynamic Dispatch, but equally important with Must Implement. The dynamic dispatch is still, however, required as the child (in the example, the protocol) is still determined at run-time. This is no different from the parent calling the "Read" from within a case-statement (where each case is a different protocol) and then carrying on to process the result of the call. However, with the case statement scenario I could indeed have different terminals for each protocol. Then I sit here scratching my head how the object is determined run-time, yet one concrete implementation requires a different ConPane than another. Again, I'll stand by the statement that if any methods in a Dynamic Dispatch hierarchy need separate ConPanes, Dynamic Dispatch was the wrong choice, and your comment on the Block Diagram "Not Used; Only to make ConPanes identical" seems like a red flag... it's the desire for the three current language benefits of overrides (defining the parent-child relationship, ability to require implementation, and ability to require calling parent for object construction and re-use), just without the requirement of the defined ConPane. Quote Link to comment
ShaunR Posted January 16, 2013 Report Share Posted January 16, 2013 (edited) it's the desire for the three current language benefits of overrides (defining the parent-child relationship, ability to require implementation, and ability to require calling parent for object construction and re-use), just without the requirement of the defined ConPane. Yup. Except that it's not just the "ability to require implementation" currently, is it?. It's the ability to require implementation with identical arguments. I could re-architect so that the dummies weren't required. In fact, I did start out on that route. What you are seeing is "after" refactoring where I reduced the codebase for that part of the system by 65% and removed 3 supporting classes in complexity. So I have made the conscious decision to trash the "proper" architecture . I don't care how "correct" an architecture is if it bloats my code by that much just to fulfill some sort of feigned righteousness. It would be more elegant and compact still, if I could get rid of the dummies too (hence my interest;) ) Edited January 16, 2013 by ShaunR Quote Link to comment
Daklu Posted February 1, 2013 Report Share Posted February 1, 2013 Read the thread. Didn't follow everything, such as... The usage of 'Must Implement' best jives with calling code that instantiate concrete, static classes. These concrete instances fundamentally represent functional programming, and incidentally enjoy the OO benefits of encapsulation/access scope/inheritance of abilities/and contractual requirements of subclassing (woot!). Can you explain what you mean by "These concrete instances fundamentally represent functional programming...?" And do you really mean *functional* programming, or do you mean procedural and/or imperative programming? -------------- Overall I agree with MJE and Rolf in that I don't really see the point of a must-implement flag. Or at the very least, I'm not convinced the benefits of having it are worth the extra restrictions it imposes. It seems like an overly-clunky way to communicate intent to other developers, and borders on defining style over function. Finally, a few common instances where Dynamic Dispatch is often the incorrect choice where Must Implement 'feels right': the initialize() function of concrete object types that requires unique sets of parameters to construct the object; a Serializer, Logger, Messenger, or DisplayNotifier class who expects its children to serialize(), yet understands and allows for their need for strongly-typed unique inputs; basically, *any* time you ever find yourself considering a variant or stringly-typed input or output to accomodate for unique interface requirements Taking the initialize() example, why do you want to impose a compile time requirement that subclasses implement some sort of constructor method? Wouldn't a class with pre-defined default values for its fields be functionally indistinguishable than a class constructor that takes those same values as input parameters? How is forcing a developer to implement an initialize() method when one isn't needed beneficial? The serialize() example is a little confusing to me. You say Logger expects its children to serialize, but where does this expectation come from? It's not a functional requirement--Logger can't call child_serializer.vi because it doesn't exist and it's not accessable through dynamic dispatching. Therefore, they are not really Logger's expectations; they are the Logger developer's expectations as to what other developers might want to accomplish by subclassing Logger. I can't even predict all the reasons I might want to create a subclass, much less predict why others might want to create a subclass. I used to include all sorts of protections to "help" others do things the right way, but for the most part I don't do that anymore. IMO it gets in the way far more than it helps. I rarely use Must Implement, and even more rarely use Must Call Parent. I try to restrict their use to situations where not using them will break the behavior of class, not just because that's how I think the subclass should be implemented. Setting aside for the moment your desire to use language constructs to impose your expectations on class users, you've already pinpointed the issue behind all of these problems. Regrettably Labview doesn't allow classes in the same ancestoral line to have methods with identical names unless they are dynamic dispatch.** If NI can fix that (and I really hope they do) I think it would relieve a lot of the issues you are describing. <minor tangent> **This bug feature characteristic of Labview actually has significant impact on my ability as a reuse code developer to guarantee backwards compatibility of my packages. We all know there are certain things you shouldn't do to the publicly available methods of a reuse library if you want to maximize compatibility. You don't remove or reorder items on the conpane, you don't change fully qualified names, etc. Here's another one to add to the list I discovered last year while I was trying to get people to understand the risks of VI package conflicts: Don't add any (public or private) methods to an existing class that is, or has descendents that are, publicly available to end users. Why? Consider LapDog Messaging. It's been out for a couple years now and a few people have used it in projects. Suppose I do a minor refactoring and move some of the code in the ObtainQueue method into a new, private _initQueue method. I haven't changed the API's public or protected interface at all, just done some refactoring to improve sustainability. It will pass all my unit tests and any unit tests others choose to run on the class to verify behavior. However, if a user happens to have used a previous version of LapDog Messaging, subclassed MessageQueue, and implemented a method named _initQueue, then any code that uses that subclass won't compile until the naming collision is resolved. Unfortunately name collisions aren't always resolvable and I'm left without any good solutions. </tangent> Yet you, and me, and others make the compromise (or make the mistake) of sometimes using Dynamic Dispatch purely for the Must Override and Must Call Parent contract. I'll go out on a limb here and claim this is a mistake. Must Override and Must Call Parent are requirements that only have meaning in the context of overriding a method. It makes no sense to flag a static dispatch method with Must Override, and it's equally nonsensical to flag a child method with Must Call Parent when there's no parent method to call. My guess is if you find yourself wanting to enforce Must Override or Must Call Parent rules on non-dynamic dispatch methods, something in your design isn't right. Quote Link to comment
Aristos Queue Posted February 4, 2013 Report Share Posted February 4, 2013 Jack: After working through your thread, I think the answer you are looking for is what would be called template classes in C++ and generic classes in C#. There you would define the ancestor class in terms of type T -- not any specific type, but an unnamed type T. Think like the Queue primitives. You have many different types of queues: queues of strings, queues of integers, etc. All of them need a "copy data into the queue" operation. Obviously that cannot be defined by the parent "queue" class. And it cannot be by dynamic dispatch because, as you point out in your examples, every child has a different interface for this operation. The templating/generics takes care of that. An entirely new class is instantiated by the compiler by supplying a concrete type to fill in for type T. R&D prototyped but never released generic VIs (loathe the name because the terminology is way too overloaded, but I'll use it for now). We need a way that you would put a placeholder in the private data control and then in your project specify "I am using a new class which is the same as my generic class but with that placeholder filled in with <your specific type here>". Templates/generics have proved quite powerful in various languages, but they generally come with the need for an ultrasmart linker that can, at load time, only create one copy in memory of the specific concrete classes, even when multiple modules join together, each one of which may have instantiated the same concrete class. They also want to only duplicate the specific methods that use type T's internals and not duplicate any methods that just refer to type T in a way that all the assembly code generated is identical (i.e. T just passes through them but is not itself modified). That addresses ShaunR's memory concerns. Without such smart linkers, templates will bloat your code very very very quickly. I assume if we ever get this feature in LV that we will have learned from the other languages that have it and build the linker capacities in from the outset. 1 Quote Link to comment
mje Posted February 4, 2013 Report Share Posted February 4, 2013 R&D prototyped but never released generic VIs (loathe the name because the terminology is way too overloaded, but I'll use it for now). We need a way that you would put a placeholder in the private data control and then in your project specify "I am using a new class which is the same as my generic class but with that placeholder filled in with ".Note that we've discussed such a feature before and I hope that should it ever see the light of day that it would not be restricted to only classes. Think generic VIs which perform operations on arbitrary array types etc. Quote Link to comment
Daklu Posted February 4, 2013 Report Share Posted February 4, 2013 I really, *really* want templates for my collections. Boxing/unboxing works, but it's awfully cumbersome and feels entirely unnecessary. R&D prototyped but never released generic VIs... Can you tell us what they looked like/how they worked? Were they more along the lines of C# generics or C++ templates? (Just curious.) An entirely new class is instantiated by the compiler by supplying a concrete type to fill in for type T. Doesn't a text language IDE know about the concrete class prior to compiling? For example, if I create an int collection in C# and attempt to put a string in it, the IDE tells me there is an error before compiling. (Come to think of it, I suppose that's just part of the type checking...) Without such smart linkers, templates will bloat your code very very very quickly. Really? Seems like having a few extra copies of a concrete class hanging around in memory would be nearly unnoticable in a desktop Labview app. The program doesn't do anything with those classes. The total number of objects created from those classes is going to be the same regardless of whether you have 1 copy or 5 copies of the concrete class. We need a way that you would put a placeholder in the private data control and then in your project specify "I am using a new class which is the same as my generic class but with that placeholder filled in with <your specific type here>". Isn't a void wire a wire whose type isn't known? Quote Link to comment
Rolf Kalbermatter Posted February 5, 2013 Report Share Posted February 5, 2013 Can you tell us what they looked like/how they worked? Were they more along the lines of C# generics or C++ templates? (Just curious.) Our friend flran posted them somewhere else on this board. Aristos himself had revealed them at some point by posting a password protected file on the NI site. Of course it didn't take long until someone peeked past the password. But this implementation is definitely to be considered part of the unfinished attic in LabVIEW, with many rusty nails sticking out everywhere and possibly causing you nasty pains. Doesn't a text language IDE know about the concrete class prior to compiling? For example, if I create an int collection in C# and attempt to put a string in it, the IDE tells me there is an error before compiling. (Come to think of it, I suppose that's just part of the type checking...) The IDE doesn't necessarily know. In fact the older Visual Studio IDEs did nothing like that, the only thing they knew about was syntax highlighting. But many IDEs (Eclipse too) have nowadays special syntax check modules, that basically contain the entire syntax parser of the compiler already in order to provide such just in time error indications. It's not that the IDE is doing something that is trivial, it's that it pulls in the entire compiler parser to do this. Isn't a void wire a wire whose type isn't known? A void wire logically doesn't yet exist in LabVIEW although the LabVIEW internal typecodes to know a void datatype, which is used for various internal things already. It is not an unknown type but a type carrying no data at all. Quote Link to comment
JackDunaway Posted February 5, 2013 Author Report Share Posted February 5, 2013 Can you explain what you mean by "These concrete instances fundamentally represent functional programming...?" And do you really mean *functional* programming, or do you mean procedural and/or imperative programming? Yes, functional; definitely not imperative/procedural, which is roughly on the same spectrum (yet in a less-desirable direction) as OO. But functional is orthogonal to this spectrum, and promotes at least one tenet that typically competes with imperative and object-oriented programming: minimizing mutable state, and ideally statelessness. An OO approach to the NotifyUser.lvclass example might suggest setting property SetMessage.vi and then invoking DisplayMessage.vi, in the meantime storing Message in the class private data. Functional Programming promotes statelessness, so we would strive to implement Display Message with a ConPane with the complete set of parameters necessary to perform the action. Not coincidentally, this style marries beautifully with event-driven programming. For this reason, I want child class functionality to implement and "override" (I use the term "override" loosely) with different ConPanes than the parent. And I'm clearly stating that this request does not jive with OO purism, so I completely agree with (yet reject as a argument against "Must Implement: ) all sentiments in this thread that "Must Implement" does not adhere to the interface as defined by the parent. I can't even predict all the reasons I might want to create a subclass, much less predict why others might want to create a subclass. Fair enough; but we can predict some. I rarely use Must Implement, and even more rarely use Must Call Parent. I try to restrict their use to situations where not using them will break the behavior of class, not just because that's how I think the subclass should be implemented. Likewise, I rarely use these either. (yet, mainly because of the limitations described in this thread!! e.g., wanting unique ConPanes for child Implementations). Your second sentence above is perhaps a better verbalization of my sentiments; a subclass who Implements a parent, yet does not Implement a certain method that would be best marked as "Must Implement" (yet not Dynamic Dispatch) is effectively breaking the functionality of the class. The reason the class designer would flag "Must Implement" goes beyond vacuous expectations of what ought a child class to do; it's an indication that "something is broken with this inheritance unless this obligation is being fulfilled". We would probably agree that if the class designer uses this contract much beyond ensuring things don't "break", that the contract is probably being overused and/or misused. This crusade is for a desire to minimize mutable state. Reduce asynchronization between an object's cached state with the subject's physical state. Reduce cognitive overhead of understanding systems (i.e., reading code). Increase opportunities for parallelizability. These are my perceived benefits of functional programming over imperative programming, and why a healthy dose thrown is thrown in with the benefits of LVOOP. Setting aside for the moment your desire to use language constructs to impose your expectations on class users, you've already pinpointed the issue behind all of these problems. Regrettably Labview doesn't allow classes in the same ancestoral line to have methods with identical names unless they are dynamic dispatch.** If NI can fix that (and I really hope they do) I think it would relieve a lot of the issues you are describing. It won't necessarily relieve the issues described in this thread; but generally agreed that it will relieve other issues, and is a much welcome improvement. I'll go out on a limb here and claim this is a mistake. Must Override and Must Call Parent are requirements that only have meaning in the context of overriding a method. It makes no sense to flag a static dispatch method with Must Override, and it's equally nonsensical to flag a child method with Must Call Parent when there's no parent method to call. My guess is if you find yourself wanting to enforce Must Override or Must Call Parent rules on non-dynamic dispatch methods, something in your design isn't right. 100% agreed, and you're not on a limb. Consider my calling it a 'compromise' a friendly euphemism for 'mortal sin' Jack: After working through your thread, I think the answer you are looking for is what would be called template classes in C++ and generic classes in C#. This is a new, interesting discussion altogether, but not the solution. It breaks down simply when you want different numbers of parameters on the ConPane for child implementations. (Whereas, genericity describes unknown type for any one parameter) That being said, genericity might alleviate (even, significantly) some of the root problems here. Quote Link to comment
ShaunR Posted February 5, 2013 Report Share Posted February 5, 2013 This crusade is for a desire to minimize mutable state. Reduce asynchronization between an object's cached state with the subject's physical state. Reduce cognitive overhead of understanding systems (i.e., reading code). Increase opportunities for parallelizability. Bingo! Quote Link to comment
Aristos Queue Posted February 5, 2013 Report Share Posted February 5, 2013 This is a new, interesting discussion altogether, but not the solution. It breaks down simply when you want different numbers of parameters on the ConPane for child implementations. (Whereas, genericity describes unknown type for any one parameter) That being said, genericity might alleviate (even, significantly) some of the root problems here. If this is true, then I definitely do not understand what you're asking for. How can a parent class have any need to call a function when it doesn't even know the parameter count? What possible need could a parent have to define *anything* for the child? Try again, from the top, using small words and pictures, please. Quote Link to comment
JackDunaway Posted February 5, 2013 Author Report Share Posted February 5, 2013 (edited) Try again, from the top, using small words and pictures, please. OK. Here are pictures. And since those pictures describe a problem that could probably be solved with a Generic input, look at this picture, yet instead of having two methods DisplayMessage-Bool-YesNo.vi and DisplayMessage-Bool-YeaNay.vi, consider just one method DisplayMessage-Bool.vi that has two inputs: the Bool to display, and a second Enum which defines how to localize the message (e.g., {"YeaNay", "YesNo"}) Here's the setup: when inheriting from parent classes, a subclass might redefine behavior of the parent. This redefinition of behavior may even be required by the parent, with "Must Override". Additionally, the parent may specify "Must Call Parent" in order to ensure some essential task is performed. Here's the problem: Dynamic Dispatch defines and requires an interface -- a ConPane -- a function prototype (this is clearly a requirement for run-time polymorphism). Additionally, only Dynamic Dispatch affords the contracts of Must Override and Must Call Parent. I find myself wanting to enforce these types of contracts ('Must Implement' and 'Must Call Parent'), yet with a separate ConPane for the concrete implementation. Here's something that I've observed many smart people do (and done myself): Put a variant (or another stringly-typed parameter) on the ConPane of a Dynamic Dispatch VI because strong-typing was in some manner breaking their inheritance mojo. Or, a more 'sophisticated' approach is to replace this input with an object. The problem is now 'fixed' on the Dynamic Dispatch method, but pushed into another location (e.g., the construction of this object and/or the handling of this object inside the method), *and* now with a larger codebase. (*** EDIT *** One additional code smell is the presence of an "unused input") Here's where everyone keeps getting hung up on this thread: Dynamic Dispatch affords the ability of run-time polymorphism. I'm specifically stating that sometimes it's desirable forgo this ability in favor of a more descriptive concrete API -- defining concrete methods with more specific ConPanes and Function names -- yet without losing the ability to define a linkage between parent and child abilities. It bugs me to see dozens of Do.vi functions in a big LabVIEW app implementing the Command Pattern, or in the Head First Design Patterns book see the Animal method makeNoise() abound instead of quack() or moo() or bark(). I totally understand that many application domains require run-time polymorphism (including the Command Pattern) or dependency injection (including not knowing which Animals might makeNoise() in an app) -- but for the many application domains that don't have these requirements, concrete implementations tend to be far easier to implement and maintain. It's much easier to work with a composition of one cow and one duck than anArbitrarySetOfAnimalsWhoImplementBarnyardInterfaces[], both as a human with finite cranial horsepower, and with a system whose types can be checked with static analysis rather than probing around run-time errors. Here's a request: Please don't quote me as an arguing against Dynamic Dispatch or interfaces or inheritance or generic programming or... Each of these concepts has merits, and application domains where they shine. Don't misquote me saying static composition is better than dependency injection, or Implemented methods categorically allow more coherent APIs than Overridden methods. (I love the opportunities to work on systems that require these higher-level abstractions.) But what you can quote is my pragmatic desire to decouple some components of our beloved language in order to allow random access to each of its merits -- in this case, Inheritance of abilities and Implementation contracts, yet without Run-Time Polymorphism and specified interfaces. Here's what I'm proposing: Right now 'Must Override' and 'Must call parent' come with the requisite usage of Dynamic Dispatch and a specified interface. Let's change the name of 'Must Override' to 'Must Implement', then allow this and 'Must call parent' as attributes of any given class member, not just Dynamic Dispatch methods. Here's where you come in: Is there a better solution to the problem? Is the presence of the perceived problem (wanting a more descriptive API for concrete implementations and a language-feature for linking implementations to parent behaviors) indicative of a bad programming practice on my part? What other languages address this issue? It's nearly inconceivable to assume this issue has not been either: 1) already implemented in a language or 2) characterized then rejected as an anti-pattern -- so I'm eagerly awaiting links to answers if this sounds familiar to anyone! Edited February 5, 2013 by JackDunaway Quote Link to comment
ShaunR Posted February 5, 2013 Report Share Posted February 5, 2013 (edited) I'm still not getting this (emphasis added by me) These two issues would be addressed by a "Must Implement" feature, shown below (note: this is not valid syntax with LabVIEW today, but through image manipulation we're able to demonstrate the proposed syntax) It is valid syntax if you don't use classes since it is simply calling a common sub-vi. You seem to be (in this example) arguing for classic labview behaviour (which already exists). Edited February 5, 2013 by ShaunR Quote Link to comment
JackDunaway Posted February 5, 2013 Author Report Share Posted February 5, 2013 It is valid syntax if you don't use classes since it is simply calling a common sub-vi. You seem to be arguing for classic labview behaviour (which already exists). For clarity, the reason this is not valid syntax is explained by this error: "This VI attempts to override a static VI in an ancestor class. An ancestor class has a VI by the same name that does not have a dynamic dispatch input terminal. Only dynamic member VIs can be overridden in child classes. To correct this, change the name of this VI so that it does not match any ancestor VI names or edit the ancestor to include a dynamic dispatch input terminal." I think you're hinting that this error and problem does not exist in non-LVOOP, 'classic' LabVIEW -- true. (And the consensus seems to be that we would like to see this relationship defined by an abstracted GUID rather than filenaming enforcement, but I digress...) Yet this "classic labview behaviour" does not exist as you suggest (in any manner other than adherence to convention), since there's no concept of the "Call Parent Method" node or an inheritance relationship defined. Call this pedantic, but those relationship/constructs/contracts do help establish intent for subclass design, and ensure certain essential parent abilities are called from the subclass. Quote Link to comment
ShaunR Posted February 5, 2013 Report Share Posted February 5, 2013 (edited) Yet this "classic labview behaviour" does not exist as you suggest (in any manner other than adherence to convention), since there's no concept of the "Call Parent Method" node or an inheritance relationship defined. Call this pedantic, but those relationship/constructs/contracts do help establish intent for subclass design, and ensure certain essential parent abilities are called from the subclass.Well. I would argue it does exist. A vi is calling the parent (the sub-vi). You just don't have your hands tied by the syntax of classes.Is a vi not "inheriting" from a sub-vi (Create sub vi menu command)? Can you not "override" the sub-vi behaviour? Are you not "encapsulating" by using a sub-vi?. Can you not "compose" a sub-vis inputs/outputs? I have always argued that the only thing LV classes bring to the table above and beyond classic labview is Dynamic Dispatch and a way of organising that makes sense to OOP mindsets. If you are not using DD, then all you have is a different project style and a few wizards that name sub vis for you.. If you look at your example, then you are simply calling a sub-vi. However. You are restricted in doing so by a peculiarity of the implementation as a class. Edited February 5, 2013 by ShaunR Quote Link to comment
drjdpowell Posted February 5, 2013 Report Share Posted February 5, 2013 The parent class is saying "if you want to be like me, you have to do at least this; you can do it however you want, but you gotta do it." Just my two cents, but as the designer of a child class, I wish to have my child objects function properly in code written for the parent (and thus am interested in overriding and/or calling parent methods where required), but I am not actually interested in being required to do things with code written for the child. Quote Link to comment
JackDunaway Posted February 6, 2013 Author Report Share Posted February 6, 2013 Just my two cents, but as the designer of a child class, I wish to have my child objects function properly in code written for the parent (and thus am interested in overriding and/or calling parent methods where required), but I am not actually interested in being required to do things with code written for the child. Then you're totally with the consensus on this thread. So does this mean you don't use the 'Must Implement' flag? Or if you do, is it just strictly in the context of ensuring run-time polymorphism won't encounter an unimplemented method? (these questions are open to all, not just drjdpowell) Quote Link to comment
Aristos Queue Posted February 6, 2013 Report Share Posted February 6, 2013 Here's the problem: Dynamic Dispatch defines and requires an interface -- a ConPane -- a function prototype (this is clearly a requirement for run-time polymorphism). Additionally, only Dynamic Dispatch affords the contracts of Must Override and Must Call Parent. I find myself wanting to enforce these types of contracts ('Must Implement' and 'Must Call Parent'), yet with a separate ConPane for the concrete implementation. I'm still stuck back on the original problem, so bear with me. You want to enforce "must call parent", but you can't say what connector pane the parent function has? Huh? You want the parent to say "I have a function and it must be called at some point by my children, but I can't say what function they need to call it from and I can't say anything about their setup". At that point, why are these children at all? Why aren't you using a delegate? Which brings directly to Shaun's points... I have always argued that the only thing LV classes bring to the table above and beyond classic labview is Dynamic Dispatch and a way of organising that makes sense to OOP mindsets. If you are not using DD, then all you have is a different project style and a few wizards that name sub vis for you.. And that's almost exactly what I've said from the beginning. Object-oriented programming adds encapsulation and inheritance. It brings the next level of organization to your code (you know how to organize a block diagram, this is how to organize the VIs in a module). And if you can't define the relationship, then it is just a regular subVI call, with no special Call Parent Node behavior definable. I'm really missing what relationship you think these two classes have and why you think there's any sort of child relationship involved. 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.