Continued from NodeJsAndHofDiscussion, which is growing TooBigToEdit.
The desktop applications mostly used OOP for their GUI eventing, not HOF's. And if you want to sell NodeJS to wider audiences, you better get better examples.
Sure, OOP for GUI eventing is fine, though the anonymous classes with a single function that are pervasive in (say) Java would be syntactically cleaner with HOFs and soon will be, given that Java 8 supports lambdas. By the way, I don't want to sell NodeJs to anyone. I'm not a salesman (well, any more -- I have been), I have no interest in sales. There is no "wider audience" for NodeJs, beyond those who need to create event-driven, responsive, highly-concurrent user interfaces. I'm sure that audience can find their way to NodeJs without my help.
"Syntactically cleaner" is often in the eye of the beholder.
That may be true in some general sense, but I think there are few who would argue that a traditional, idiomatic Java event handler like the following...
myButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myLaunch(e.getWhen());
}
});
...is even as clean as -- let alone syntactically cleaner than -- the same code using a Java 8 lambda:
myButton.addActionListener(e -> myLaunch(e.getWhen()));
What's wrong with:
<button id="buttonX" title="click me" onclick="myLaunch" ... />
...
class myLaunch inherits GUIevent {
main() {
console("Clicked on: " & self.eventTime);
console("Mouse coordinates: " & self.pointerX & "," & self.pointerY);
}
}
"myLaunch" would inherit from a GUIEvent class that automatically supplies click time as a class-defined attribute (as well as other commonly needed event attributes such as clicked object ID, mouse coordinates, etc.). No need to dick around with "add listener" bureaucracy. (That stuff may be under the hood, but the app developer rarely has to open the hood that far.) With your approach, you'd have to visit each call to add any new parameters, such as mouse coordinates. Another approach:
<button id="buttonX" title="click me" onclick="myLaunch(self.GUIevent)" ... />
...
function myLaunch(e) {
if (isDefined(e)) { // "e" is an optional gui-event info object
console("Clicked on: " & e.eventTime);
console("Mouse coordinates: " & e.pointerX & "," & e.pointerY);
}
}
What's wrong with it is that it's not Java. It would, at least, require corrections to the syntax so it's Java, and some XML-to-Java compiler to convert the UI specification to Java. What you call the "'add listener' bureaucracy" is conventional use of the PublishAndSubscribe-like event handler pattern, which allows multiple, independent handlers to act on a single event without having to be related to each other. Could you explain how your approach is syntactically cleaner, or superior to either the usual Java convention of using an anonymous class, or the Java 8 approach of using a HigherOrderFunction? By the way, my GUI example was only intended as an easily-recognisable illustration; it's not the only use of lambdas. Whilst use of Java 8 lambdas will unquestionably simplify GUI coding, they'll simplify any similar use of what would otherwise require (at least) an anonymous class.
One more thing: if myLaunch() needs all the information that parameter 'e' (an ActionEvent, in the above) can contain, you simply pass it as an argument to myLaunch(). The Java 8 lambda example would be:
myButton.addActionListener(e -> myLaunch(e));
If 'e' isn't needed by myLaunch, it could be:
myButton.addActionListener(e -> myLaunch());
I'm not limiting the discussion to Java; I thought we were talking
general GUI approaches, not a specific language; for I don't want to debate about specific languages. For most "click on" events, we don't need to see that stuff. If it uses
PublishAndSubscribe (or what-not) under the hood or further down the abstraction ladder, that's fine, but don't expose it to the most common case if the most common case doesn't need to show/expose it.
The GUI designer is usually thinking, "I just want to run a snippet of code if the button is clicked", and it may optionally need typical GUI-related info such as the time, coordinates, and target id/ref of clicking, for example. (I modified one of my examples to make the event info parameter optional). In the spirit of WorkBackwardFromPseudoCode, I'm simplifying the common case. I don't want distracting wires from the underlying infrastructure hanging out.
The GUI designer doesn't deal with code. He or she defines forms or Web pages using code or a forms painter / page designer or a wire framer; the most technical aspect of the job is giving each UI element a unique name. It's the programmer who pairs element names with runnable code. Of course, sometimes the programmer is also the GUI designer, but not always, so design and coding should be clearly separable.
- Even if it is separated, I don't see how that changes the "score". If you mean the GUI specification by the GUI designer shouldn't even mention code and that the non-GUI app programmer should do the wiring of any GUI objects to event handlers, that may be true, but I don't see how that changes much. The non-GUI programmer could perhaps have an interface like this:
class buttonFooClick inherits guiEvent;
self.objectToWatch = "buttonFoo"; // ID as defined in GUI XML
self.addClickWatch(); // tell it to watch for clicks
self.addDoubleClickWatch(); // we'll also watch for DC for the hell of it
main() {
console("Clicked button foo at " & self.eventTime);
}
}
// fancier versions may be able to watch multiple objects via an addIdToWatch() method[1]
.
// Variation B
class buttonFooEvents inherits guiEvent;
self.objectToWatch = "buttonFoo"; // ID as defined in GUI XML
method onClick() { // override stub method
console("Clicked button foo at " & self.eventTime);
}
method onDoubleClick() {
console("Double-Clicked button foo at " & self.eventTime);
}
}
Java or not, I still don't see how your examples are syntactically cleaner -- remember, that's what started this thread -- than using a HigherOrderFunction.
Maybe that part is subjective. I find it more natural and suspect most developers will agree.
What is it about buttonFoo.addClickListener(e -> console("Clicked button foo at " & e.eventTime)) that is less natural than the above?
For one, it's not clear what "e" is, nor how or where buttonFoo is defined.
Where buttonFoo is defined isn't shown in the anonymous class example, either. We assume it exists. The parameter name 'e' was chosen to match the anonymous class example, but it probably should be something more readable. How about this: buttonFoo.addClickListener(actionEvent -> console("Clicked button foo at " & actionEvent.eventTime))
- If the button already defined, then there can be an "onClick" method to be added (overridden) and thus you shouldn't count the overhead of defining a class. (An OOP library may include multiple ways to do that same thing to fit different styles or GUI-IDE's).
class buttonFoo inherits gui::button { // Example 8462
self.location = [etc.];
self.title = "Click Me!";
method onClick(event) { // override stub handler method
console("Clicked button foo at " & event.eventTime);
}
}
// See footnote [1] for an instantiation note.
- If you're handling events through overriding methods, then you can only have one handler per UI widget. That doesn't work. In many cases, the GUI framework needs to apply its own event handlers to widgets. By the way, your example above has defined a class of buttonFoo. What if you only need an instance where the only customisation is the event handler? Not sure what you mean by "the overhead of defining a class".
- What precludes additional handlers? The "onClick" method may register an event handler or listener behind the scenes but that doesn't prevent further handlers from being registered for the same event and/or object. The "onClick" method is just a handy short-cut for common usage patterns.
- I thought your onClick override was the only event handler. Certainly, if it's used in addition to registering listeners, that would work. I'm not sure what it gains, though, to define every button as a new class -- especially if there are a lot of buttons.
- Where else should most or all of the button-related code for a given button go? I expect you agree it should be in some kind of module because we probably wouldn't want to scatter it all about. (One shouldn't have to explicitly "register listeners" most of the time. I don't know where that exposure fad came from. I'm not against having a central "listening" registry (table); it's just the registering steps should be automatic, behind the scenes for most of the common API calls.)
- Code related to customising a button or a category of buttons certainly belongs in a new class derived from the base button. However, an event handler isn't a customisation, it's an attribute of a button. (A button without an event handler isn't a button, it's a decoration.) The code that's invoked by the button's event handler usually belongs to the form containing the button, because it will typically affect the form and not (just) the button. Use of listeners is an idiomatic Java pattern, reflective of its philosophy that 1 item shouldn't be handled any differently from 'n' items. In other words, the Java philosophy is that a single mechanism should be able to define 'n' event handlers, whether 'n' is 1 or 100.
- What do you mean by "belongs to" in code terms? Buttons "belong to" a form. And I disagree with that "handled any different" if it conflicts with "make the most common situation the simplest". The second is too useful, and usually outweighs the first in my experience. The vast majority of time we define one and only event handler code snippet for a button, and that's clicking. And it does not preclude further events IF we do want them, it's just that we don't optimize the primary interface for multiple at the expense of one.
- In code terms, I mean the code invoked by a button's event handler is defined by one or more methods of the class that defines the form. As for simplicity of code, the Java 8 lambda appears to be the simplest, certainly more so than either the traditional anonymous class or your approach of defining each button as a derived class with an overridden 'onClick()' method.
- Doesn't look simpler to me. I don't have to mention listeners, for one. That's lower-level wiring that shouldn't "stick out" in most cases, and is perhaps swappable with other techniques. I'll LetTheReaderDecide whether they like the click handling code in the derived button class or the derived form class. I like it how I like it and you like it how you like it and the reader can decide which they like. (Preferably I'd like it all TOP-based over OOP, so that the grouping is a custom view rather than something hard-wired into code, but that's another topic.) -t
- Several lines doesn't look simpler than one line and fewer tokens? "Listener" is merely Java vernacular for "event handler".
- It can be in-lined (depending on the language), but cramming too much into a single line is often a poor coding practice in my opinion. Readability and "compact" are not always the same thing. In my experience, blocks and block-like structures are easier to read for a typical developers if they follow a pattern similar to:
block X (optional stuff) { // example 4726
block body
} // end block X
- What's crammed? In the Java 8 lambda examples, it's just the event handling code -- same as the usual anonymous class based event handler -- with the anonymous class scaffolding removed and replaced with a terse parameter list and a '->'. It's the same code, essentially, but shorter. If anything, it's less crammed than the usual anonymous class based event handler.
- Why require minds to toggle between OOP and HOF's when they often cover mostly the same territory? That tends to confuse typical programmers and increases the experience requirements of staff. Keep the conceptual tool box simple by avoiding excessive overlap. Saving a few lines of total code may cost in other ways. If your particular mind and/or team can freely toggle between them without problems, that's great, give yourselves gold stars on your foreheads. But that may not scale to most shops.
- Why require minds to toggle between 'for' loops and 'while' loops when they often cover mostly the same territory? That tends to confuse typical programmers, etc.
- I don't see that in practice. Anyhow, this seems to be degenerating into our usual pattern of debate and is already covered in other topics such as GreatLispWar and StaffingEconomicsVersusTheoreticalElegance.
- My point is that why should a lambda expression be more difficult to understand than an anonymous class with a method -- i.e., a FunctorObject -- when a 'for' loop isn't any harder to understand than a 'while' loop?
- My point is why have them when OOP can do just fine such that we don't have to mix two and hire and learn two, and mentally toggle between techniques? Just because YOU can (allegedly) handle inconsistency does not mean every one can. YOU are not the world. ParadigmPotpourriMeansDiminishingReturns. And maybe we can do without a FOR loop; I've never tested it or given it much thought. maybe while(for(&i,1,50)) {...} is fine once you get used to it.
- Why have HigherOrderFunctions? Because they simplify more than just the event handlers shown here. They close over their context, which makes references to an environment implicit rather than (sometimes laboriously) explicit. They can be used generatively, to produce a series of functions under program control, which (where appropriate) can eliminate redundancy and repetition. They can be used instead of dynamic construction and execution of program source code via 'exec()' mechanisms, thus providing TypeSafety, better performance (no parsing and interpreting/compiling needed after initial construction), and avoiding risk of code injection.
- Yes, you can do without FOR loops (though your example of doing without FOR loops seems to have a FOR loop inside a WHILE loop -- Huh?) and use only WHILE loops. You don't even need IF statements, if you have WHILE loops and boolean variables or a 'break' statement to exit the loop. You can write any program with only some I/O primitives, WHILE loops, variables and variable assignment, and some comparison operators. Of course, you don't need high level languages. You can write programs using nothing more than cellular automaton primitives, which are lower-level than any assembly language. However, that would be unreasonable. Every construct above that level (or, pragmatically, above the assembly language level) exists to make programming easier. FOR loops make WHILE loops easier. Are they needed? No. WHILE loops are sufficient, but FOR loops make certain code easier. Likewise, HigherOrderFunctions aren't needed, but they make a variety of operations easier.
- The "for" was intended to be a library function, as opposed to a dedicated loop type (3rd optional parameter would be increment amount). FOR came into existence because early languages were math-centric, and matrix and array traversing was very common. And your evangelizing about HOF's is getting repetitious and tedious. Your GUI demo failed in my book: a SLIGHT code-size reduction, but at the expense of taxing WetWare and training. OOP does the button event perfectly fine, clean, and simple. I'm confident a jury of typical developers would agree me and vote you off the island. Use HOF's to factor your evangelism text if they are such great abstraction tools. Claim they cure world hunger, end racism, prevent rashes, and rescue kittens from trees, while you are at it. I don't want to sit thru yet another failed blowhard demo of yours; I'd rather talk to a used car dealer.
- "For" was called "for" (originating from German "für", appearing in Superplan in 1951) in Algol 58 because it executed the body of the loop once for each value of the loop variable. I'm not "evangelizing", simply pointing out the benefits of HigherOrderFunctions. I can do the same for structured FOR loops and IF statements compared to GOTOs, but then so could you, I'm sure. If you did so, would you be evangelising FOR loops and IF statements?
- I believe Fortran predates Algol, and it had it, but called it "do" instead. Still, Algol probably targeted a similar market, as the military and scientific research were the primary domains back then. And blocks are "better" than GOTO's largely because indentation gives the flow a visual "shape". GOTO is not visual (in most languages). This is similar to why "method" blocks are cleaner than HOF's, per above near example 4726. HOF's make for syntactically and visually sloppy blocks because they are often nested in function calls.
- [But you get identical syntactic cleanness for higher-order functions and for other block structures in RubyLanguage and CoffeeScript -- as well as in somewhat more obscure languages, such as JuliaLanguage. The decorator syntax in PythonLanguage also provides extremely similar functionality with extremely normal-block-structured syntax. JavaScript's tendency toward syntactic mess isn't characteristic of higher-order functions, just of JavaScript. Why make a grand, universal claim about something that only really applies to one significant language? -DavidMcLean?]
- I'll grant you that, but the same applies to OOP syntax.
- [Probably. What do you mean, exactly? For instance, are you saying OOP syntax can be made cleaner for the same uses we're recommending higher-order functions? This is obviously true, but doing so is literally the same thing as providing clean first-class function syntax -- witness Java 8's lambdas, which are sugar for anonymous inner classes but are primarily a clean anonymous function syntax -- and so isn't really a point against the higher-order function approach. -DavidMcLean?]
- So it mostly comes down to personal preference.
- [Where'd you get that? You can't really fine-tune OOP syntax to make solving higher-order-functiony problems as clean as doing it with actual higher-order functions, because if you try you're just providing syntax for doing it with actual higher-order functions. The fact that functions are also objects in OO languages is irrelevant; you're still using functional design techniques, you still need to comprehend all the implications of first-class functions, and so on. If you have a syntax in mind that'd gear OOP towards these sorts of problems better without literally just becoming first-class functions, feel free to present it. -DavidMcLean?]
- The only real "size" difference is that methods require a name. That's a good thing overall. One can perhaps squeeze size pennies out by skipping that, but to complicate the tool set and training requirements to chase pennies like that is not worth it overall. Consistency simplifies longer-run maintenance in my experience, and flipping among paradigms works against that.
- [Well, no, the difference in size is more than requiring a name. Recall the Java examples given above:]
/* with anonymous inner class */
myButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myLaunch(e.getWhen());
}
});
/* or with Java 8 lambda */
myButton.addActionListener(e -> myLaunch(e.getWhen()));
.
- [The anonymous inner class, which itself actually exists primarily as a syntax for approximating anonymous functions anyway, clearly has a lot more bloat relative to the lambda version than merely a method name. As for your comments on "flipping among paradigms"? When you're working in certain fields, it's pretty much impossible to avoid working with certain functional techniques - eventing is a major example of such a field, since without some means of passing code-to-run-on-event as a first-class value it becomes remarkably inconvenient to express handlers. You can't escape this "paradigm-flipping" by using OO-ier syntax, since either way you're still working with the same concept - a higher-order function - and just using different words to refer to it. -DavidMcLean?]
- I'm assuming a good OOP framework, not the poorly designed one that typically comes with Java. It's usually possible to make a framework that makes a given technique score poorly. I thought we agreed on the reference example, why are we going back to the old trashy one? I'm also assuming the button class already exists to represent the button. Thus, you are comparing two apples to one orange. And if the method is doing the "same concept", why complain about it? If developers prefer the OOP version, then let them be instead of fanboy-sales-nag them.
- [I won't deny that Java's less than perfect; what OOP design do you have in mind? Of course a button class already exists. The examples above presume not only the button class but an actual button instance already exist. How's that relevant? As for your question, I'd "complain about it" because it's the same concept but expressed in a messier, more verbose way, as shown in the examples I just quoted. Why not encourage cleaner, shorter syntax for such things? -DavidMcLean?]
- I showed it already at Example 8462. We don't have to "talk to" the action-listener directly the vast majority of the time.
- [Uh, why would you want a design like that? Why would you want a separate class for every instance of a widget, rather than classes for, y'know, classes of widgets? What benefit does that afford, if any? -DavidMcLean?]
- Didn't we have that discussion already? See above near "Where else should most or all of the button-related code for a given button go?"
- [Ah. No, we didn't have that discussion, although you had it with someone else. And it looks like you already resolved that particular question. "In code terms, I mean the code invoked by a button's event handler is defined by one or more methods of the class that defines the form." -DavidMcLean?]
- Not sure that's a "resolution". If you have specific practical complaints against having a button class per button, please state them.
- [Sure. It's unclear how and when instances of per-widget classes are instantiated - with classes representing widget types, instances of the class represent instances of the widget and naturally are instantiated to produce the relevant widget, but if classes represent individual widgets, the purpose of instantiating instances isn't clear, and nor is the meaning of instantiating multiple instances of the same customised button class. Do these button classes demand SingletonPattern? It's also not meaningful to have a base "button" class that cannot of its own accord act as a button - as event handlers are registered by subclassing the button, apparently, the button class itself isn't a button. Additionally, classes are often statically defined, which is not conducive to generating additional widgets on-the-fly; of course, the more dynamic languages let you create extra custom classes at any time, but rarely frictionlessly. There's a few, off the top of my head. -DavidMcLean?]
- The GUI framework would typically take care of instantiation, and may use SingletonPattern under the hood. And there likely may be ways to dynamically generate widgets (when needed), and the button class may even use that technique under the hood, but directly doing that in app coding should be a relatively rare need for typical development. Sometimes you don't want to make it too easy to "drive off road" in order to encourage developers to stick to the conventions. It's a psychological "herding" in a sense of making the preferred route the most simple and easy. True, it may make it difficult for "rock star" developers to use tighter custom abstractions, but the flip side is that it can reduce MentalMasturbation and related mayhem from those who think they are rock-stars or like throwing monkey wrenches into the works. It's a trade-off, and I've seen what orgs seem to prefer in terms of that balance.
- Side Note: Dynamic languages can blur or eliminate the difference between "class" and "object". In some cases, objects are more or less maps (containing potentially both attributes and function-like pointers or snippets to methods) that automatically traverse up the inheritance hierarchy if a map key (method/attribute) is not found in the current object "node". The "static" nature of classes in static languages is often useful in enforcing or protecting base structures/info/API's, but also can create awkward side-effects in some situations. I've been kicking around the idea of a hybrid dynamic/static language where one can "lock" portions as needed. A "locked" object would resemble a class. But that's another topic. -t
- [Indeed, the distinction between "class" and "object" really isn't that important, and PrototypeBasedProgramming can be a lot more conceptually elegant, as well as more flexible. However, reducing objects to a kind of dictionary -- with method names as keys and actual methods as values -- is a trick reliant on the presence of first-class functions: It's a dictionary with strings as keys and functions as values, which is something you can only have if you're allowed to have functions as values. Thus, again, we're right back to functional techniques! -DavidMcLean?]
- The definitional difference between OOP and FP would probably result in another LaynesLaw mess. Call it whatever you want, I'm just describing the interface that I feel works best for certain things. How it's classified, I'll try not to care, but most would probably call it "OOP" rather than FP. If they are wrong, spank them, not me.
- [I wouldn't want to call the presence and use of first-class functions alone FunctionalProgramming either, since FP is a broad paradigm; the mere presence of first-class functions doesn't imply ReferentialTransparency of those functions, the ability to use non-strict evaluation, PatternMatching for function definitions, algebraic data types, and so on. However, reifying anything and everything as values is certainly closer to a functional technique than an object-oriented one; OO tends to seek objects that can wrap up certain kinds of values (functions being a prime example), while functional programming works with the values directly. (In fact, I'd go so far as to suggest the central tenet of functional programming is "EverythingIsa value".) It's not particularly relevant what "most" would call the reification of functions as first-class values, since regardless of what people call it it's still a functional technique. -DavidMcLean?]
- It may be a "functional technique" AND an OOP technique. Or perhaps FP-ness is not Boolean, but a continuum.
- [Nah, it's a functional technique. It's just that it can be very useful in conjunction with OO. -DavidMcLean?]
- Repetition makes it true. Repetition makes it true. Anyhow, I don't care if HOF's are used under the hood to implement OOP, but at least make doing OOP easy, or at least easy to do "traditionally" without having to expose or directly access the HOF's underneath.
- [Repetition makes nothing true. Truth makes things true, by tautology. Why bother to conceal higher-order functions -- or, more accurately in this case, first-class functions? They're valuable. As evidenced by things like Java 8's introduction of both lambdas and method references, their introduction simplifies certain idioms tremendously, and there's no apparent cognitive penalty relative to the alternatives, especially considering object-oriented development already demands one understands the role and implications of passing blocks of code around, since DependencyInjection and FunctorObjects both perform such a feat. -DavidMcLean?]
- Like I already said and you already forgot, it simplifies things in Java because Java is an awkwardly-designed language. As far as "cognitive penalty", this gets back to measuring average WetWare, which nobody here has the means to do. I'd rather see OOP perfected in a language rather than have crappy OOP and HOF's to patch the bad spots in OOP. Do one well instead both lackluster. -t
- [I've not forgotten that Java is an awkwardly-designed language, but I continue to doubt seriously that a better OO design can simplify these particular use cases -- at least, more so than use of first-class functions does. If you have some perfect ideal of OO in mind that clearly is better than the functional approach, demonstration and explanation of its relative perfection would be welcome; your examples thus far haven't been compelling, suffering from awkward design of their own such as the necessity of a class-per-instance. -DavidMcLean?]
- Re: "clearly is better than the functional approach" -- Strawman, I never claimed that and that's not the point nor goal. The point is that if the OO is decent then FP doesn't add much such that we should trim it out to avoid excessive paradigms and confusing intermixed idioms. ParadigmPotpourriMeansDiminishingReturns. Too much complicates staffing and training. I know you disagree, so be it. That's my assessment based on my experience in real shops, and if you don't agree, well tough fucking boo hoo waa waa. Re: "Such as the necessity of a class-per-instance" classes are not more bloated than objects in a good language (and can be the same thing). Just because they are bloated and cumbersome in Java does not mean they HAVE TO be bloated and cumbersome. If you have solid proof they MUST be B&C, then bring it up.
- [Is your rudeness necessary? I'll admit that claim was a little strong, so I'll rephrase: I don't demand a "clearly better" OO approach to this problem, just a "not obviously worse". Demanding a class definition for every instance of a widget is both conceptually and syntactically messy, as well as failing to solve of its own accord obvious questions like "what if there are two handlers on a given event?". To anticipate your probable response, I'm fully aware your system allows multiple handlers on one event but have absolutely no inkling of how, because you've never actually gone on to explain how you go about registering a second handler under your approach. I'll agree that classes can certainly be the same thing as objects in a good language, in the sense that there are only objects and no classes, i.e., PrototypeBasedProgramming. JavaScript and IoLanguage both attempt this, and both fall a little short in certain aspects, but the idea is extremely sound. Indeed, MyFavoriteLanguage is prototype-based among other things. But how does that help here, with our button scenario? How do you gain the ability to register events on widgets cleanly as a result of unifying objects and classes? How would any solution based on overriding methods be as flexible and as straightforward as providing an .addEventListener() or equivalent method? If you have a solid example of method overriding for event handling that's usefully comparable to .addEventListener()-style methods, then bring it up. -DavidMcLean?]
- Re: "conceptually and syntactically messy" -- How so exactly? I don't know how you are measuring/comparing this? I cannot read your mind. The only "extra stuff" I see appears to be language-specific and NOT some in-born fault/limit of all of OOP. Per below (Example 8490), it's possible to allow objects to redefine methods outside of their usual define point (often a "class"). Whether that's "good" is another issue. OOP itself doesn't prevent it. I suspect it's not common because most find it makes for messy code. And I'm not sure exactly what UseCase you are targetting with the multiple handler issue.
- ["conceptually and syntactically messy" in ways we've already discussed -- for instance, syntactic mess arising from the need for a class definition, which even in the most concise of languages (say, Python) is still heavier than a mere method call as .addEventListener() approaches involve, and conceptual mess arising from the fact that the base Button class itself can never be instantiated usefully as a button, and therefore that it isn't a button. Redefining methods outside the class is very nice to have, and it's good that RubyLanguage and PythonLanguage provide ways to do it, but it doesn't solve the issues. As for the multiple-handler issue, the GUI framework generally needs to do work of its own when you click a button, plus you need to add your own action, at the minimum; under your approach, do you need to explicitly invoke the method in the superclass to make that work? -DavidMcLean?]
- Continued at NodeJsAndHofGuiDiscussionTwo
- Sounds like an argument in favour of BondageAndDisciplineLanguage. Questionable, that, at best.
- Encouraging certain practices is not the same as forcing practices. It's "soft" BondageAndDisciplineLanguage. Whether that's "good" or not probably repeats many of the heated "staffing" debates. -t
- [Actually, encouraging certain practices is close enough to forcing practices to make no difference. You can do anything at all in any TuringComplete language; the only relevant distinction is what languages make easy or difficult. -DavidMcLean?]
- There's always a tug-of-war between cowboys and socialists. There seems to be a setting level in the middle that many orgs feel most comfortable with, and extremists on both sides lobby for their position.
- A similar discussion about lines and blocks can be found at ChallengeSixLispVersionDiscussion. I believe the industry has agreed with me after 50 or so years of having the FP-influenced alternatives around. AlgolFamily-style blocks are still the preferred approach for team-centric development. The FP fad cycle is in full bloom again, but it will fade into niches as it has before. I'd bet my money on it if I could. -t
- I remember when non-structured FORTRAN was the "preferred approach for team-centric development". I remember when cars had points and carburetors, too. Code was crappy and cars were unreliable. Now we've got functional programming -- growing steadily since its inception, I would argue, without any apparent cycles (beware of conflating AI research popularity cycles with functional programming popularity; they are orthogonal) -- and we've got computerised fuel injection and electronic ignition. Technology evolves. We're not going to go back to points and carburetors, and for the same reasons, we're not going to go back to pre-FP programming.
- It's difficult to distinguish trends from fads early in the "curve".
- True, but given that the functional programming has been growing steadily since the 1950s (see http://en.wikipedia.org/wiki/Functional_programming#History), if functional programming is a fad, then almost every other progression in computing -- which is almost inevitably younger -- must be a fad too. Is structured programming also a fad?
- I don't see any evidence of that trend, and I'm approaching fogey-land. I remember the tail-end of the Lisp hype of the 80's with similar claims about FP idioms.
- What "Lisp hype"? There was a brief LispMachines hype, but that's because running Lisp on a PC became cheaper than a dedicated LispMachine. Growth of FunctionalProgramming has been steady, to the point that we're now seeing FunctionalProgramming features in popular imperative programming languages like Java and C#. Do you think those features will later be removed from Java and C#?
- No, like you keep forgetting, languages tend to gain features over time in general as they lap up the fad-of-the-year to look "with it". That does not necessarily mean usage goes up significantly. We keep having this debate. We disagree, get over it already, we are stuck at an AnecdoteImpasse and rehashing this shit yet again is not going to change anything. IT'S DEAD JIM! STOP OBSESSING ON IT, DAMMIT or I'll jam a functor up your sphincter!
- I don't agree that languages gain features just to lap up "the fad-of-the-year". They gain features because their designers think programmers will find the features are useful.
- That's because you are either naive or biased.
- Maybe, but it's also possible I'm right. Study the origin of language features in popular programming languages, and you'll see they're not coming from the marketing department.
- Few authors are going to admit they are following fads merely to avoid making the language appear to be "old". They'll make up lofty BS to look more dignified. The human ego is like that. Microsoft clearly chased heavy and hard after Java when it started eating into their market-share, throwing VB-classic customers under the bus in order to make the change quick. They were driven by sales, not thoughtful language design, otherwise they'd revamped earlier.
- Perhaps that's actually because VbClassic was almost universally decried in the developer community as dire and in desperate need of replacement. Isn't the best way to drive language sales to make a language that's appealing to the developers who will use it?
- VBclassic was better for small projects but it scaled poorly. MS wanted to go after the big project market because they felt it more profitable. They switched the target project type.
- So you're essentially saying an inadequate product is acceptable for small projects? Isn't it more reasonable -- following your line of reasoning -- that Microsoft wanted to create products good enough for projects of any size?
- No, the newer VB is more "bureaucratic" than classic, making it more effort for smaller projects. A project-size-related trade-off was made.
- Really? How so? Having used both VB.net and VbClassic (every version), I'm hard pressed to think of any way that VB.net isn't superior. (C# is even better, but that's a different topic.)
- I don't remember. It's been a long time since I've used such for desktop programming. I once saw nice article that gave examples, but I cannot find it.
At this point I'll say I find the OOP example personally more intuitive; and it's not objectively clearly less code. And I suspect most developers in the shops I typically work at would prefer the OOP version.
It's clearly less code -- there are fewer tokens, if nothing else. Are you sure it's not just because you're more used to the anonymous class version? The lambda can be considered mere syntactic sugar, otherwise equivalent. Surely less keystrokes to achieve the same thing is good? If you're uncomfortable with the type inference and would prefer to see an explicit type, this is equivalent: buttonFoo.addClickListener((ActionEvent actionEvent) -> console("Clicked button foo at " & actionEvent.eventTime))
It's roughly about the same. Plus the attributes and methods have explicit names which can make it easier to follow in debuggers. Anonymous functions have no printable identifier.
How could the code size be "roughly the same"? Obviously, it isn't.
- We'll probably have to flesh out a fuller example to make sure we are comparing apples to apples.
- It is a full example. The first is a standard anonymous class based event handler, and it's five lines long. The second is a standard Java 8 lambda based event handler, and it's one line. They both do exactly the same thing. They can even be used together, but when you've got Java 8 lambdas, I don't know why you'd use the anonymous class approach.
- I meant the full GUI library because there are a lot of trade-offs to make when designing a GUI library. WaterbedTheory.
- Even in a full GUI library, if we replace every standard anonymous class based event handler with an equivalent Java 8 lambda based event handler, surely it's all gains with no trade-offs. No?
- How are you measuring "all gains"?
- I mean that there are no downsides to replacing every standard anonymous class based event handler with an equivalent Java 8 lambda based event handler, and the upside is you get simpler code.
- "Simpler" may be a loaded description. See above for grokkability versus pure code size.
- The examples of the Java 8 lambda based event handler code shows that it's the same event-handling code as the anonymous class examples, but with the repetitive class scaffolding replaced by a terse parameter list and an '->'. Given that it's the same code -- minus repetition, plus a terse parameter list and an arrow -- I don't see how it would be any less grokkable than the traditional anonymous class. Indeed, it should be more grokkable, given you don't have to read your way through the scaffolding to get to the meat of the event handler.
- Repetitious scaffolding sometimes increases readability. I don't know why exactly, that's just way typical developer minds work, based on my experience. We cannot dissect actual live neurons without going to jail, so we'll just have to settle for an AnecdoteImpasse. It is what is and I call it as I see it. Humans are not Vulcans. (Some come close, perhaps, but I'm talking averages.)
- Does the repetition of "print(1); print(2); print(3); print(4) ..." increase readability over "for (int i=1; i<=100; i++) print(i);"? For the beginning programmer, unfamiliar with loops, it probably does. In the early days of structured programming, structured code was unfamiliar to every programmer. I remember arguments against structured programming that claimed GOTOs were "just [the] way typical developer minds work." Developers learned to work with structured programming, and there's no impetus to go back to unstructured GOTOs. Similarly, for the programmer unfamiliar with HOFs, they're unfamiliar... For now. That will inevitably change.
- Similar quotes can be found for dead fads. Anyhow, time will tell who's wrong so there is no use re-fussing over this issue, it's getting old (no pun intended).
- What "dead fads" would those be?
- I don't remember specifics, I only remember hearing that cliche multiple times over the years.
- Then your point is pointless.
- Projection, SalesBoy?.
- You claim "similar quotes can be found for dead fads", but you can't name any. That sounds like a pretty pointless point to me. Wouldn't you accuse me of the same had I written it?
- No, it's just a weak anecdote. Weak evidence is still evidence. I find much of your evidence weak also.
- It's not even an anecdote. At least an anecdote tells a story. You've only made a claim -- a claim with no evidence and a refusal to give any.
- We've had this debate already about the def of "anecdote". I disagree with your personal opinion on its meaning and worth as evidence.
Anonymous functions don't need a printable identifier. When tracing in a debugger, the flow of control comes from the calling context and goes into the function body when appropriate, and comes out and returns to the calling context when done, like with any other function.
It just seems easier to understand the debugger output and error messages if important code-blocks have explicit textual names. The option of looking at or pointing at code is available under both techniques, but names are not.
Loops, if statements, case statements don't have explicit textual names. Why should a function without a name be a problem but a loop without a name is not? They're both just a block of code.
They don't share dual contexts like HOF's do.
What are "dual contexts"?
They share some context with both the caller and callee.
So do loops. What's outside the loop and inside the loop are shared.
- True, but both of those are right next to each other.
- So is the body of a higher order function and its enclosing context.
- No, one is "passing it" to another area. One of the advantages of a HOF is that it partly shares multiple contexts such that it's partly "in" one code area and partly "in" another at the same time. Schrodinger would be proud.
- What do you mean that it "shares multiple contexts"? Do you mean that a HOF closes over its referencing environment? Of course, that is indeed one of its advantages. I'm not sure why you feel that is a problem.
- I didn't say it was a "problem" here, it's just a reason to be more careful about identifying it to the system and tool set.
- Having debugged a fair share of HOFs, I haven't noticed that's a problem.
- That's fine, but different people are bothered by different things.
Another issue is that all the methods for a given button object would be packaged together. With HOF's, the equivalent can be
scattered about. I will agree that sometimes that is a good feature, but most of the time they are best kept together. Thus, HOF's are generally anti-module. -t
What's scattered about? All methods for a given button object are still packaged together. Note that the usual Java code with an anonymous class...
myButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myLaunch();
}
});
...is conceptually equivalent in every way, but less code using a Java 8 lambda:
myButton.addActionListener(e -> myLaunch());
- What do you mean by "packaged together"?
- Presumably, the same thing you did. Nothing is more "scattered about" in the second example than the first.
- Methods have to be defined inside the class in most OOP languages. HOF's can be defined anywhere, such as outside of the button class (or equiv).
- I'm not clear how that relates to my example, above.
- I think I'd have to use a code example to explain it, for words ain't cutting it. Not today, though.
- How about tomorrow?
- See near Example 7392.
Foot Notes
[1] If the event ID's and type combinations become complex or a large volume, then a many-to-many ControlTable may be a better way to manage such. How the table actually calls code depends on many specifics of the architecture, language, etc. That's an interesting question, but for now I'll stick with OOP examples instead of TOP examples. If one is managing boat-loads of associations, tables tend to be superior to code-centric approaches in my opinion. See TableOrientedGuiDiscussion for more.
Use of HOFs may be even better. See http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html under "Improving Code with Lambda Expressions". Or, if your environment supports it, you can even store HOFs in tables. See http://dbappbuilder.sourceforge.net/docs/AnonymousAndFirstClassOperatorsInTutorialD.pdf
Tutorial D, uuuuuugh.
That's not the point.
I'd think that whether the snippets were functions, methods, or HOF's should be a hidden detail. We won't want to have to expose and/or require repetitious scaffolding code to each snippet. But all that depends on how parameters and scope is handled for a particular DB or language.
Likewise, do you think, say, loops should be a hidden detail? I agree that we shouldn't have to "expose and/or require repetitious scaffolding code", but that's precisely what facilities like HOFs can help with. Again, see http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html under "Improving Code with Lambda Expressions" which is precisely about eliminating repetitious code.
The devil's in the details. If it requires syntax and keywords that are not relevant to task at hand, they should be removed or made optional. I'd have to study a given proposal. The PDF above gives this example:
OPERATOR ( a INTEGER , b INTEGER ) RETURNS INTEGER ; RETURN a - b
; END OPERATOR
It seems the "Operator" block construct is unnecessary. Instead, this can be in a "cell":
( a INTEGER , b INTEGER ) RETURNS INTEGER ; RETURN a - b;
A row already has a unique identifier such that we don't need a function/method name either. (Although naming as an option would be nice.)
In the PDF above, there aren't function/method names. Yes, the anonymous operator syntax is painfully verbose, but as noted in footnote #6, "Whilst [the syntax is] verbose, it is intentionally in keeping with existing syntax. As Tutorial D is intended primarily for pedagogical purposes, it is appropriate to favour consistency over brevity. However, the author grew weary of endlessly typing OPERATOR during this phase of Rel development and so created a shorthand form of anonymous operator definition, using a pair of digraphs in place of “OPERATOR” and “END OPERATOR” which the author has deemed too aesthetically heinous to mention here.
We don't need (mandatory) digraphs either. Brevity is not the main problem though, it's distraction and confusion from "funny syntax", such as causing unfamiliar error messages if typed wrong. My main point is that if you strip out such irrelevancies, the app developer cannot tell if they are HOF's or functions or methods or gerbils and under the hood, and it could be any of the four as long as it "works".
- Something is needed to delimit operator definitions -- anonymous or otherwise -- whether with keywords or something else. The syntax is intentionally designed to avoid "funny syntax". It shares all the syntax of a named operator definition except the name, so that students can see that anonymous operator definitions are just ordinary operator definitions without a name.
- But why should they care if it's an OPERATOR, function, HOF, method, etc? That's irrelevant to the typical task at hand. Hide the implementation guts. (Please indent embedded replies; otherwise I might miss them.)
- It's a language construct, distinct from other language constructs. Your question is akin to, "why should they care if it's a while loop, if statement, or function call?" Each serves a different purpose. Likewise, an anonymous operator serves a different purpose from a named operator, though both are operators.
- If a vast majority of the time a "cell" type/context will contain a WHILE loop, then dispensing with the word WHILE and related block constructs is certainly the better way to go, in my opinion. It's similar to the way we don't repeat the column name for each table "cell"; it's already knowable from the column name, and most know where to look if they forget. If the UI doesn't make such clear, then it needs a rework. (If there is a fair amount of variation, then perhaps a block_type column is in order instead of textual syntax to indicate such. It's more "table-y" that way.)
- If the vast majority of the time a "cell" type/context will contain a WHILE loop, but it can occasionally contain something else, you will need to be able to distinguish WHILE loops from something else.
- That's fine, require it for the exceptions from the common case, but NOT for the common case.
- That's certainly reasonable for an "industrial strength" language. Tutorial D is a language intended for teaching (hence the name) and illustration, so its design philosophy is to favour syntactic consistency -- e.g., all operator definitions share the same syntax -- over being succinct, except where variety (e.g., some operators are infix, some are postfix, some are prefix) is used for illustration. It does make the language a somewhat verbose syntactic hodgepodge (like Pascal and PL/I) but that's how it was intended.
- Fair enough. So we agree that for a production language, such default factoring may make sense?
- Sure. In a production language, all manner of syntactic shortcuts may be appropriate, as long as they don't preclude expressing the full semantics of the language.
- The "long-cut" is not precluded.
- Hah! Look at that, we agree on something.
Perhaps consider having a separate column for parameters and the body. Or perhaps create a parameter
DataDictionary for that part. The syntax of the body block would then be dirt-simple: no outer wrapper or funny or special syntax needed; just plain-jane code. -t
If the parameters and body are in separate columns, how would you define an anonymous operator that isn't stored in a table at all? What happens if you project out the body column without the parameters, or the parameters without the body? What would that mean?
- As described above, whether it's an "anonymous operator" should probably be hidden from typical developers. I don't know what you mean about the "project out".
- If you hide features, you lose facility. Projection is a RelationalAlgebra operator. To "project out" a column is nominally equivalent to the column specification in a SQL SELECT statement. In other words, if you define the parameters and body in separate columns, what happens if you SELECT just the parameters, or just the body? What would that mean?
- I don't understand what you are getting at. If a query writer writes a dumb SELECT statement, then spank them. There are various UI/perception/WetWare trade-offs with multiple information packaging techniques and I'd probably have to study a specific actual or realistic system or scenario to make a recommendation for that system. I am not prepared to make a summary design decision about such at this time. One could perhaps argue the same for "block type" indicators, but it's fairly safe to say that if a given column will usually or always have the same block type in it, then repeating that block information for each "cell" is a violation of OnceAndOnlyOnce. Make it only required if it deviates from the usual or default.
- What I'm getting at is that it doesn't make sense to define a function with a parameter specification that is separate from -- or separable from -- the function body. It's like separating a decimal point from its floating point number.
- I don't see that analogy very fitting. Anyhow, it'll probably take experience in production to really know if it's helpful or hurtful and articulate scenarios and reasons for either case. At this point, it's only speculative. I feel that isolating the parameter signature may turn out to be useful for "interface summary" listings, sort of like an "in a nutshell" chart you see in pocket references or API posters.
- The analogy is fitting because anonymous function definitions and floating point numbers are values. Removing part of a literal that denotes a value results in a different value. For example, removing the decimal point from 3.14 no longer denotes the same numeric value. It would now be 314. Similarly, removing the parameter specification from an anonymous function no longer denotes the same anonymous function. For example, if we remove the parameter specification from '(p, q) -> return p + q' so that it's just 'return p + q', the latter implies that 'p' and 'q' are identifiers in its enclosing environment rather than parameters. However, I can appreciate the use of having (say) an operator's signature without its body and for such purposes, I would provide a function that returns -- perhaps as a string -- the parameter declaration of a given anonymous operator. Thus, the components of an operator definition can be extracted, but I would not maintain them in some pre-extracted form like separate columns. That would be as odd as representing floating point numbers by putting the digits in one column and the decimal point position in another.
- It's not "removing", just hiding portions. One rarely needs to do such with decimal values/literals such that there's no reason to explicitly support it. Let usage be our guide. If doing X is fairly common, then design the system to facilitate X. And it's generally easier to "glue" parts together than take them apart, because taking them apart requires parsing. Thus, the resource penalty of having to isolate parts that are not cleanly isolated is higher than the penalty of gluing multiple parts together when we want them together.
- [It's removing when you project out only the parameter column or only the body column. Taking apart values does not require parsing, excepting when the original literal is parsed, a process which cannot be avoided by destructuring literals into multiple columns; there would be no performance difference between projecting only a parameter column and using a "get the parameters" operator. On the flipside, the cognitive penalty of having to treat what is conceptually a single atomic value as two or more columns can be great, once you start needing to pass it around through various operators, join on it, whatever. Treating values as atomic and providing operators that pull them apart when necessary is much more convenient for the common case -- treating the value as a value -- and no less convenient for rare cases (such as needing to inspect the parameter list of an operator). -DavidMcLean?]
- How exactly are you measuring this "cognitive penalty"? "Conceptually" it may be two columns in some cases. You word it as if being together (atomic) is a universal true-ism. Anyhow, I'll leave it to experience with a production system before a final judgement is made. Opinions are like assholes: everybody has one. A third possibility is that the parameters are defined in a DataDictionary-like construct and only the body text is in a single cell.
- [The cognitive penalty arises because, simply enough, you have to think about two things whenever you use a "destructured" operator, instead of one. When you pass it from place to place, you've gotta pass both the parameter list and the body. When you join on it, you need to join on both pieces. (Joins and other equality tests are also inhibited in a destructured situation because the two pieces are tested separately, which means that 100%-the-same-even-lexically equality is the only kind that will work. \x -> x and \y -> y denote the same function - under a property called "alpha equivalence" - but if you test "x" and "return x" against "y" and "return y" separately you'd never know.) And so on. It's comparable to C's treatment of arrays as a pointer plus a size_t to track the array's size, which makes passing arrays around a lot less convenient than in languages where arrays carry their sizes directly. Why would an operator or function conceptually exist as two distinct and separate components (parameters and body)? People don't speak of "the parameter list x and the function 'sine of x'" but of "the sine function" or, in more complex cases, something like "the function that maps x to x + 1". Separating the two components can and will change their meaning: A body "return x" on its own would refer to a free variable x and might return anything at all, dependent on its environment, while the function "\x -> return x" always refers to its argument and is an identity function. It can certainly be valuable to access the parameter list sometimes, which is why reflection features often offer that ability, but if you provide operators for doing this rather than hardwire the parameter list into the schema then you both avoid having to pass the pieces separately when you don't care about them individually and you can have multiple operators to access the parameter list in different ways - say, as a string that resembles the literal syntax like "int x, int y", as a list of types [int, int] or of strings ["int x", "int y"], and so on. Providing the parameter list as a separate column does not enable such flexibility. -DavidMcLean?]
- Inventing splitter functions as a work-around would make it harder to be language-neutral because one would need to build such parsers for each language. And it's not comparable to C's array because if we want them together we simply do: $parameters . ' ' . $body so that we can pass just one. C has no direct equivalent. Make a function to combine if you think it would be a problem, which I doubt. And we are not talking about what people think about functions in a general sense, but rather how they work with them in code.
- [Functions that access components of values aren't a workaround; they're the foundation of AbstractDataTypes. I have no idea what "parsers" you speak of; an operator value would necessarily be parsed when it is initially entered into the system as a literal, of course, but operators that access its components would not require any parsing. $parameters . ' ' . $body would only work if we stored both the parameter list and body as strings in the appropriate literal syntax, which is honestly pretty gross. Like I just said, the literal that denotes the operator can be parsed at initial entry - why transform it back to a string at all? Why not leave it as an AST or even as bytecode? You can of course bundle together components in a struct, which is sometimes done with C's arrays but not as often as I'd prefer, but why even offer the option considering how much rarer it is to want the parts of functions separately and how easy it is to access them if needed? -DavidMcLean?]
- I don't know what Grand System you have in mind. If we are not talking about storing textual code, but rather structured (pre-parsed) versions, then that changes the whole game.
- [Well, of course. I'm talking about storing actual functions, rather than textual code that can define them. That's what Tutorial D's proposed anonymous operators do, which are exactly what we've been talking about. Is there a problem with that? -DavidMcLean?]
- Furthermore, I don't know what "Grand System" anyone has in mind. Did someone mention a "Grand System"?
- Curious. So it's stored binary or tokenized? So how do you view the source and edit a function/method/snippet in a cell? Does it require an IDE to recognize and recompose it to text? Are the comments lost when de-texted?
- It's stored as a value, and the value's representation is opaque and implementation-dependent. I could tell you whether it's stored as binary, tokenized, or something else, but then I'd have to kill you. :-) In the RelProject, every value can be emitted as a string containing a literal that can be used to re-create that value. An IDE is not required to recognise and recompose it to text; it's just a string. Comments are treated as whitespace and so are lost. "Special" comments associated with definitions of RelVars, operators, types, and so forth -- that would be preserved in the database -- are an interesting idea, but that's for future consideration.
- Then kill me. I prefer the PowerOfPlainText. This doesn't necessarily mean "Eval()" usage, for the text may be compiled. The details about how, where, when, why, and what languages are supported is probably a big discussion. I would hope that a TOP-friendly system would support multiple languages.
- That's the neat thing: Because every value can be emitted as a string literal that can be used to re-create that value, you always have the PowerOfPlainText without losing the power of encapsulation. You get the best of both worlds.
- I thought you said comments were lost (and possibly formatting)? And what if other languages don't support such micro-compiling (or whatever it's called).
- Comments and formatting are lost, but the essential text -- the literal that denotes the value -- remains. If other languages that access a shared database don't support the types used in it, then -- as usual when dealing with such things, including typical SQL DBMSs -- some lingua franca needs to be employed by the developer(s).
- Losing comments and formatting is not a very good selling point. In my TOP, I prefer to stick to text. Compile a copy, fine, but leave the original as the coder intended.
- An anonymous operator stored in a RelVar isn't source code any more than an integer is a string. However, when displayed on a screen, both integer values and operator values are shown as a string literals. That makes them easy to recognise. When we insert an integer into a table, if there happens to be a comment above the integer would you expect to see it in the table later? Probably not. So why expect to see comments associated with an operator value inserted into a table?
- I'm not making a technical point, I'm making a (missing) feature point.
- What do you mean?
I'm glad some are at least looking at TOP-based code management, even if it is Tutorial D.
The RelationalModel related to programming language design is a rich and active research area.
An Attempt at a Summary Opinion
The scoring of the examples seem to be heavily dependent on GUI library design and possibly the language used. I have not seen any inherent and clear simplification from HOF's for decent libraries (or libraries tuned for the use/audience). Perhaps they are an improvement under poorly designed GUI libraries, but there may be other ways to create screwy libraries that make HOF's score poor. (I cannot think of any at this time, so don't ask.) Time is better spent on designing decent GUI OOP libraries or wrappers than on evangelizing HOF's. --top
Good. That's another one cleared up. He's seen no decent libraries. there are may be other ways to create screwy libraries, so go design some gui libraries. Forget about that HOF stuff. Who needs it? -- ChaunceyGardiner
{Indeed. It also ignores why HOFs are used, even inside GUI libraries. See, for example, http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html under "Improving Code with Lambda Expressions".}
I consider frequent need for HOF's by typical application developers to be smell (a yellow alert) that the libraries (or wrappers) are poorly designed. This pattern is found in NodeJsAndHofDiscussionTwo also: if the "timer" library is redesigned, we don't need HOFs, and I believe most developers will find the OOP version more "natural" (maps to expectations of block grouping, etc.). The real fix is better libraries or wrappers. I believe OOP provides better accountability and modularization on both the button and timer example, and I believe most regular developers will agree. (No, I don't have formal studies; it's based on my lifetime WetWare-related observations.) HOF fans will balk, but so be it: let the FP fanboys have their hoots and hollers, but I predict the rest of the world will eventually walk over them and ignore them like they did before in the 80's. I'd be willing to bet most regular developers will look at these two examples and say, "Top is right, with good library design, we don't really need HOF's, and HOF pushers have been exposed as loud zealots." -t
- [Perhaps this is accurate, but could you perchance demonstrate any of those claims? Show us a complete example of a timer library that's "designed properly" and therefore supersedes the need for higher-order functions, for example. The example on NodeJsAndHofDiscussionTwo both isn't complete and does not appear to demonstrate any particular improvement over JavaScript's current approach. How is JavaScript's design "improper" (?) relative to what you have in mind? -DavidMcLean?]
- On the flip side, have you seen anything about it that prevents it from existing? Yes, a runnable implementation would be nice to have, but so would a pony. And the reasons I personally prefer the OOP version are already given at that topic; I see no reason to repeat them here. If you need clarification about it, ask there, not here. I don't claim the OOP version is objectively superior, only that the average developer is more comfortable with OOP interfaces than HOFs.
- [I don't dispute that the example on NodeJsAndHofDiscussionTwo can exist -- of course it can -- but there's a severe lack of evidence that such a library is "designed properly" or that by implication the current JavaScript timers are designed improperly. Don't back out of your arguments by claiming now that you don't claim the OO version objectively superior. You just made a grand, sweeping claim by arguing that higher-order functions are a symptom of poor library design; unless you plan to withdraw it, please, prove it. -DavidMcLean?]
- Where do you get this "now" shit? I never used the world "objectively". It's a judgement call based on personal observations and experience about what would be more natural to existing developers. I don't have a formal study to "prove" it, but you don't have a pro-HOF study either. AnecdoteImpasse. Live with it. We've been over this evidence loop already. I'm tired of it. LetTheReaderDecide.
- [I got "now" from the fact that you said the thing I'm talking about just a few minutes ago. It's a colloquialism, sure, but the meaning was clear. In any case: Correct, you didn't use the word "objectively"; you just implied it by making a universal sweeping claim. Your claim is that higher-order functions are a symptom of poor library design, and that better-designed libraries will avoid them. Prove it. Burden of proof's on you. -DavidMcLean?]
- Projection from the pushy HOF evangelizer, goddam hypocrite!
- [Are you saying you never made that claim? It's there. Just look up a couple of lines. -DavidMcLean?]
- What claim explicitly?
- ["Your claim is that higher-order functions are a symptom of poor library design, and that better-designed libraries will avoid them." -DavidMcLean?]
- That would be "avoid the need for them" for what the OOP API takes care of.
- [Fine, then your claim is that the need for higher-order functions is a symptom of poor library design, and that better-designed libraries will avoid the need for them. Prove that, if you don't mind. -DavidMcLean?]
- Example 8462 demonstrates that. It shows what I believe is a more natural and familiar way to attach a click event to a button object/class. It doesn't have to mention action listeners, for example, which is distracting lower-level implementation guts. High abstraction is a good thing, no?
- [High abstraction is certainly a good thing, but action listeners are not lower-level implementation guts; they're just what Java calls event handlers. The approach of using inheritance to override particular handlers has been criticised by myself already elsewhere on the page, and I shan't clutter by repeating the issues. -DavidMcLean?]
- They ARE lower-level in that context. I disagreed with the inheritance complaints for the usual "staffing" reasons. We've gone in circles again.
- [How are event handlers lower-level? It's literally just "when the button is clicked, do this stuff", exactly as with an overrideable onClick method except with the improvement that it's more straightforward to register several of them. -DavidMcLean?]
- For the purpose of merely attaching a Click even to a button, they are an implementation detail. One just needs a simple association a vast majority of the time; they don't need to worry about HOW. The interface hides HOW away. And we rarely need to add several. Nor did I prevent adding several. We've been over that already.
- [We haven't. You and someone else may have been. In any case, an .addEventListener() method also hides away the "how". Maybe it stores the code in a dictionary. Maybe it registers it as a hidden method on-the-fly. Maybe it writes it out to a file and then executes the file each time the button is clicked. We don't know and it doesn't matter. (Heck, strictly speaking it's more abstract than overriding a method, because .addEventListener() could actually do that behind-the-scenes if it were the best approach. Or it could store the code in a list. Who cares?) -DavidMcLean?]
- Well, I cannot fully explain it, but Example 8462 just feels cleaner, simpler, and more natural; and is closer to how most developers would prefer it based on my experience. I cannot articulate further than that at this time. It's a gut feeling.
- [That's fine, provided you make clear it's simply an opinion rather than a universal truth. Are you willing to withdraw the claim that higher-order function use is symptomatic of poor library design? -DavidMcLean?]
- No, because it's based on my experience. If you want evidence higher than that on the EvidenceTotemPole, sorry, you're not going to get it from me, but the flip side is that the HOF fans don't have a OfficialCertifiedDoubleBlindPeerReviewedPublishedStudy either. AnecdoteImpasse AnecdoteImpasse AnecdoteImpasse AnecdoteImpasse AnecdoteImpasse LetTheReaderDecide.
- [If you don't have proof to back up your claim, then please withdraw your claim, and don't shift the burden of proof. -DavidMcLean?]
- If you feel specific text needs disclaimers or "personal experience" clarification, point them out.
- [The entire paragraph that this ThreadMess spawned off would be a good start. -DavidMcLean?]
- Put "[^]" markers next to SPECIFIC text you feel is excessively "claimy" and I'll review it. I cannot read your mind.
- [I don't need you to read my mind, just my words. I said "the entire paragraph" and meant it. But sure, I can add those. -DavidMcLean?]
- Your description of which paragraph was not specific enough. "Spawned" and "this" in particular were not very helpful. I'll provide a rewrite of the marked one at a later date.
- [Ah. Sorry about that. I did think it fairly clear that the paragraph I've marked was the one that "this ThreadMess spawned off", considering we're in the middle of a ThreadMess and it started with the marked paragraph. Nonetheless, good to know it's clear now. -DavidMcLean?]
- "That spawned this particular indent level" would have been better. A PageAnchor even more so.
- [Indeed. On the flipside, "I'm sorry, but I'm not sure which paragraph you mean; would you mind clarifying or adding a PageAnchor?" would have been better than "Put "[^]" markers next to SPECIFIC text you feel is excessively "claimy" and I'll review it. I cannot read your mind." We all need to work on these things. -DavidMcLean?]
- I thought it would be multiple scattered spots such that a PageAnchor seemed like the wrong tool for the job.
Granted, with "stiff" or poor languages or entrenched libraries, it may be difficult to create decent API's or wrappers, and this is where HOF's may have some benefit: work-arounds to crappy libraries under crappy languages. But in my opinion
it's better to evangelize good library and/or language design rather than better band-aids. You guys are evangelizing the wrong thing. If you wish to formally claim "HOF's help one to deal with poor languages/libraries/API's", I WON'T challenge that claim at this time. A go? I'm giving you a partial victory if you want it. (And Oracle/Java are not going to admit their GUI libraries are crap. It's better PR to talk up the band-aid.) -t
What is a "stiff" language? Aren't HigherOrderFunctions a way of making a "stiff" language less stiff? Of course, there are potential alternatives to HigherOrderFunctions, but HigherOrderFunctions have specific benefits in specific cases that make them a worthwhile option.
- HOF's are essentially "dynamic functions". A heavily "static" approach would dissuade its use for the usual "bondage and discipline" reasons.
- HOFs provide "dynamic functions" whilst retaining static TypeChecking without requiring repeated compilation or risking code injection. Syntactically, the code of a HOF winds up being the same as would be dynamically-generated and passed to an eval() function, without the scaffolding required to dynamically-generate a string, and with the benefits of static TypeChecking and closure over the defining environment. As such, a strict StaticallyTyped language does not dissuade HOF use because static TypeChecking is fully enforced. Strict StaticallyTyped languages dissuade against using eval() because it precludes static TypeChecking.
- Dynamicness is a matter of degree.
- Indeed. Arguably, HOFs provide all the "dynamicness" that is usually required, without being so dynamic as to require recompilation, risk code injection, and/or lose static TypeChecking.
I think this debate is increasingly a demonstration of TopVsOthers.
OOP-everywhere fans used to "gang up" on me also. Same pattern. It appears to be THREE pro-HOF dudes, hardly a representative sample. As I mentioned before, I use to get a fair amount of "fan mail" from those who agreed with my opinion but didn't want to jump into the heat of the battles. (I don't publish an email address anymore.)
TopVsOthers has nothing to do with "ganging up" on anyone, and everything to do with personal preference. Note that it is balanced and non-judgemental.
Incidentally, here is how multiple different kinds of events, and wild-card event handling, can be interfaced up:
class ButtonFoo inherits gui::Button { // Example 8473
self.location = new Point(34, 129);
self.title = "Click Me!";
method onClick(event) { // override stub handler method
console("Clicked button foo at " & event.eventTime); // sample usage
}
method onMouseOver(event) {...}
method onMouseOut(event) {...}
method onDoubleClick(event) {...}
method onFocus(event) {...}
method onBlur(event) {...} // lost focus
method anyEvent(event) {...} // roll-your-own or micro-manage
} // instantiation note at [1]
Do you really expect the user to define a new class for each button? That seems painfully tedious, even more so than the current Java idiom of defining event handlers ("listeners") as anonymous classes -- the tedium of which is one of the reasons for introducing lambda expressions in Java 8. Your suggestion is just the sort of thing that makes FunctionalProgramming proponents decry the verbosity of OOP.
What's the alternative? Those attributes and event handler blocks have to go somewhere. And note that an IDE can actually create the classes, it doesn't have to be hand-coded. That technology was available back in the days of early VB classic even. And I personally believe it's better modularization to have all the button-related attributes and methods in a single module rather than scattered about (at least for text-centric coding).
The general alternative is to define one instance of the generic Button class per button, unless you need a new category of ButtonS. All attributes -- event handlers, titles, text, colours, etc. -- are set on a per-instance basis. All the button-related attributes are then associated with their appropriate button instances. Since a button's event handlers typically affect the context in which the button is defined, the event handling code appropriately belongs to that context rather than the button. One approach to defining event handlers is to use Java 8 lambda expressions. Another alternative -- one commonly seen today -- is to define event handlers using anonymous inner classes. Whether code is generated by some external technology, or written by hand, why have more code than you need?
It's still not clear to me what your complaint is. "Appropriately" by what standard? It's not more code or more typing, at least not by a substantial margin (+/- about 20%). What is "tedious" exactly and how are you measuring that and comparing to the allegedly non-tedious counter-example(s)? It might cause difficulties in languages that prefer one-file-per-class, but that's a language-specific issue.
What's tedious is defining a new class for each instance. By the way, I have added code to your example to create a button instance, set the 'location' attribute to something realistic, and changed the capitalisation slightly to reflect Java/C# conventions. Without requiring new class definitions, and using Java 8 lambdas, the above could be:
Button buttonFoo = new Button();
buttonFoo.location = new Point(34, 129);
buttonFoo.title = "Click Me!";
buttonFoo.onClick.add(event -> console("Clicked button foo at " & event.eventTime));
buttonFoo.onMouseOver.add( ...etc... );
buttonFoo.onMouseOut.add( ...etc... );
Now it's syntactically simpler than your example, requires no new classes, and can support both single and multiple event handlers per event with the same construct.
- I disagree with "syntactically simpler". And whether it's "syntactically simpler" may not override the benefits of being "natural". The class approach is more "natural" in my opinion. It's hard to quantify, I know. Plus, it's non-modular, de-grouping button-related info. I prefer the more regimented approach if staff is fungible based on experience with confused newbies. One could end up scattering those all about the code:
buttonFoo.title = "Click Me!"; // Example 7392
// lots of code in between...
buttonFoo.onClick.add(event -> console("Clicked button foo at " & event.eventTime));
// lots of code in between...
buttonFoo.onMouseOver.add( ...etc... );
- Some shops like to scatter those all about the code. They set all titles in one group, set all event handlers in another group, set all widget positions in yet another group, and so on. When I first saw it I thought it was awkward. Then I tried it and it was good. Now I'd be hard pressed to say which is better, group-by-widget or group-by-action. I'm inclined to think it's a matter of personal preference.
- That seems a really silly grouping. Why did it turn out "good"? I agree that different shops will have different preferences/conventions, though. The chief architect would ideally rework the shop API's to fit the shop conventions. And really, most GUI's should be handled or specified by TOP or TOP-like techniques in my opinion for this very reason: group, sort, and search by whatever aspect or factor you want. Typically you'd want to see the title adjacent to the factor (such as coordinates) you are studying/editing to help identify it, for example. Internal ID is often not very friendly to read. You agree that an ID name is not always the best way to find a given widget's attribute assignment, don't you?
- GUI's are roughly 90% attributes (or could be 90% attributes if common action idioms are factored into attributes also). Mass attributes with involved relationships are much easier to manage in a TableBrowser with query-like mechanisms than as code. When the system is attribute-intensive, then (mostly using) code is the wrong tool for the job. But in my observation, grouping by widget is the most common preference if we are stuck with text-code-centrism: all attributes and event handlers for widget X are together.
- It turned out "good" because it meets typical UseCase's. For example, you need to change the layout of the widgets so you go to the place where all the widget locations are set, and change them. For example, you need to re-write the text that appears on the widgets so you go to the place where all the widget text is set and edit it. And so on.
- Okay, I'll accept that is a fairly common need, but there are often "widget centric" changes that are easiest if grouped by widget. We don't have enough info to accurately say which is the most common. But such grouping fights/conflicts are often a sign that TOP may be a better solution. Note that a powerful IDE could potentially project class/object/etc. parts as filterable and multi-column-sortable tables.
- But even if a shop does group by attributes and method name (or equiv), the code size may not be much different, although, again, it does heavily depend on the language.
As for "appropriately", what I mean is that event handlers typically affect the dialog or window in which they're defined, not just the button that invokes them. Thus, a realistic example snippet might look like:
void launchProcessing(ActionEvent event) {
buttonFoo.enabled = false;
textboxName.enabled = false;
buttonCancel.enabled = true;
processingAnimatedGif.start();
startProcessing(event.eventTime);
}
//
Button buttonFoo = new Button();
buttonFoo.location = new Point(34, 129);
buttonFoo.title = "Click Me!";
buttonFoo.onClick.add(event -> launchProcessing(event));
Note that launchProcessing is an event handler for buttonFoo, but it can also be invoked as an event handler for other widgets. Because it might need to be invoked by multiple widgets, and because it interacts with other form elements (e.g., textboxName, buttonCancel, processingAnimatedGif) and methods (e.g., startProcessing), it belongs to the form/window/dialog in which buttonFoo is defined rather than being defined as part of a ButtonFoo class.
The class approach does not prevent using a shared routine.
method onClick(event) {
formContext.launchProcessing(event);
}
Sure, but now you're just using awkward syntax to delegate event handling to formContext.launchProcessing, and you still don't have an easy means to have an onClick event launch multiple actions where some actions are defined by the form rather than the author of the button. Use of onClick.add(...) means onClick.add(...) can be invoked by the author of the button, but also by the form in which the button is used.
I'm not sure what you mean by "awkward syntax". It's pretty much an old-fashioned call to a subroutine. How is that "awkward"? I don't know what you mean by the last sentence. There is a misunderstanding in here I suspect.
By "awkward syntax", I mean you're still using a class definition for essentially nothing more than assigning event handlers.
No, it also contains attributes, and perhaps other event handlers.
[Indeed, hence "essentially nothing more than assigning event handlers". You don't need a class definition to assign attributes either. -DavidMcLean?]
Sorry, I don't know what you are getting at. Scattering methods and initializing attributes all over the place is uncivilized write-only HackerLanguage clutter. I bet your basement bedroom has pizza and underwear stuck to the ceiling. Your mom is too scared to even try to clean it. "Hi Mom, would you like HOF a slice also? I'll get the ladder. Oh wait, which one's the pizza and which the underwear?" Are modules out of style now?
[Nice rant. (I mean that sincerely. It was actually pretty funny.) Did you see your other correspondent's code sample (described as a "realistic example snippet")? It doesn't scatter buttonFoo's customisation -- it's all in one place. Related code is still grouped; however, it's not always necessary for the thing that groups related code to be a class. We have functions and (indeed) modules too, after all. If you're going to insist classes are the only appropriate grouping tool, why only apply that rule to GUI widgets? Why not replace for loops with a For class, which you must subclass and overload the methods init(), condition(), increment(), and block() to use? -DavidMcLean?]
You seem to be arguing that since our army pocket knife has 12 blades already, we should go ahead and add 13. I don't know if OOP could replace loops and similar self-rolled blocks. I haven't given it much thought. Humanity settled on certain conventions and those conventions improve communication/code-reading because we all learn a semi-standard: mental QwertySyndrome perhaps. If starting over, like the cockroach may do after we nuke ourselves to extinction, they can consider such.
And an OOP language could potentially allow adding or redefining methods away from the original declaration/class, per below. But I suspect it's not common because the need didn't exist. If HOF usage exposes gaps in existing OOP languages, then perhaps such languages will make method definition more flexible. -t
[Loops can be replaced by an OO class fairly trivially by doing what I just said, but it would be verbose and horrible to work with, as well as conceptually messy since the base class wouldn't ever be directly usefully instantiable. The same applies to GUI widgets. That was my point. Flexible (e.g., external to the class) method definitions are allowed in the more capable of dynamic languages, but I see no way they actually help solve this issue. -DavidMcLean?]
If we make some things too easy, it encourages messes and inconsistency and JobSecurity MentalMasturbation. This is back to the GreatLispWar again. A Lisp-like language doesn't need any built-in block or types since they can all be added via libraries or app developers as they please. But so far I don't see how it applies to GUI widgets, I only see how it plugs problems/limits in Java and/or it's GUI system.
[If making some things too easy produces messes, inconsistencies, and JobSecurity MentalMasturbation, why don't we stop making things easy? Why don't we remove the existing easy syntax for expressing for loops and replace it with a class that must be subclassed to use? Why not do the same for while loops? Why not demand that even all named functions are defined by subclassing a Function class and overriding its call() method, instead of using function definition syntax? Why not design your language such that variable declarations are only allowed in the form of subclasses of the Variable class? Making things "too easy" encourages programming -- heck, making things easy is the entire point of designing a programming language. -DavidMcLean?]
Where a "rule of thumb" works and where it doesn't is ultimately determined by observation. My observations and talks with managers and lead developers leads me to make certain conclusions. Your observations appear to differ. AnecdoteImpasse. Same ol' same ol.
[A "rule of thumb"? What are you talking about? -DavidMcLean?]
Footnotes
[1] The GUI controller could do the equiv of the following line such that explicit instantiation coding may not be necessary. Auto-instantiation (or equiv) would be part of the button master class(es). And a switch or override may be available to deactivate auto-instantiation when desired. Further, languages that blur distinction between objects and classes may not need an instantiation step altogether. -t
Button buttonFoo = new ButtonFoo(); // traditional instantiation example
How would the "GUI controller" automatically instantiate a Button, and where and when would it do so? What is a "button master class" and how is "auto-instantiation (or equiv)" part of it? Please be explicit, and illustrate with code.
It highly depends on the language, where languages that blur the distinction typically don't need ANY instantiation. I am not a Java programmer so won't attempt a Java example. In some languages perhaps auto-instantiation is not possible and the coder would have to explicitly add a line like the above. (Ideally I'd prefer a DualTypingLanguage where the architect can optionally "lock" objects/classes and multi-instantiation as needed.)
Could you give an example of a language that blurs the distinction and so doesn't need any instantiation?
Let me clarify something. If objects and classes are essentially the same thing, then making a "class" such as Example 8462 is the same as instantiating an object. So, yes, we are "instantiating an object", but we don't need a "double" instantiation to give a class an object-like interface.
The syntax of Example 8462 implies static classes, which means you're going to have to instantiate an instance of buttonFoo somewhere. However, I can imagine that the language used in Example 8462 creates a new class via (say) the 'extends' keyword (in place of 'inherits'), but if you use the 'inherits' keyword (as you've done) it creates a new class and implicitly creates an instance of that class stored in a constant with the same name as the class. That would work. Of course, we can imagine anything we like with imaginary languages.
By the way, what is a "'double' instantiation"? Whilst it is entirely reasonable for classes to be implemented using 'class' objects, or to replace static classes and 'class' objects with prototype objects, you still have to ask the static class, or the 'class' object, or the prototype to provide you with each new instance. Even the implicit instantiation I've described above using the 'inherits' keyword is still an explicit request for instantiation.
I used certain pseudo-code keywords in an attempt to make the examples clearer, but didn't necessarily mean to borrow all concepts for similar-looking languages. For the samples, I'll assume ObjectsAreDictionaries and classes and objects are same thing and defining a class creates an object then and there. "Class" is just a more convenient way of initializing an object, and establishes the parent. "Inherit" means that an internal link is set to a parent object/class, and if a given attribute or method is not found in the current object/class, the interpreter/compiler traverses up the parent branch until it finds that keyword defined.
We could design the language to allow defining or re-defining methods if we really wanted to "scatter them all over" per above. (I'm not necessarily condoning it, but considering it terms of an exploratory option.) It could look something like:
// Example 8490 -- Simplifying the re-defining of methods in OOP
class myButton inherits gui::Button {
myAttribute = 7;
onClick(event) {...} // method (this version skips need for "method" keyword)
}
...
myButton.onClick(event) {newStuff();} // redefine method
The redefined one would still have the same parameter scope as if it were in the class such that "event" is still valid (which is defined in parent class "Button" or higher).
I don't know if there are any existing languages that do all these things in one, but my point is that language-specific issues seem to be the driving factor, not some inherent weakness of OOP, that keeps them from being competitive with HOF's, or at least doing the "tricks" you show, irregardless of whether those tricks are a good thing in practice, such as (re)defining methods away from the class.
[You actually can define methods like that in RubyLanguage (using normal method definition syntax) and PythonLanguage (using first-class functions, as is the only method definition option in JS). But does that help? You're still using a class definition to create an instance, and still you aren't addressing obvious concerns like "two handlers on one event". -DavidMcLean?]
Continued at NodeJsAndHofGuiDiscussionTwo
CategoryJavaScript, CategoryConcurrency, CategoryFunctionalProgramming CategoryDiscussion