JackDunaway Posted February 6, 2013 Author Report Share Posted February 6, 2013 You want to enforce "must call parent", but you can't say what connector pane the parent function has? Huh? No, the other way around: a child can always statically determine the ConPane of the parent (right?) -- I'm just suggesting parent does not necessitate the ConPane of an Implemented child method, like with an Overridden method. 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? The child class must mark at least one method as Implementing the parent method. Maybe it has the same filename; not necessarily (if GUIDS in the class definition could establish this relationship). There would exist a facility for marking the child method as Implementing the parent requirement (currently, the facility for marking a method as Overriding is purely filenaming convention; if we all agree that this would be better served by GUIDS, with the ability to freely name files, we still need to develop a non-implicit facility for marking a method as Overriding a parent method!!) I'm really missing what relationship you think these two classes have and why you think there's any sort of child relationship involved. Can you comment on the specific example of NotifyUser.lvclass and its children? (from post #7, the tl;dr version copied below for convenience) I can't think of a better example; sorry. Consider the class, NotifyUser.lvclass which only has one method, DisplayMessage.vi, whose purpose is to display a string to a user. Later down the road, you consider it worthwhile to display other types of things to users, like Bools and Numerics, so you decide to subclass NotifyUser.lvclass since it's already got all the business logic for displaying messages. The illustration below shows this: Notice how the two implementations have separate ConPanes from the parent.... That's where Must Implement saves us -- the ability to establish a relationship between child and parent methods, without requiring them to have the same interface. Another thing to note above is that the two children are broken with the message "This VI attempts to override a static VI in an ancestor class." This is due to today's design limitation that subclasses cannot have the same method names as parent methods, except in the case of overrides. Further, note that in the middle diagram, use of the "Call Parent Method" is unrecognized. These two issues would be addressed by a "Must Implement" feature, shown below... Now, with "Must Implement", we have the ability to contractually require functionality in subclasses while maintaining the ability to extend parent class functionality, having acknowledged that Dynamic Dispatch is the incorrect tool when distinct function interfaces are desirable. Quote Link to comment
Daklu Posted February 6, 2013 Report Share Posted February 6, 2013 An OO approach to the NotifyUser.lvclass example might suggest setting property SetMessage.vi and then invoking DisplayMessage.vi... I fully agree SetMessage followed by DisplayMessage is a less than optimal design path. In this particular case I think the problem is related to your class design and naming. First, NotifyUser implies gaining the user's attention somehow and optionally providing them with information to act on. Currently is has a single method, DisplayMessage, which displays a message box. Presumably a NotifyUser object could have different methods to notify the user in different ways, such as ApplyElectricShock or PlayAudioFile. These methods would have different inputs appropriate for what the method needs. Creating a child class named NotifyUser-Bool causes problems--ApplyElectricShock and PlayAudioFile have no meaning in a boolean-titled subclass created to override the DisplayMessage method. Sure, NotifyUser-Bool can simply not override those methods and use the default parent class behavior. The point is calling a PlayAudioFile method on an object named NotifyUser-Bool is incoherent. It doesn't make semantic sense. If you want to make child classes for each of the possible data types, then DisplayMessage (or other string-based notifications) is really the only method you *can* have in the class. And if that's the case then the NotifyUser class is misnamed. It's not a generalized object for notifying users; it is in fact a very specific way of notifying users. It should be called something like SingleButtonDialogBox. Now, if we have a class SingleButtonDialogBox with a single method DisplayMessage requiring a string input, it makes sense that all subclasses implementing DisplayMessage will also have a string input. After all, they are all going to be (or should be) some sort of dialog box, and dialog boxes require string inputs. Maybe you create a TimedSingleButtonDialogBox class that dismisses the dialog box after a fixed amount of time. But it's still going to require a string input to display something to the user. In short, the class name NotifyUser implies a high-level object that is able to notify an arbitrary user in an arbitrary way with an arbitrary notice. It creates the developer expectation that this object is responsible for any kind of user notification. On the other hand, the DisplayMessage method is too specific. The functionality implied by its name is too far removed from the general functionality implied by the class' name. I suspect it's this gap in functionality and unclarity in what the class is actually responsible for that is causing you difficulty. In particular, you are making NotifyUser (and it's subclasses) responsible for transforming an arbitrary data type into a string, and that responsibility is (imo) better left to someone else. (Such as giving the data object a ToString method.) (I realize NotifyUser is simply an example meant to illustrate the issue, but any example I come up with illustrating this problem leads back to questions about the design.) 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. I fully agree with this. Many of my classes have little, if any, mutable data, and I have found it makes multi-threaded programming much easier. However, I don't think your suggested solution is the right path to take the G language. In my eyes it enables incorrect designs with no appreciable benefit for advanced users. Minimizing mutable state is entirely possible using good programming practices with existing versions of Labview. It's not clear to me how adding a Must Immplement flag on non-existing vis moves towards the goal of minimizing mutability. (As an aside, in the past I've openly wondered how closely related dataflow programming is to functional programming. It's apparent there are similarities, but I don't have enough experience with functional programming to give a fair assessment. It appears you've discovered the same similarities. ) 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". Hmm... I guess my thought is simply the need to flag "Must Implement" is, by definition, an indication the design is flawed. I'm open to being convinced otherwise if you can show me an example of a good design that needs the Must Implement feature. (And to clarify, by "break the behavior of the class" I mean situations where a reasonable developer would have to dig into the parent's source code to discover why the child object is not behaving the way he expects given what he has implemented. I specifically do not mean that the child object will do everything "correctly" as a fully implemented child class would.) Have I ever wished I could change the conpane of overriding methods? Yep. Have I ever created child classes for a specific data type? I'm pretty sure I have. But these were driven by a desire for expediency, not because they were the correct design decisions. 1 Quote Link to comment
Aristos Queue Posted February 6, 2013 Report Share Posted February 6, 2013 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. Right. 5 copies of your class. Easily leading -- as I saw happen in the late 1990s with early naive implementations of C++ templates -- to multiplying the size of your entire program by 5 or 6. Took quite a few years to get it right. We can learn a lot from their trailblazing, but it still isn't dead obvious. Isn't a void wire a wire whose type isn't known? Sure. And we might be able to use it for that purpose. But there's no control for a Void type. And there's no template instantiation in the project tree or in the type propagation (because, ideally, types would be instantiated from the diagram the same way new queue types are created from wiring). And keep in mind we're not talking "save as" here... we're talking a chained template that has to be kept up to date with the original and only instantiate the type at *runtime*, not as actual source files. There's roughly a bijillion issues with template classes. I'd rank it as easier to do than interfaces, but it still isn't a one-release feature. -- Stephen Quote Link to comment
Daklu Posted February 6, 2013 Report Share Posted February 6, 2013 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! I think it's pretty clear from my post above that I think your problems stem from incorrect class design. But you asked for links, so here is one... Principles of Object Oriented Design In particular, your NotifyUser child classes seem to violate the Single Responsibility Principle (they convert data to a string AND display a message box for the user) and the Liskov Substitution Principle (child objects can replace parent objects without breaking the program's behavior.) Suppose you had a program that used the NotifyUser.DisplayMessage method, and at some point you decided to replace it with NotifyUser-Bool.DisplayMessage. What changes do you have to make? In a properly constructed class hierarchy you can simply replace the class constructor method (and arguments as needed) and everything else will work correctly. But with your NotifyUser design you'll have to go in and change the client code everywhere the DisplayMessage method is called because the message is coming from a different source. (As it must since the original message source is a string.) Now, if your intent is not to replace the NotifyUser object with a NotifyUser-Bool object at runtime, there's no reason to make NotifyUser-Bool inherit from NotifyUser and we circle around to AQ's question: Why have you established a parent-child relationship between these two classes? 1 Quote Link to comment
JackDunaway Posted February 6, 2013 Author Report Share Posted February 6, 2013 First, NotifyUser implies gaining the user's attention somehow... Great feedback on the example. To tweak the example, consider a base class called ProbeDisplay.lvclass whose job is to show a human-readable string for the Probe Watch Window. One could imagine there might be a lot of VIServer legwork and IDE voodoo in the base class (since it's desirable to reuse these abilities through inheritance), whereas child implementations such as ProbeDisplay.lvclass and ProbeDisplay-MyClass.lvclass would be relatively simple: serializing unique datatypes, and for the sake of illustration even sets of datatypes with multiple inputs on the ConPane if we could create some new WackassProbeExecutiveAggregator.lvclass child implementation. Now, it's clear you cannot just say "all implementations should be strings" -- now what do you do? OK, in the meantime rewriting this post, you came in with some good reference material that will take time (read, maybe days, weeks, after CLA Summits even) to chew on: Principles of Object Oriented Design In return, here's a link that has helped me, from which I recognized the name Uncle Bob (who has 3 contributions on this list): 97 Things Every Programmer Should Know And finally... Have I ever wished I could change the conpane of overriding methods? Yep. Have I ever created child classes for a specific data type? I'm pretty sure I have. But these were driven by a desire for expediency, not because they were the correct design decisions. It would be eternally helpful to do a post mortem on what the *right* solution would have been if you're able to share. (Or if Shaun is game, let's tackle his example in post #23) Perhaps, we decide the 'correct' solution is syntactically way too burdensome and in the architectural atmosphere, but at least we would feel less icky about the solutions as developed. 1 Quote Link to comment
Daklu Posted February 6, 2013 Report Share Posted February 6, 2013 <off topic> Right. 5 copies of your class. Easily leading -- as I saw happen in the late 1990s with early naive implementations of C++ templates -- to multiplying the size of your entire program by 5 or 6. Took quite a few years to get it right. We can learn a lot from their trailblazing, but it still isn't dead obvious. Was it naive implementations of templates, or naive use of the feature? If that was your experience I won't claim otherwise, but it still doesn't make sense to me. First, it will only occur for those classes that are instantiated from templates. I expect relatively few classes would come from templates. (Something less than 100% of them anyways.) Second, if I understood correctly class duplication only occurs when separate modules are invoking the same template with the same data type. It stands to reason that not all modules will create classes from the same template. Third, even 5 copies of a concrete collection class (the most obvious use for generics) in memory is likely to be much smaller in size that the many objects that the collection object contains. And keep in mind we're not talking "save as" here... we're talking a chained template that has to be kept up to date with the original and only instantiate the type at *runtime*, not as actual source files. I'm not quite sure what you mean by "chained" template, but it did occur to me that these instantiated classes do not have any representation on the file system. The template will, but not the classes created from the template. It's not clear to me how users *should* interact with template-created classes. Should the instantiated class appear in the project explorer? (My gut says no.) Should users be able to override or extend instantiated classes? (Again, my gut says no.) I image instantiated classes would behave similarly to dynamic dispatch method. If I replace a child class on the block diagram with the parent class, all the dynamic dispatch methods change as well. In the same way the wire of an instantiated class would carry information about what to replace the void controls with in each method. When the original instantiated class is replaced with a different one (created from the same template) all the void controls are updated to reflect the data type the new class supports. Anyway, I was just wondering if the prototyped edit-time behavior was anything like that. There's roughly a bijillion issues with template classes. Now you only have a bijillion-1 issues. I'd rank it as easier to do than interfaces... Rats. I keep hoping NI is working on them (actually traits rather than interfaces) but if templates are easier to implement it makes sense to tackle them first. (Given the lack of enthusiasm for interfaces at least year's summit I might never see them in Labview.) </off topic> Quote Link to comment
Daklu Posted February 6, 2013 Report Share Posted February 6, 2013 To tweak the example, consider a base class called ProbeDisplay.lvclass whose job is to show a human-readable string for the Probe Watch Window. One could imagine there might be a lot of VIServer legwork and IDE voodoo in the base class (since it's desirable to reuse these abilities through inheritance), whereas child implementations such as ProbeDisplay.lvclass and ProbeDisplay-MyClass.lvclass would be relatively simple: serializing unique datatypes, and for the sake of illustration even sets of datatypes with multiple inputs on the ConPane if we could create some new WackassProbeExecutiveAggregator.lvclass child implementation. Now, it's clear you cannot just say "all implementations should be strings" -- now what do you do? I know you struck out this comment while you take time to read over the material in the link, but I'll speak to it anyway because I think it raises a couple important points. 1. One of the mantras of OOD is, "encapsulate the things that change." In other words, if it changes, put it in a class. In your NotifyUser example (and presumably the ProbeDisplay example, but you didn't identify any methods so I'm not sure) you are not doing that. Specifically, the method's input types change. If you want to be able to change those inputs, they should be a class instead of a raw data type. 2. What do you mean by, "it's desireable to reuse these abilities through inheritance?" Do you mean you want to reuse those abilities through the inheritance tree, or do you mean you're using inheritance as a mechanism for enabling code reuse? IMO, the former is a natural desire arising from good design. It is reasonable to assume child classes will need to do much of the same VI Server work the parent class does. In these situations I lean towards implementing helpers as protected parent class methods. (Obviously it depends a lot on the specific situation.) The latter, using inheritance solely as a mechanism for enabling code reuse is possible, but (again IMO) far more error-prone. It lures unsuspecting developers into creating child classes simply to gain the benefit of reuse when delegation is the correct solution. When is it okay to subclass for the sole purpose of reusing code? **shrug** Honestly, I haven't articulated a set of rules to follow.[**] A lot of it is just what feels right. But here are some things to think about... Describing two classes as having a parent-child relationship doesn't adequately describe their roles. There are many reasons one would want to create a child class, however, all child class methods can be categorized as one of two things: a) The method modifies the behavior of the parent class by overriding a parent method. We're all familiar with examples of this. (Command pattern, unit test fakes, etc.) b) The method extends the behavior of the parent class by adding a new method. If I have a Dog class with all the usual suspects (Sit, Bark, etc.) I might create a Chihuahua class and add a ShakeUncontrollably method to account for behavior it should have but does not apply to all dogs. If a child class only implements modifying methods, then there is no reason to call child class methods directly, or even do anything with the child class on the block diagram other than create it. You can interact with it entirely through the parent class methods. If the child class implements extension methods the expectation is at some point the child object will move to its own wire to call those extended methods. (Those extended methods cannot be used on the parent's wire, and if you're not going to call them why create them in the first place?) In other words, child classes that modify the parent's behavior are usually accessed through the parent's methods, while child classes that extend the parent's behavior are usually accessed through the child's methods. Is the NotifyUser-Bool class (and the DisplayMessage method) intended to modify or extend the behavior of the NotifyUser class? Your posts aren't completely clear. On the one hand, the method's name and your use cases indicate an intent to modify the parent class. On the other hand, you've clearly indicated you want this feature to apply to non-dynamic dispatch methods, indicating an intent to extend the parent class. And while a child class can both extend and modify the parent class, any given child method can only do one or the other. You've given us examples of how NotifyUser is implemented. Could you perhaps give example code showing how you would like to use the NotifyUser classes? [**Edit] Having thought about it a bit more, my rule of thumb is to reuse code via inheritance only when I need dynamic dispatching. In other words, if I expect the new class to run on the original class' wire, then I subclass. Otherwise I use composition and delegation. It would be eternally helpful to do a post mortem on what the *right* solution would have been if you're able to share. (Or if Shaun is game, let's tackle his example in post #23) Perhaps, we decide the 'correct' solution is syntactically way too burdensome and in the architectural atmosphere, but at least we would feel less icky about the solutions as developed. It will come as no surprise to anyone that I'm more than happy to talk endlessly about architectural designs. I don't have any examples where I've wanted to change the overriding method's conpane at hand... and any that I dig up I'd be unable to share on a public forum. We'd have to dig into the code details enough for me to be uncomfortable with breaking client confidentiality. I'm willing to do a design review on Shaun's code (assuming he's able to share,) or NotifyUser, or ProbeDisplay, or anything else you can come up with (time permitting of course.) If you want to take it offline and review production code that led to this request we can do that too. And FWIW, often the "correct" solution *is* too burdensome to implement. The benefit of knowing the correct solution is you can confidently pick an appropriate less correct solution for the given situation, and more importantly, you can tell when your implemented solution is going to become a hinderance and refactor it into the correct solution. Quote Link to comment
ShaunR Posted February 6, 2013 Report Share Posted February 6, 2013 (edited) Now, with "Must Implement", we have the ability to contractually require functionality in subclasses while maintaining the ability to extend parent class functionality, having acknowledged that Dynamic Dispatch is the incorrect tool when distinct function interfaces are desirable.Of course. A polymorphic VI has the feature that although you must have identical conpane layouts and directions. You can have different terminal types and numbers of defined terminals. However, the info for selecting the method is by the type wired to it or selection by the developer. In my example, I would just be requiring that class behaves like a polymorphic VI at run-time and method selection is dependent on the object rather than the data type wired to it. (I in fact, see no semantical difference between a polymorphic VI and a DD class except the choice mechanism). Edited February 6, 2013 by ShaunR Quote Link to comment
drjdpowell Posted February 6, 2013 Report Share Posted February 6, 2013 Consider the class, NotifyUser.lvclass ... Looking at this one, I would get rid of the child classes entirely and just have two VIs, “DisplayMessage-Bool” and “DisplayMessage-Numeric”. But this is perhaps because the example is (as most examples are) a very simple case. Even with child classes, though, I would just rename the methods. Now, with "Must Implement", we have the ability to contractually require functionality in subclasses while maintaining the ability to extend parent class functionality, having acknowledged that Dynamic Dispatch is the incorrect tool when distinct function interfaces are desirable. I’m not sure it is desirable to "contractually require functionality in subclasses”. That’s the author of the parent class trying to constrain and dictate the design of every yet-to-be-thought-of child class. Too easy to over-constrain. “Must override” and “must call parent” are different in that they are often actual requirements to make the child class function with parent-type code, and thus it is reasonable for the parent’s author to dictate this. Quote Link to comment
Daklu Posted February 8, 2013 Report Share Posted February 8, 2013 It would be eternally helpful to do a post mortem on what the *right* solution would have been if you're able to share. Okay, I found some code from a couple years back we can pick apart. It contains some extra complexities, but it's real code--I wrote it originally intending to eventually release it--so maybe it will give us some more insight into the issue with Must Implement. I'll let you decided if it's close enough to your use case to be worth discussing. The attached project (LV 2009) contains one of my early exploratory attempts at creating a List collection for LapDog. Lists behave similarly to arrays, but they don't allow or require the same level of detailed control so they can be easier to work with. The goal was to permit end users to customize both the implementation (to improve run-time performance) and the interface (to permit strong typechecking) independently of each other. The List class (and its future subclasses) is the interface the client code would use. The default data type for Items in the list is LVObject. I had hoped to be able to subclass the List class to support different native data types so client code wouldn't get cluttered up with boxing and unboxing. The ListImp class (and its future subclasses) contain the actual data structures and implementation code. The default implementation class, ListImp-Array, uses a simple array as its data structure. Subclasses could implement a buffered array, a queue, a hash, binary tree, or whatever data structure necessary to fit the specific requirements. Users select which implementation they want to use when the List collection is created. (Dependency injection FTW.) I don't think the ListImp class is terribly relevant to this discussion. (FYI, List and ListImp do not have a parent-child relationship.) I was aiming for a high degree of flexibility--I wanted users to be able to mix and match an arbitrary type-specific List subclass with an arbitrary implementation-specific ListImp subclass. You can see a few of the List method names have "(LVObject)" appended to them. That's an indicator that those methods either accept or return an Item. These methods are type-specific. Suppose I create a List-String subclass designed to handle string Items instead of LVObject Items. Obviously List-String.Insert cannot override List.Insert because it will have a different conpane. I adopted that naming convention so child classes could create a non-overriding Insert method that won't have a name collision with the parent class. ("List-String.Insert(String)" or something like that.) Does that work? Mechanically, yeah, it works. Does it gain anything? **shrug** I don't think it's a very good solution, but I guess that's what we're discussing. How would you use Must Implement to solve this problem, and are there any better solutions you can think of? (I do have a redesign plan in mind, but I'd like to see what other people come up with.) Collection-List v0.zip [Note: This code is not intended for production use. While I hope to add collections to LapDog someday, the attached project is exploratory code and is not be supported.] 1 Quote Link to comment
Rolf Kalbermatter Posted February 8, 2013 Report Share Posted February 8, 2013 The List class (and its future subclasses) is the interface the client code would use. The default data type for Items in the list is LVObject. I had hoped to be able to subclass the List class to support different native data types so client code wouldn't get cluttered up with boxing and unboxing. The ListImp class (and its future subclasses) contain the actual data structures and implementation code. The default implementation class, ListImp-Array, uses a simple array as its data structure. Subclasses could implement a buffered array, a queue, a hash, binary tree, or whatever data structure necessary to fit the specific requirements. Users select which implementation they want to use when the List collection is created. (Dependency injection FTW.) I don't think the ListImp class is terribly relevant to this discussion. (FYI, List and ListImp do not have a parent-child relationship.) I don't really have an idea how to do it better in LabVIEW, but this use case is specifically, what Generics are for in Java and .Net and I suppose templates in C++, although the template mechanism seems so involved to me that I never tried to understand it nor have used it. The one limitation at least in Java is, that generics only work with object datatypes, not on the primitive datatypes. That is sometimes rather inconvenient but Java also has object types for its primitive datatypes so it's possible to get around that, yet using object types everywhere for primitive datatypes including in arrays and such can have a significant memory impact. Quote Link to comment
shoneill Posted February 8, 2013 Report Share Posted February 8, 2013 I would say that you need a base List class for each different datatype you want to support. If you do it via inheritance then your String class still has a method to insert Objects which can violate the "String" idea and might lead to problems elsewhere. It's just not a clean implementation. This (in current LVOOP) requires more or less a COPY of the base class for String, DBL, LVOOP Object and so on. If you want strict typing to a specific LVOOP class, then you need a base class for that otherwise your back door "LVObject) will remain open always. Either that or simply input EVERYTHING as an object. Write a set of simple wrapper classes (Yeah, it's a pain) and simply use the LVOOP insert everywhere. You can define the Methods Insert (String) if you like, but I would have them use the LVOOP method in the background. Templates create (if I'm not mistaken) new entities from a generic data type which shares no inheritance relationship to it's cousins. This is similar to simply replacing the datatype of the base class in your example. Instead of having some "void" datatype, simply create a template class with String and replace as required. Would it be nice if this could be automated / sped up with LV, sure. That's maybe a good suggestion for the Idea Exchange. For me Templates are (regarding the end result) similar to my first point above (Different base classes for each datatype). Sometimes we get so LVOOPy in our heads we try to do everyhting WITHIN the Hierarchy. Sometimes the answer is to copy the hierarchy and be done with it. Create the new classes as required instead of trying to cover all bases from the beginning. And yeah, I'm always traing to over-inherit such functionality myself. Just my 2c. Shane. Quote Link to comment
Daklu Posted February 8, 2013 Report Share Posted February 8, 2013 ...this use case is specifically, what Generics are for in Java and .Net... Yeah. There are a few of us around who keep hoping Generics (and Interfaces) show up in Labview, but not enough to push the idea up far enough on the idea exchange. It's a little discouraging... LVOOP has been stable since 2009, and here we are 3.5 years later with no significant additional object-oriented capabilities. I don't fault NI for choosing their business priorities based on user feedback, but it's still discouraging. I would say that you need a base List class for each different datatype you want to support. If you do it via inheritance then your String class still has a method to insert Objects which can violate the "String" idea and might lead to problems elsewhere. It's just not a clean implementation. This (in current LVOOP) requires more or less a COPY of the base class for String, DBL, LVOOP Object and so on. If you want strict typing to a specific LVOOP class, then you need a base class for that otherwise your back door "LVObject) will remain open always. That's exactly what I had in mind. Instead of using inheritance to create my StringList class, use composition. Have the StringList methods do the boxing and unboxing and delegate the rest of the functionality to the String class. I fully agree with your judgement that being able to insert objects into a string list will likely lead to problems, and that having that ability does not make for a good api. Using the language from my previous post, it's clear I wanted StringList to modify the behavior of the List class, but in reality it's extending the behavior to work with a different Item type. This is also apparent once you realize there are no non-trivial implementations where a StringList object would run on a List wire, due to the different Item type. (Child object on parent wire = modifying behavior. Child object needs its own wire = extending behavior.) Either that or simply input EVERYTHING as an object. I've considered creating a thin library of native type objects. The problem is that they require too much boxing/unboxing to use with Labview's built-in functions. I'd also have to build object-based versions of all the string functions, all the math functions, etc. Thanks, but I'll pass. I'd like to see NI implement objects for native types, but I won't hold my breath. (To be honest sometimes when I need an objectified native type I'll cheat and just use a message object from LapDog.Messaging.) That's maybe a good suggestion for the Idea Exchange. It's been on there in a couple different variations since 2009. But since it's an advanced feature relatively few LV developers want, it doesn't get the votes to push it up the priority list. I know R&D is aware of the issue and I know it is a hard problem to solve. I don't know if the lack of a solution to date is because it is too difficult or just because it hasn't been prioritized. And yeah, I'm always traing to over-inherit such functionality myself. I've mentioned this on other threads before, but it's very common for new OO developers to overuse inheritance. (I did it alot when I first started and obviously I still fall into that trap sometimes.) It's not surprising why that happens. Pretty much all "Intro to OO Programming" material ever written focuses heavily on inheritance. It leaves the impression that inheritance is what OO programming is all about. I built plenty of bad class hierarchies before I realized the parent-child relationship is actually a fairly restrictive one and my goals were usually better served by using composition. Quote Link to comment
Daklu Posted February 10, 2013 Report Share Posted February 10, 2013 Here's a rewrite of the List Collections using the design I described earlier. There are no UML diagrams or external docs describing the code, so to give a little overview, there are three projects in the zip: ListsCollections.v0.Lists.lvproj Source code for the libraries that make up the LapDog.Collections.v0.Lists namespace. The libraries are: LapDog.Collections.v0.Lists - This is the main library for all list collections and contains the root interface class (List) and the root implementation class (ListImp.) LapDog.Collections.v0.Lists.Implementations - Optional List implementations subclassed from ListImp that can be injected to meet specific performance requirements. Currently only contains one alternate implementation, BufferedArray. This library depends on the Lists library. LapDog.Collections.v0.Lists.Interfaces - Optional List interfaces that can be used to enforce type safety. Currently contains only one alternate interface, StringList. This library depends on the Lists library. ListsexamplesList Examples.lvproj Currently this project only contains two vis I created for benchmarking; however, you can look at them and see how easy it is to swap out a different implementation if you need to fine tune performance characteristics. This buffered array implementation works really well on tail inserts, but head inserts are absolutely atrocious. ListsdevtestListTesting.lvproj This project contains the unit tests for the LapDog.Collections.v0.Lists namespace. You're free to look at them--just make sure you have JKI's VI Tester installed. Lists.zip Quote Link to comment
JackDunaway Posted February 13, 2013 Author Report Share Posted February 13, 2013 Okay, I found some code from a couple years back we can pick apart.... I want to acknowledge this, and will get back with a proper response (after the mad rush to the #CLASummit). In the meantime, here's a fun article. It's an ironic allegory comparing Functional to Object-Oriented programming: http://steve-yegge.blogspot.de/2006/03/execution-in-kingdom-of-nouns.html A few great quotes: One of our first instincts as human beings is to find shelter from the elements; the stronger the shelter, the safer we feel. In Javaland, there are many strong things to make the citizens feel safe. They marvel at the massive architectural creations and think "this must be a strong design". This feeling is reinforced when they try to make any changes to the structure; the architectural strength then becomes daunting enough that they feel nobody could bring this structure down. ...advocating Object-Oriented Programming is like advocating Pants-Oriented Clothing. This article comes down a little hard on OOD, but I think its in good fun; his conclusion and the spirit to to embrace the merits of both OO and Functional! They're not mutually exclusive! Quote Link to comment
Daklu Posted February 13, 2013 Report Share Posted February 13, 2013 This article comes down a little hard on OOD... I read it more as a Java criticism than an OO criticism. Quote Link to comment
Rolf Kalbermatter Posted February 14, 2013 Report Share Posted February 14, 2013 I read it more as a Java criticism than an OO criticism. I also read some criticisme about OO. Not that it says functional programming is better than OO programming. Neither is better than the other in general AFAIC, but many OO fanatics tend to pull the OO sword for everything even if a simple functional approach would be much easier and quicker. OO has its merits but making functional programming more or less impossible like Java actually does is simply taking the idea over the top. And that are not the only issues I have with Java, but I haven't thrown it away yet. Quote Link to comment
flintstone Posted February 14, 2013 Report Share Posted February 14, 2013 There's roughly a bijillion issues with template classes. And still the STL is one of the mostly used programming libraries around, might have a bigger user base than LV overall. @topic: I do not get why a parent class should give restricitions to a derived class. What problems can be avoided by that? The child class may still implement the functionality errornously. And you as the parent class designer do not know now what your class might be used for in e.g. three years from now (if you still can open it in your then current version of LV ). If the child class programmer does it wrong, that's it, now flag will give you any additional safety there. Cheers, flintstone 1 Quote Link to comment
drjdpowell Posted February 14, 2013 Report Share Posted February 14, 2013 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) I assume you meant “Must Override”; I don’t use that much, but I’ve never had experience with writing parent classes for other designers to make children of, so I can’t really comment on that. As far as “Must Implement”, I can’t really think of a good use. Child classes can be used for purposes that are different from what the designer of the parent class envisioned, so adding restrictions at that stage seems inappropriate. Especially ones that can be easily broken by adding a not-to-be-used extra methods (what icon glyph should we have on VIs never meant to be called but needed to satisfy misguided restrictions?). Quote Link to comment
Aristos Queue Posted February 14, 2013 Report Share Posted February 14, 2013 For the "too long; didn't read" crowd, just read the four boldface sentences. The rest is explanation. :-) @drjdpowell and @flintstone: A child class that does something the parent never intended is a bad child class in almost all cases. The phrase "And you as the parent class designer do not know now what your class might be used for in e.g. three years from now" is false. The parent class designer establishes, in stone as it were, exactly the uses of a child class. Why? Because code will be written that uses the parent class and only the parent class, and that code knows nothing about the children that will eventually flow through it. All children are expected to match those expectations or they are going to have runtime problems. The more you can convert those runtime problems into compile time problems by letting the parent declare "these are the requirements", the more successful the authors of the child classes will be. This is true whether you are one developer working on an app by yourself or whether you are writing a framework for third party developers to plug into. A child class needs to match it's parents for Identity, State and Behavior or else a child cannot be effectively used in any framework written for the parent. The parent defines the invariants that all child classes will obey -- that's what allows frameworks to operate. The more that a language allows a parent to say "these are the exact requirements needed to be a well defined version of myself", the more power the language has to build frameworks that are guaranteed to work out of the box. The parent designs for "the children will have free reign to do whatever they want here" and "the children will do exactly this and nothing else here". I'll give you an example that we were discussing yesterday: dynamic dispatch on FPGA. At the moment, the parent implementation of a dynamic dispatch method just defines the connector pane of the method. It does not define the cycle time of the method. In order to write an FPGA framework where any child class can be plugged in and the framework works, there are cases where you need to be able to guarantee that the child override will execute in the same number of clock cycles as the parent implementation defines. Essentially, the parent implementation needs a way to say "I have three Feed Forward nodes on my diagram in series between this input and this output. I require all overrides to maintain the same amount of pipelining... their diagrams must have exactly three Feed Forward nodes in series." We were discussing ways to add that restriction declaration to LabVIEW and whether the compiler could really check it. I have plenty of other examples, from many languages, of parent classes that want to define particular limitations for children. LabVIEW has the scope restrictions, the Must Override restrictions, the DVR restrictions [particularly the one that says this one ancestor is the only one that can create DVRs for all the descendants]. When we someday have template classes, we'll have the ability for the parent class to set type limits, just like every other language that has templates. If you are defining a class that is a lot like a parent but violates Identity, State or Behavior, do not use inheritance; use containment instead. Delegate to a contained instance of the parent class when (if) appropriate. Or define a new grandparent class for the parent and move the common functionality up to the grandparent such that the invariants of the parent class are unchanged and you can now inherit off of the grandparent to get ONLY the functionality that your new piece requires. > (if you still can open it in your then current version of LV ) We just last year walked a LV 4.0 VI all the way to LV 2012. It opened and ran just fine. You have to open it in LV 6.0, then LV 8.0 then LV 2012, as those are the defined load points, but the mutation paths have been fully maintained. 1 Quote Link to comment
ShaunR Posted February 14, 2013 Report Share Posted February 14, 2013 We just last year walked a LV 4.0 VI all the way to LV 2012. It opened and ran just fine. You have to open it in LV 6.0, then LV 8.0 then LV 2012, as those are the defined load points, but the mutation paths have been fully maintained. Off topic (apologies). Is 2012 a load point? Or just that you loaded it finally in 2012? More generally. At what version is it planned that 2009 vis will not be loadable? Quote Link to comment
Aristos Queue Posted February 14, 2013 Report Share Posted February 14, 2013 but many OO fanatics tend to pull the OO sword for everything I do pull the OOD for everything. I do NOT pull the OOP. That's the huge mistake that JAVA makes. When planning out a program, being able to say what object each piece of data is associated with gives you an organizational power that I haven't found anywhere else. And once you're done with the planning, you look at the plan and say, "It would be ridiculous to build an entire class for this concept, so I'm not going to do it and just write a function for that thing." Having said that, it does many programmers good to spend some time operating in the world where "you will only have functions that are members of classes." That whole "everything has a place" aspect of JAVA is actually a really valuable perspective, and in my experience, code written in any language from programmers who have spent time in JAVA is cleaner than code written in any language by programmers who have only used free spirit languages like C++ or LabVIEW. The other language that provides a needed discipline is LISP. I'd fully support any CS program that said "all freshmen will write in JAVA and LISP, alternating each week, and only then do we put the less-regimented languages in your hands." Unfortunately, most schools only do the JAVA part. And they never get around to handing the students LabVIEW. *sigh* Off topic (apologies).Is 2012 a load point? Or just that you loaded it finally in 2012? More generally. At what version is it planned that 2009 vis will not be loadable? No, 2012 is not a cut point, just the latest version at the time. We aim to maintain it as long as is practical, and we have at this point maintained backward load support longer than at any other time in LV's history, so far as I can tell. I suspect the current load support will go for quite some time because there's not really a problematic older feature that all of use in R&D want to stop supporting. 2 Quote Link to comment
ShaunR Posted February 14, 2013 Report Share Posted February 14, 2013 No, 2012 is not a cut point, just the latest version at the time. We aim to maintain it as long as is practical, and we have at this point maintained backward load support longer than at any other time in LV's history, so far as I can tell. I suspect the current load support will go for quite some time because there's not really a problematic older feature that all of use in R&D want to stop supporting. Phew. Many thanks for clarifying. Quote Link to comment
flintstone Posted February 14, 2013 Report Share Posted February 14, 2013 @Aristos Queue: Of course a parent class has to limit the way the child classes behavior but wrong restricitions will lead to unusable results. I'll give a made-up FPGA example (all Altera stuff): You designed a component, that takes a stream of input data, processes them and outputs them as a stream again. Your implementation needs n clock cycles until output data starts so you restrict all derived code so that it must present data within exactly n clock cycles. Now somebody wants to derive from that code and attach it to an FPGA soft-core processor (NIOS). He adds the necessary stuff for an Avalon-ST interface but this consumes another clock cycle. This is no problem for the system, which uses streaming interfaces everywhere, the Avalon-ST and the NIOS drivers will do everything for you. The "n clock cycles" restriction is obsolete now, yet it prevents the code from being used. This is, as I said, made up because OO programming for FPGAs in textbased languages is not used apart from the simulation realm. But it's a perfect example for how the world of FPGA programming changed and the way components were done some years ago does not fit todays way of building systems there. Cheers, flintstone Quote Link to comment
drjdpowell Posted February 14, 2013 Report Share Posted February 14, 2013 AQ, Good arguments for “Must Override”, but does that have anything to do with “Must Implement”? A child must still respect the requirements of parent code, but “Must Implement” tries to define what can be done for child-type static methods that are independent of parent code. 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.