Continued from NodeJsAndHofGuiDiscussion.
["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?]
I don't know what you mean by "heavier".
[Read "heavier" as "takes more syntax". Pretty straightforward. -DavidMcLean?]
[My point is that you can't meaningfully instantiate an instance of the Button class, if the only way to attach handlers to a button is by overriding its methods. Any instance of the Button class itself is not actually a button, because it can't possibly have any associated behaviour. -DavidMcLean?]
I'm still not following you. And I never ruled out other ways to attach handlers. I'm am just making the most common way easy.
[For the first point: The class Button represents a button. If you make an instance of it, with new Button(), the thing you get should be a button. However, if you can only bind event handlers by subclassing it (MyButton? extends Button), then making an instance of Button itself, with new Button(), does not give you a button; it gives you a decoration. As for other ways to attach handlers: Correct, you didn't explictly rule them out, but you also never actually presented any other ways to attach handlers and you continually deride the most common approach to doing so. Feel free to propose other ways to attach handlers, preferably that work for multiple handlers. -DavidMcLean?]
Re: "If you make an instance of it, with new Button(), the thing you get should be a button." -- That's exactly what is happening. I don't see a problem. Subclassing is doing pretty much the same as "new" in the kind of language I'm thinking of (objects and classes are the same thing). Perhaps we need better syntax conventions to clarify that; I'm just half borrowing Java parts to try to make the examples resemble something familiar.
[If you make an instance of Button with new Button(), you get a button with no event handlers attached to it. That's not a button; that's a decoration. That's the problem. -DavidMcLean?]
No, it's a blank button that doesn't do anything. (Or, an error if title is required.)
[A blank button that doesn't do anything is a decoration. -DavidMcLean?]
Well, if you don't want a blank/dud button, then fill it up with stuff.
[Obviously. But you can't do that if you have an instance of Button, because you can only attach handlers by subclassing. -DavidMcLean?]
Well, that's what we want to maintain regimentation of modules. Example 8490 shows a possible way to allow "externally defined" classes, but I'm not sure it's wise. I'm not sure I want to try to compete with your scatter-shot techniques. This is dicey territory.
[We "want" it to be meaningless to instantiate instances of the base Button class? Why? How does it "maintain regimentation of modules"? -DavidMcLean?]
I'm not sure what you are getting at. It seems you are used to doing it a certain way. Java's rigidity has perhaps boxed in thinking. Incidentally, if we wanted to programmatically prevent an "empty" instance, then we could have a language with "required" key-words etc., just like in CRUD-land.
[I'm not a Java user; its rigidity has had no impact on my thinking. I don't see why it would be useful to provide a Button class which can be instantiated if doing so cannot produce a useful instance. How does it "maintain regimentation of modules" to do this? -DavidMcLean?]
How is that different than forgetting to hook a HOF to a button? I don't get your complaint.
[You can create a button instance, assuming you have .addEventListener()-style methods on the class, which has no event handlers. However, that button instance can subsequently have event handlers bound to it. Therefore, the instance provides a meaningful button. If you create a button instance in a situation where the only way to bind handlers is subclassing, then that instance is not meaningful, as no event handlers can be bound to it. It's a question of what's possible rather than of what's actually done, essentially. -DavidMcLean?]
So you are saying you want to be able to do something like Example 8490? (Putting aside whether it's good design or not.)
[The ability to add methods to a particular instance after its instantiation does address the complaint, in the sense that the instance is no longer "meaningless", but it implies a strange blend of classical and prototypical OO, and I believe it still doesn't resolve the conceptual problem since hanging additional methods off an instance involves changing its interface on-the-fly. -DavidMcLean?]
Re: "it implies a strange blend of classical and prototypical OO" -- Yes, it's called making OO more competitive with FP. I don't know what you mean by "changing its interface on the fly". Isn't that what a dangling HOF does?
[RubyLanguage has the same strange blend. Why not go for pure prototypes? As for changing the interface, no? Not at all? The interface of an object is the set of operations that can be performed with it; adding an extra method changes that, while adding an event handler does not. -DavidMcLean?]
We could have "locking" mechanisms in the language, but I'm not sure it would help anything much in a practical sense. Generally we want the ability to customize objects/classes. I didn't like Java's "final" for that reason: let me "locally" customize stuff as I see fit. As far as whether to use pure prototypes, I don't know if they could be flexible enough without goofy syntax. At this point we are just looking at the possibilities rather than putting syntax and OO conventions in stone.
class formX extends Form { onClick(event) { // method if (event.targetObject.name=="buttonX") { console.write("The form also responded to buttonX click.") } } }
class formX extends Form { onClick(event) { if (isDefined(event.targetObject.belongToGroup7)) { console.write("A Group 7 button was clicked.") } } buttonX extends Button { belongToGroup7 = True; // define an attribute onClick(event) {whatEver();} } buttonY extends Button { belongToGroup7 = True; onClick(event) {whatEver();} } buttonZ extends Button { // No group membership <----NOTE onClick(event) {whatEver();} } }
buttonX.onClick.add((event) -> whatEver()); buttonY.onClick.add((event) -> whatEver()); buttonZ.onClick.add((event) -> whatEver());.
I once encountered a case that didn't seem like a technical reason, and others complained about it also online. I don't remember the details, so don't ask. Anyhow, if we had an interface that accepted a map as an input parameter, for example, adding extra key/value pairs to that map is generally not considered a problem; any extra stuff in the map is typically ignored. If you are thinking of "locking" in types etc., protection-oriented syntax or key-words can be added to a language. But I don't see how HOF's prevent such anyhow. If a HOF returns a string for an interface that expects a number, we have the same problem (although it may depend on language).
Multiple Handler Issue
Re: "As for the multiple-handler issue, the GUI framework generally needs to do work of its own when you click a button,..." -- Yes, but isn't that done BEFORE it calls "onClick"? The GUI framework typically calls an "onClick" method, not the app developer, which implies that the GUI framework can do whatever it pleases before doing the onClick method(s).
[That's one approach, and if the framework is simple enough that's perfectly viable. If there're a lot of aspects that need to react to a click event, though, in a more elaborate framework, then letting them hook in separately is going to be a lot tidier. In addition, presuming that the application developer will have no use for multiple handlers simply because your framework doesn't use them internally isn't sensible framework design. -DavidMcLean?]
Re: "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?" -- I need clarification on this.
There are different ways to design a GUI event system, but here is one way to go about it.
The base Button class defines a constructor. When an explicit button is defined (allocated) this constructor runs for the button and establishes or registers a reference of the newly allocated button instance object to the GUI system. (The pointer could be a RAM address or the object name). When that button is clicked, this reference to the object is used to see if any onClick event is defined for that object, and runs it if it exists.
[So the base classes would invariably have absolutely no behaviour in their on* methods, under this design? Otherwise, it becomes necessary to invoke the superclass methods explicitly to guarantee proper handling. -DavidMcLean?]
I don't see a need for such at this point.
[A need for any behaviour existing in the base-class on* methods, you mean? -DavidMcLean?]
Correct, I currently see no need for it. Well, except maybe logging a "not-handled" warning message.
[Alright. Reasonable, assuming a fairly simple framework, I suppose. -DavidMcLean?]
PageAnchor parent_run_01
If you can envision a useful scenario, please do. Note that a language can have an "always (run) before" and "always after" keyword set that tells it to run the current (parent) method even if a child overrides it (although it's really sort of prepending or appending instead of "override", technically).
[True -- that's a fairly common AspectOrientedProgramming feature, actually. A full complement of AOP features would make on* methods perfectly viable as a solution, by my reckoning, since that would imply different components of the framework could hook into the event methods arbitrarily as necessary with before/after/around advice. Cool. -DavidMcLean?]
One syntactic approach is a marker or dummy method in the parent to indicate where the child method(s) run such that "before" or "after" is determined by where in the method code one puts the marker. For example, if you put the marker at the end, then the child runs "after" the parent method code. If you put it in the middle, then half runs before and half after.
[That seems worse than proper AspectOrientedProgramming facilities, since it leaves the decision of hook position in the definition of the original method; if the parent method failed to provide a hook point at all, then there'd be no options for hooking. With advice, as AOP actually provides, multiple hooks may be inserted and any of them may be before, after, or "around" (the hook itself chooses when and if to run the original code), which is far more flexible. -DavidMcLean?]
// Example 8562 -- syntactically trimmed class buttonFoo:Button { location = new Point(34, 129); title = "Click Me!"; onClick(event) { console("Clicked button foo at " & event.eventTime); } }[With the typical API and naming conventions of JavaLanguage or CsharpLanguage, the above would typically be implemented along the lines of:]
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));[This example did already appear on NodeJsAndHofGuiDiscussion, so I'm not sure why we need it again, but nonetheless. -DavidMcLean?]
As far as code volume, it looks pretty much the same to me, perhaps even slightly less. And I only have to use one paradigm instead of mix two, reducing the potential for confusion. Incidentally, in my opinion, the last line would be more readable as:
buttonFoo.onClick.add( event -> console("Clicked button foo at " & event.eventTime) );Or perhaps
buttonFoo.onClick.add(event -> console("Clicked button foo at " & event.eventTime) );[You're right regarding volume and regarding code formatting; indeed, realistically I'd probably use your second proposed formatting, as I often do in CoffeeScript. This isn't a demonstration of formatting, though. I'd doubt there's more potential confusion in my approach, considering PublishAndSubscribe is a cornerstone of OO designs. However, consider this: What happens when we want the button to do something real? For instance, say I put my above code inside the form class's initialiser. Here's a sample form class:]
class MyForm? extends Form { init() { Button buttonFoo = new Button(); buttonFoo.location = new Point(34, 129); buttonFoo.title = "Refresh"; buttonFoo.onClick.add(e -> this.reCalculate()); this.widgets.add(buttonFoo); } reCalculate() { // do some stuff, update the form's output } }[Notice that what the button does depends on the context it's called in -- it invokes a method on the form class, under this setup, which potentially affects every widget in the form as well as the model data lying behind the form. If the button's independent of the form, how do you cleanly assign a handler that operates on the form as a whole? -DavidMcLean?]
I did indeed forget to describe how to bind it to a panel/form/window. One way to do it is create a middle-man class that binds it:
class formXbutton:Button { container = formX; } class buttonFoo:formXbutton {...}Or explicitly set it in each button:
class buttonFoo:Button { container = formX; location = new Point(34, 129); title = "Click Me!"; onClick(event) { formX.reCalculate(); // call a method of the form } }PageAnchor NestedClasses01
[Where does formX come from in these examples? How does the namespacing work? Are these classes nested inside the form class? If so, why write formX instead of "this" or some sort of "parent" keyword? -DavidMcLean?]
Since objects and classes are the same thing here, we'd probably want to allow classes to be nested, and in that sense you are correct, we could do that also. It wouldn't necessarily be a "parent" class, but an outer container class since it's not the same kind of "thing". This may allow a way to have automatic default form scope so that we don't have to explicitly set it. The gut implementation, such as the top master button class, could check the existence of such:
// Example 8653 // Master button class setting default container as a short-cut (yet still overridable) if (isObject(outer)) {container=outer;}Here "outer" is somewhat similar to "self" in that it references the "outer" class/object. I realize scoping rules can result in sticky tradeoffs and WaterbedTheory.
[So do you need to nest classes to get a reference to the container? It's still unclear where "formX" comes from in your above sample; is it a global? Is baking references to containers into the actual widget classes a good idea? For example, what happens if you want to reuse the same widget across a few forms, when the widget carries a reference to its container? (With an .addEventListener() approach, the container reference is captured in the listener itself because of closures.) -DavidMcLean?]
"FormX" is the containing class/object. This nesting is not really different than Java, except the fact that objects and classes are the same thing here.
But this class nesting is not to be confused with OOP inheritance: it's a "coded" default in this "form" case, not an OO-inherited one. I agree that nested class/object relationships versus OOP relationships is confusing, and this is why TOP should be involved: code is shitty for managing/tracking myriad multi-factored relationships. Relational is just a better tool for that, even if we have to use code generation as a final step because the DB brand cannot handle direct code snippets.
Example outer64
// --- GUI library class Form extends GuiWidget { // master form class } class Button extends GuiWidget { // master button class container = null; // establish an attribute of this class method constructor() { // line "sally" if (isObject(outer)) { container=outer; } } } // --- Specific Application Code class appX extends GuiApp { class formX extends Form { // instance of form class buttonX extends Button { // instance of button, line "brenda" method onClick(event) { console.write("Button is in form instance: " + container.name); // shows "formX" } } } } // syntax chosen for clarity, not brevityAn instance's class-nest scope determines its context even if it runs a parent method via inheritance.
When the "buttonX" object/class is instantiated (line "brenda"), the Button class runs its constructor (line "sally"), and it runs in the current object's scope such that "outer" returns a reference to the formX object. (Remember, "outer" is a scope word comparable to "self". A language could have a lot of scope words for various purposes, but we won't get into that here.)
[So "outer" is bound lexically to each class, and when you access it from a method it dispatches based on the object's actual class rather than where the method's defined? That seems a little confusing as a scoping method -- it doesn't cleanly correspond to either lexical or dynamic scoping -- but seems generally viable and wouldn't take much more getting-used-to than any other scope approach. What about the case where you want the same widget (same colour, title, position, whatever) displayed on two forms, with different behaviour? How does that come out in this design? -DavidMcLean?]
One approach is to subclass a button containing the common items, and overriding any difference. Another is to use an IF statement on "container.name". And a third approach is to call a form-level method for the differing methods ("container.mySpecialFormMethod(event)"). A fourth approach may be to create a template form class, and then override the differences, but I haven't really thought out all the implications of such.
[The option of an if statement on "container.name", presumably in the button class's onClick(), is unconscionably mixing concerns. The rest of those seem reasonable, given sufficiently "nice" syntax for working with the object system. I'm not sure there's necessarily any gain over the typical approach to GUI widgets, but it's at least looking comparable rather than horrifically worse. Nice. -DavidMcLean?]
Having a direct and simple on-click event attached to the button object/class to me seems the most natural and strait-forward and uses a paradigm already in use for an application: OO. We thus cut down on paradigms and make the code strait-forward. And I don't see it as inherently "more code"; it's roughly a wash. I realize "strait-forward" is perhaps subjective or subject to an AnecdoteImpasse on what typical developers prefer, but if we are at an impasse then we are at an impasse, and at this point LetTheReaderDecide and call it day. -t
[I see no value in cutting down on paradigms, but if that's your goal this does seem to be a way to do it. -DavidMcLean?]
This "cutting down on paradigms" seems like programming philosophy driving programming practicality. Top, if we told you that Java was dropping lambdas, replacing them with a shortcut syntax for defining anonymous inner classes, would it make a difference? For example, this is the usual way of adding a Java event handler to a button:
Button buttonFoo = new Button(); buttonFoo.location = new Point(34, 129); buttonFoo.title = "Click Me!"; buttonFoo.addActionListener(new ActionListener() { void actionPerformed(ActionEvent event) console("Clicked button foo at " & event.eventTime); } });What if we got rid of lambdas, and instead had a shortcut syntax for defining an ActionListener that looked like this?
Button buttonFoo = new Button(); buttonFoo.location = new Point(34, 129); buttonFoo.title = "Click Me!"; buttonFoo.addActionListener((event) -> console("Clicked button foo at " & event.eventTime));Would that be better? Remember, it's not a FunctionalProgramming lambda -- we got rid of those -- it's a shortcut syntax for defining anonymous inner classes.
Example 8562 to me seems the most natural because it uses regular plain-jane OOP. No funny arrows, no curly braces inside parenthesis, etc. I know you will probably disagree regarding staffing/training/hiring/confusion, but let's not reinvent that battle here unless you have something truly new to add to it. It won't be settled via words nor repetition.
OOP can be pretty flexible for "block management" if tuned right such that we don't need to introduce FP and HOF-like techniques and syntax. Master the OOP for such and dispense with paradigm clutter of competing block-management techniques/syntax.
What does "master the OOP for such" mean in Java?
What is "paradigm clutter"? That sounds like something you made up.
It wasn't intended as a formal term. We should have a consistent way to specify "passable" blocks (for lack of a better term), and plain-jane OOP methods appear to be good enough in a well-designed OOP language (which Java doesn't qualify for in my book). On occasion it may be somewhat more code to live with a limited "kind" of passable blocks rather than also use HOF's, etc, but worth it in terms of consistency and familiarity.
So code blocks are fine if they're assigned to named constants (that's what a "plain-jane OOP method" is) but not if they can be passed as parameters or assigned to variables?
Generally yes, because the syntax and context is confusing and different for anonymous ones. And it gives a reference name for debuggers and human discussors to work with, and allows a place to hang related attributes and other methods off of if features expand in the future. Such regimenetation improves fungible team communication: all cars must have a license plate. If that bothers libertarian coders, screw-em, they can move to Somalia.
Sorry, I misread the "constant" thing. I'll try to re-word it better. And loops are generally used for different purposes such that you are comparing apples to oranges.
In typical imperative programming languages, a FOR or WHILE loop is a flow-control construct that can repeatedly execute the anonymous block that is statically assigned to it. An anonymous block is an anonymous block whether it's:
But we don't pass references to loops or IF blocks around. OOP and HOF's are a way to pass around references to "behavior", as are functions. Loops and conditionals are intended to be used only in their current environment.
Sure. When anonymous blocks are dynamic (see above re "dynamically assigned" and "dynamically passed") they can be passed around, just like objects and other values. So?
Thus, they are not comparable to regular loop and condition blocks.
Of course they are. They're blocks of code. Why would they be any different? It's no different from an instance of a class, which is precisely the same instance whether it's passed around or not.
They are different. If we intend to pass them around and/or reference them from a wider variety of code spots, then we typically need extra info and maintenance-friendly conventions to work with them effectively. A box of stuff sitting in your closet doesn't need the same labels and packaging as a box you intend to ship to Timbuktu. Or even a "sub-box" inside the Timbuktu-destined box.
[While your metaphor is true, it isn't particularly applicable to blocks of code. Lambda expressions are in most cases referenced exactly once, as in cases like f(x -> x*2) where the anonymous function is immediately passed to another function, so there's no demand for "extra info" in those situations. When a particular function is to be referenced from multiple places, it's given a name -- it has to be, since rewriting the expression each time it's used will produce a different function -- and then there is no meaningful distinction between that function defined by a lambda and a function defined with "normal" declaration syntax (excepting degenerate cases like PHP's horrendous interpretation of functions and "closures" as fundamentally distinct). Static functions, from the language's perspective, have only their name and their type (the latter of which might not even be explicitly accessible, if working in a dynamically-typed language), and we don't need "extra info" to work with such functions effectively; why would we need more info if the function's assigned to a variable dynamically rather than statically? -DavidMcLean?]
Whether they are mentally comparable to loops and conditionals or not is turning into a rather obtuse discussion. My main point is that with decent OO and OO libraries, we don't need HOF's and lambda's for GUI programming. If OO can do the job, then don't introduce or depend on yet another language idiom to do the same thing. (H/L may shave code size SLIGHTLY in some cases, but that's not worth the added confusion by my judgement. Consistency often overrides code size in terms of team grokkability.) I am comparing them to OOP, NOT loops etc.
Your alleged confusion over "yet another language idiom" (e.g., lambda expressions) appears to be baseless, especially as certain language constructs like lambda expressions appear to simplify real UseCases, like being able to attach an arbitrary number of independent event handlers to an event without the labyrinthine circumlocution described in NodeJsAndHofGuiDiscussionThree. In short, I see trivial evidence that "yet another language idiom" (e.g., lambda expressions) can simplify code (and thus make it less confusing) but no evidence that "yet another language idiom" causes confusion. Can you provide evidence that it causes confusion?
See NodeJsAndHofGuiDiscussionThree regarding event handling.
As far as "evidence", see LetReaderDecideEvidenceAgreement.
The claim that HOFs and lambda expressions cause confusion is yours alone. If you want "agreement", retract your claim until you have evidence to back it up.
Moved replies to EvidenceDiscussion