MithrilJS / mithril.js

A JavaScript Framework for Building Brilliant Applications
https://mithril.js.org
MIT License
13.98k stars 925 forks source link

Rename "controller" #499

Closed gilbert closed 9 years ago

gilbert commented 9 years ago

My thinking is that "controller" is not sufficiently agnostic; the word comes with MVC baggage, yet it's use is very flexible source

It's not that the view context can't serve as a controller, nor that it shouldn't if you choose to use MVC, but that it shouldn't be inferred to be limited to that concept by it's name. source

Suggestions: scope, context, viewContext, viewctx, state, viewstate

Counter arguments:

While a controller can do many other things I think the MVC "baggage" is useful as it's a shared term with an understood meaning that gets the point across. Just because you can use it in other ways doesn't negate the fact that calling it a controller identifies the basic utility of it in a quick way that doesn't force you to learn a new term source

Why the word "controller" is wrong? "Controllers contain the interface between their associated models and views and the input devices (keyboard, pointing device, time). Controllers also deal with scheduling interactions with other view-controller pairs: they track mouse movement between application views, and implement messages for mouse button activity and input from the input sensor. Although menus can be thought of as view-controller pairs, they are more typically considered input devices, and therefore are in the realm of controllers." This is what the controller of mithril is doing right now. Mithril is one of the few frameworks that i really believe is using the MVC pattern correctly. source

l-cornelius-dol commented 9 years ago

This has my strong, but tentative, vote.

My tentativeness comes from the fact that I have only employed variants of MVx in my code, and it's entirely possible that I just under-appreciate the value proposition of an MVC controller.

This proposal (as modified below by @pygy) has my strong, unequivocal vote.

My contention is that the thing called "controller" is broader than an MVC controller, and the name is too conceptually restrictive.

From my comment on the original thread:

My take is that at its simplest it's a rendering context explicitly injected into the view (and I love that explicitness, detesting JavaScript's non-syntactically-scoped this atrocity), which is why I think I prefer view-context or render-context. I prefer the former because it correlates to the view method, and it fits the actual use well, although it still inherits some small amount of MVC conceptual baggage (which is not necessarily bad, being limited to the concept of a "view" which I think is well understood and clear).

source

Supported in part by Leo's comment in the docs:

In client-side MVC, however, this dissonance doesn't exist, and controllers can be extremely simple. Mithril controllers can be stripped down to a bare minimum, so that they only perform a single essential role: to expose a scoped set of model-level functionality. [emphasis added]

source

My point being that if "Mithril controllers can be stripped down to [view-models]", then they are not specifically controllers in the MVC sense, nor specifically view-models in the MVVM sense, but both and neither.

And I want to stress my earlier comment that my experience writing code "has ingrained in me to use things the way they are designed to be used, or risk all hell breaking loose down the road". If Mithril's design intent is not to limit the use of the thing called controller, as the documentation currently states, then it should not be named "controller".

So, my answer to "What's wrong with controller", is that it's simply the wrong name for the concept it embodies in Mithril.

tivac commented 9 years ago

You linked my argument already, so just stopping by to reiterate my :-1: on changing this.

l-cornelius-dol commented 9 years ago

There's also the question of the name, for which My vote would be for ViewContext, if it remains a constructor, and viewContext if it is changed becomes a function that returns a view context.

Personally, I'd prefer the latter, because I vastly prefer closure-based objects, not this-based objects. However, I don't feel strongly about that since if it's redefined as a view-context a plain JS object is quite tolerable.

l-cornelius-dol commented 9 years ago

@tivac: Of course, renaming it to ViewContext does not stop you, in your code, providing a Controller as the view-context, using it as a controller, and employing MVC, including continuing to calling it ctrl in your view. So you lose nothing from the rename.

That would seem to make you ambivalent to the proposal rather than opposed to it; not to put words in your mouth. (Apart from then needing to do a global find/replace of "controller" to "ViewModel", perhaps?)

pygy commented 9 years ago

I think that we need to use different names for the initializer function (currently called controller) and the object it populates (for now this, then ctrl).

{
    init: function(foo) {
        foo.data = m.prop()
    },
    view: function(foo) {
        return m(".bar", etc...)
    }
}

...where foo can be named differently according to the context (ctrl, scope, viewmodel, etc...), but should, by convention, have the same name in both functions.

gilbert commented 9 years ago

Nice! that's a neat option I haven't seen before. I think this will allow Mithril to get rid of the new keyword entirely. Also, I like the symmetry between the controller/init and view function signatures.

One affected aspect that I can of is @lhorie's blog post on using Meteor with Mithril (a lovely combination, by the way). The reactive helper presented in the article would change only slightly:

// I renamed `instance` to `foo` to correspond to your example
var reactive = function(controller) {
  return function(foo) {
    var computation = Deps.autorun(function() {
      m.startComputation()
      controller(foo)
      m.endComputation()
    })
    foo.onunload = function() {
      computation.stop()
    }
  }
}

Leaderboard.controller = reactive(function(foo) {
  foo.players = Leaderboard.players()
})

Astounding. To me this just feels right. If I'm not mistaken, it also makes controller/init functions more composable.

This also sidesteps the problem of what to name it our misunderstood little controller. If we call this new function something like init, setup, or onload, the user can name the foo parameter whatever they want (ctrl, vm, etc).

l-cornelius-dol commented 9 years ago

@pygy : That's strikes me as a really promising idea. I very much like the symmetry of invocation in initializing the view-context and generating the view. And it removes Mithril from having an opinion about what role the context fulfills.

I agree with @mindeavor, that it has a "feels right" appeal to it. And notice his example uses it as a controller with absolutely zero cognitive impedance.

I presume the Mithril call then becomes, using current module as an example:

m.module = function(root, module) {
    ...
    var isPrevented = false;
    ...
    if (!isPrevented) {
        m.redraw.strategy("all");
        m.startComputation();
        roots[index] = root;
        var currentModule = topModule = module = module || {};
        var context = {};
        if(module.init) { module.init(context); }
        ...

Where the name "context" is entirely up to Leo and is never seen externally; heck, it could remain controller, for all we care, though leaving it as that internally might be too dissonant to the actual use and documentation.

As well, logically then there's never a "dummy" context like we now have with the anonymous controller, only an empty one. This also feels cleaner, even though only really a semantic difference.

I would stick with init rather than onload, because I think onunload belongs as a property of the view-context and that would avoid the sense that onload and onunload should be in the same object but are not.

In terms of 0.x compatibility, it would be simple for Mithril to have a createContext function:

        var context = createContext();
...

function createContext() {
    var context;
    if(module.controller) {              
        context=new module.controller(); //FIX: 1.0 make throw; 1.1 Remove
    } else {                             
        context={};
        if(module.init) { module.init(context); }
    }
    return ctx;
}

I believe I give this idea a solid thumbs-up. It's the best proposal so far, I think.

barneycarroll commented 9 years ago

I like this idea of having the unique instance as a passed in argument is great. Several distinct advantages:

This really helps you think about the lifecycle properly and massively demystifies the hitherto 'magical' aura of controllers. When we accept that there's a persistent object you can access from redraw to redraw, and that there's an optional function that only executes on the first draw, 'controllers' such as they are sound a lot like the thing being asked for in #492. Taking the thinking in reverse, and looking at how we currently use config – which has an init flag to indicate the first run and a persistent ctxt object for your own use – and accepting that nested modules' controllers already execute in the view step (and we can deal with that), it suddenly becomes apparent that we may not need a separate controller method after all:

myComponent = {
    view : function( ctxt, user, data ){
        if( !ctxt.init ){
            ctxt.stuff = coolThingsWith( data );
            ctxt.name  = user.firstName + user.secondName;

            if( ctxt.stuff.pending ){
                ctxt.then( m.redraw );
            }
            else if( ctxt.stuff.wrong ){
                m.route( '/away' );
            }
        }

        return m( 'h1', 'Power overwhelming' );
    }
};
barneycarroll commented 9 years ago

BTW, in tangential support of the 'get rid of this' campaign, does anyone want to see how eye-gougingly horrible it is to do partial application with variadic constructors? No? Too bad:

function partialComponent( component, arg1, arg2, etc ){
    var args = [].slice.call( arguments, 1 );
    var noop = function(){};

    return {
        controller : component.controller ? function partialCtrl(){
            // Javascript is horrible. This is how you apply arguments to a variadic constructor:
            return new ( noop.bind.apply( noop, [ component.controller ].concat( args ) ) )();
        } : noop,
        view       : component.view ? function partialView( ctrl ){
            return component.view.apply( ctrl, args );
        } : noop
    };
};
lhorie commented 9 years ago

@pygy is the lack of return data intentional in your idea?

l-cornelius-dol commented 9 years ago

@lhorie : I believe the lack of return data is intentional, because it's not a constructor of foo, but an initializer of foo, where foo is a simple {} object. See my theoretical change to 0.29's m.module function.

lhorie commented 9 years ago

to clarify, I'm asking because DI'ing the context object means you no longer have control over its creation, so, for example, you can't use the singleton factory pattern.

gilbert commented 9 years ago

@lhorie Mithril could treat it similar to the way JavaScript treats the keyword new. If the function returns something, that something becomes the context. Otherwise, foo stays the context.

l-cornelius-dol commented 9 years ago

Mithril could treat it similar to the way JavaScript treats the keyword new ...

Provided Mithril explicitly documents this behavior.

My theoretical context creation function would become:

function createContext() {
    var context;
    if(module.controller) {              
        context=new module.controller(); //FIX: 1.0 make throw; 1.1 Remove
    } else {                             
        context={};
        if(module.init) { context=module.init(context); }
    }
}

simplified, inlined and removed in version 1.x to:

var context=module.init ? module.init({}) : {}; 

though, I believe that would require init to return the passed in object, so maybe:

function createContext() {
    var ctx={};
    return (module.init && module.init(ctx)) || ctx;
}
pygy commented 9 years ago

I hadn't thought it through with respect to singleton factories, but @mindeavour adresses it. That being said, that would be re-implementing a core JS functionality for the sake of ditching this. Dunno if it' worth it...

Another point, as raised by @barneycarroll on gitter is the ability to use instanceof on ctrl objects:

But this still depends on the user not having implemented anything like 'instanceof' And the worst thing about that 'instanceof' thing is that I've thought of using it. To qualify what kind of incoming navigation a new element should get depending on whether the element previously in place was of the same type

I don't understand what he means by "what kind of incoming navigation", though.

lhorie commented 9 years ago

If the function returns something, that something becomes the context. Otherwise, foo stays the context.

That sounds like a description of what it currently does (with the exception that you create the object instead of Mithril)

function MyController() {
  return {foo: "bar"}
}

That doesn't warrant a breaking change, imho. We could talk about the merits of dropping this separately from the merits of dependency injecting the object (which is really just a variation of using this and new).

I'd argue that from the controller caller side, it's easier to .apply arguments when you don't need to .unshift an extra parameter at the beginning of the argument list, and that from within the controller, the snippet above is much more intuitive than attaching things to a injected dependency (and on a semi-related note, that DI thing is eerily reminiscent of Angular's $scope.)

barneycarroll commented 9 years ago

At the risk of putting too much into this thread, I might take this opportunity to point out that I've recently become quite a fan of the config function's undocumented this, which is the virtual DOM representation of the element within scope. Because this (haha) is undocumented, I don't think there's any harm in shifting that reference to a fourth argument (pending this suggestion of mine to expose an extra step of the Mithril lifecycle to authors)

l-cornelius-dol commented 9 years ago

Let's not get side-tracked from the actual request, which is to rename controller to clearly indicate it's actual purpose, or, more properly, the desired purpose for which it's actually often used, and that is a context object for the view. That to me, is well worth a breaking change in 0.x, trivially addressed with a simple global find/replace.

The change from constructor to initializer is, in my mind, hugely beneficial, in (a) the way it conceptually defines this as a container object, and (b) the symmetry it introduces in the two properties of the module (soon to be component, hopefully), the context initializer and the view generator, and (c) in the conceptual simplicity of having a simple view context that parallels config and doesn't imply more than it should.

But the main point of the request is to widen the narrow and somewhat contentious concept of controller to the wider concept of context.

gilbert commented 9 years ago

Angular didn't invent DI, and just cause they go overboard with it doesn't mean it's bad :)

But yes, we are getting off topic. If we want to stay backwards-compatible and JavaScript-intuitive, an alternative name for controller could be init or constructor. The docs could say Mithril uses the init function to initialize the component when you use m.module (or m.component)

lhorie commented 9 years ago

I know Angular didn't invent DI and I'm not even opposed to DI in principle, I'm just saying that usage pattern seemed very similar to $scope in the sense that it feels like magic

Btw, constructor is already a non-enumerable property of objects in Javascript, FYI.

console.log({}.constructor === Object) // true

function Foo() {}
var foo = new Foo()
console.log(foo.constructor === Foo) // true
l-cornelius-dol commented 9 years ago

@Nijikokun : Actually, I think far more correct is that the name "controller" sometimes perfectly suits what it does. Other times it "doesn't exist", that is, it's anonymous (in a pure view module), other times it's a view-model (MVVM; see my quote, earlier, from the Mithril docs, where Leo advocates it contains only a generic map named vm), still other times it's a presenter (MVP).

In actuality, the concept that embodies all these disparate things, is a "context for the view" which may be utilized in a variety of ways.

Even more importantly, the thing named "controller" isn't even the object, it's (currently) the "whatever-it-is" constructor, and (as very much a secondary idea in this thread) could be changed to be the "whatever-it-is" initializer by conforming to the same DI model as the view generator.

l-cornelius-dol commented 9 years ago

@lhorie,

it feels like magic

I have to disagree; as an explicit parameter of an init function it seems less magical than a constructor, and parallels the view function, which I think reduces cognitive load. As well it parallels the context of config.

The conceptual notion of a container for holding things for this instance of a module/component is a simple concept, and avoiding this, as the view does and in which context you've previously defended not using this avoids the nastiness of this reassignment within the same syntactic scope.

The only question before us, on this _secondary_ consideration is whether the loss of being a constructor vs. being a simple object, that is the loss of a specific type is material to the way this object is intended to be used. On that, I think not. But in the rare occasion that it is necessary, maybe allowing the init to return an object that might differ from that passed in (and which capability is explicitly documented), might suffice. Certainly, as a design choice, this is much less weird to me than a "constructor" which is really an "initializer of this" returning an object other than this, or returning anything at all (which capability I think is more a side-effect of the way JavaScript pseudo-objects evolved than a true design intent).

barneycarroll commented 9 years ago

@lawrence-dol I apologise if this is deviating even further, but do you think it's right to keep mechanics considerations —and potential changes to them — in this thread, or would it be more helpful if we moved that out to a separate issue?

pygy commented 9 years ago

On gitter, @lhorie said

I'm seeing people on both sides of the fence, so let's see where this discussion goes personally I like controller. It might be a fuzzy term that means a lot of different things, but that's precisely what controllers are: a fuzzy thing that can be used in a bunch of different similar patterns (mvc, mvvm, mvp, mvblah)

The trouble is that the function currently named "controller" is a controller (vm, presenter...) constructor/initializer rather than a controller-level entity as defined in the original MVC pattern. I found the terminology confusing, at first.

I actually wrote a post about it on the Google group, then removed it when I grasped its usage. I had refrained from bikeshedding on the name, but since we're doing it now ;-)

l-cornelius-dol commented 9 years ago

The trouble is that the function currently named "controller" is a controller (vm, presenter...) constructor/initializer rather than a controller-level entity as defined in the original MVC pattern. I found the terminology confusing, at first.

Exactly! So did I.

Furthermore, an MVC controller is not an MVVM view-model, nor an MVP presenter, etc. Worse, there's very little agreement on what constitutes a controller, even if you are using MVC. Whatever the thing initialized by what Mithril calls a "controller" is, it is not (always) the "C" of MVC.

Even Leo advocates so in the documentation and in "Put your controllers on a diet".

So the problem is two-fold; not only is the thing referenced a constructor for the object, the purpose of said object does not always (even often) conform to an MVC controller.

Also, allow me to point out that quite apart from the "it's not a controller it's a constructor" argument... there are those, like me, who don't use it as a controller and don't want it called "controller", and those who do use it as a controller for whom a more accurate universal name would do no harm.

lhorie commented 9 years ago

My reservation with the word context is that it's about as meaningless a name as the likes of FooManager and Foo.run() when you don't have all of its surrounding context (pun not intended) loaded in your brain.

The confusion between "presenter", "view-model" and "controller" still falls back to the idea that they are things with nuanced differences but with similar roles, and for better or for worse, the word "controller" has become a bit of a catch-all term for the controller-like entity in systems with models and views.

I don't have any objections about init or initialize

gilbert commented 9 years ago

As much as I like init, controller actually might be best for marketability. People recognize MVC; it makes them feel comfortable. It also makes it easier to differentiate from other similar libraries like React.

Correctness does not always bring popularity, unfortunately.

On Thursday, March 19, 2015, Leo Horie notifications@github.com wrote:

My reservation with the word context is that it's about as meaningless a name as the likes of FooManager and Foo.run() when you don't have all of its surrounding context (pun not intended) loaded in your brain.

The confusion between "presenter", "view-model" and "controller" still falls back to the idea that they are things with nuanced differences but with similar roles, and for better or for worse, the word "controller" has become a bit of a catch-all term for the controller-like entity in systems with models and views.

I don't have any objections about init or initialize

— Reply to this email directly or view it on GitHub https://github.com/lhorie/mithril.js/issues/499#issuecomment-83850335.

leeoniya commented 9 years ago

the traditional MVC as a pattern for frontend-only is very murky with regard to benefits. declaring something as MVC for marketability when in fact the C is so gutted, repurposed and discouraged compared to what people expect is quite confusing. the flexibility of javascript and the paradigm of everything living in the same space doesnt leave much for the controller. i know of several devs who basically use Backbone without using its controller aspects because they say there's not much that it adds to the picture in a browser. it's easy to say that a shared context object can be used in several ways, one of which is the C in MVC or the VM in MVVM.

edit: i actually think that it's precisely the familiarity with the prescriptionist naming/expectations of Controller that makes it weird to use in non-MVC patterns where the role it needs to take on is sufficiently different.

l-cornelius-dol commented 9 years ago

@mindeavor : Mithril could (should?) still market itself as MVx, which, theoretically should broaden the appeal.

Also, this could be done fully backward compatibly using my suggestion on the related issue #505.

l-cornelius-dol commented 9 years ago

@lhorie,

My reservation with the word context is that it's about as meaningless a name...

What the resulting object is called internally in Mithril is up to you; it could even remain controller. What it's called in our code is up to us.

It's only the name of the property that specifies it's constructor / initializer that's at issue in this thread, which should reflect the purpose of the property itself, not the purpose of the object it produces (IMO).

Though the name should not be changed until a decision is reached on #505 since it would very unfortunate to rename controller and only afterward decide the merits warrant repositioning it as an initializer instead of a constructor.

barneycarroll commented 9 years ago

@lawrence-dol just figured out how we can sidestep this whole issue without modifying Mithril at all — and at the same time address @ciscoheat's desire for complex components which are 'just views':

var component = {
    init : function( vm, options ){
        vm.initialised = true;
    },
    view : function( vm, options ){
        if( !vm.initialised ){
            this.init( vm, options );
        }

        return realView;
    }
}
gilbert commented 9 years ago

Pretty.

barneycarroll commented 9 years ago

I'm reminded of 2 of @lhorie's articles — the one on conquering fear of complex view logic and the one on thin controllers.

This ties in with a lot of what @ciscoheat was saying about keeping APIs flexible and things you were saying @lawrence-dol about the esoteric requirements of various applications.

The pattern above could easily be extended to enhance the component with different initialisers based on user input, and single components with multiple views (this isn't possible if the module is initialized as a submodule by m.module, but is with Modulator), or altogether different views depending on initialisation (again, Components branch will probably screw your DOM).

@mindeavour mentioned in the chatroom that this conditional logic might be seen as a potential performance shortcoming but the important thing to remember is that any view execution is going to result in at least one m() call which will in turn compare at least 3 object properties — and that's conservative by an order of magnitude.

l-cornelius-dol commented 9 years ago

@barneycarroll I've been thinking about such formulations.

But the point of this thread is that Mithril should accommodate this without the boilerplate, which is what my suggestion on #505 accomplishes:

var component = {
    init : function(ctx, options) {
        ctx.aProp=m.prop("Hello");
    },
    view : function(ctx, options ) {
        return m(".greeting", ctx.aProp()); // the real view
    }
}

but delivered with far greater clarity.

The performance consideration of testing for controller and init in my example is irrelevant due to the relative scarcity of component construction.

barneycarroll commented 9 years ago

@lawrence-dol yeah, you can hard-code the rename from controller to init and pass on the object in core. What I'm saying is that if we leave Mithril as it is and don't specify controller, we get that object for free and we can call it what we want. We can also call init what we want, and we can define our own conditions for when and how to call init — and / or any number of other functions.

l-cornelius-dol commented 9 years ago

@barneycarroll : Agreed, but what this and the related thread really teases out, I think, is the fact of a very narrow conception in Mithril which many (might I say most) developers seem to be skirting. That's a sign of a mistake in the abstraction, and version 0 is the place to identify and correct such mistakes. IMO.

Even Leo recommends treating the "controller" as a "view-model", and everyone is agreed that the thing so labeled is not the thing but the thing's constructor.

barneycarroll commented 9 years ago

@lawrence-dol totally. Is the fact that you can actually do this right now with one line of code not even interesting?

l-cornelius-dol commented 9 years ago

@barneycarroll : Absolutely; as is the fact that you'd want to, and/or that it's arguably cleaner and better than stock Mithril. But inasmuch as it results in a cleaner or preferable abstraction it does, I think, indicate a change in Mithril is warranted.

At the least, I'd like to be able to avoid a bug because I forgot to call init in my view, and your suggestion is a useful alternative if I/we are unable to convince Leo. But another (my bigger?) concern is that I also often use pure view-generator functions and I'd like not to have a different formulation for them than that for a view as part of a module.

Also, a lot of Mithril's flexibility and power derives directly or indirectly from its lack of opinions; IMO removing the MVC opinion in favor of a more general MVx is a really good thing.

barneycarroll commented 9 years ago

So this makes the framework even less opinionated as a modus operandi. Also, if you explicitly wrote the init function yourself with your own reasons for invoking it, the notion that you forgot to invoke it seems far less likely — and indeed far more obvious in hindsight — than, eg, I wrote the init function with offset params because I forgot how and when Mithril core applied those arguments.

l-cornelius-dol commented 9 years ago

@barneycarroll : My argument is more one of consistency and symmetry. Views are v-DOM generator functions that are injected with a component context. I'd like all my views to not have to be concerned with whether that context has been initialized, regardless of whether it's a pure view-utility or a component's view. That makes refactoring and extraction of common functionality much easier and makes views consistent.

gilbert commented 9 years ago

Mithril could (should?) still market itself as MVx, which, theoretically should broaden the appeal.

@lawrence-dol's point is a good one. I do like the sound of a MV* framework. What other framework can advertise as much? :)

ciscoheat commented 9 years ago

Thanks Barney for mentioning me, this was an interesting discussion! Since Mithril advocates ViewModels in its documentation it has already deviated from the straight MVC path of course, but being aware of that, it is still one of the most useful frameworks for doing MVC.

As I have written before, MVC is the pattern for UI and connecting the computer to the users mind. Its level of formalization and research is miles above the derivatives. I haven't seen anyone shunning or modifying MVC with full understanding of it, especially two things:

  1. M, V and C are objects
  2. M, V and/or C can be contained in the same object.

So the view function in Mithril is not the MVC View, but the interface to the framework. The real View would be the object containing the view method. Same goes with controller, but since that function is instantiated by Mithril it's a bit different, even surprising, and as a programmer I don't like surprises. But now when there is no controller requirement, I'm fine. I'm gonna use Mithril for MVC no matter what happens. :)

I'm mentioning this again if it helps to avoid a potentially sour MV* discussion. The MVC pattern (and hopefully the derivatives) doesn't concern itself with method naming, so I don't think we have to use MVC to argue for/against it. We should be able to come up with useful names anyway.

gilbert commented 9 years ago

That reminds me, I have some anecdotes about teaching Mithril:

I've actually taught Mithril to a classroom of ~18 beginner-ish students before. One of the hurdles students ran into was learning the interactions between the controller and the view, and what Mithril implicitly does with each one. I'm not sure if it's possible to make that easier to learn, but I just wanted to throw that out there.

More importantly, I'm currently writing a blog post about Mithril's component features, and the name controller is giving me a bit of trouble. In one of my code examples, it's awkward having to explain that the controller function doesn't actually play as a controller, but a view-model. In another example, I also explain that not all components need a controller.

Completely ignoring the initializer change (which is now its own issue, btw), I think changing the name controller to init will make teaching Mithril easier. This will solve my explanation problems: I won't have to betray the name controller, and I can easily say that the init function is optional.

barneycarroll commented 9 years ago

@mindeavour this is interesting anecdotal evidence. I think there's a generic problem with constructors (which ES6 class notation goes some way towards addressing) in that there's room for cognitive noise between the verb of the function and the noun of the thing. The de facto (but in no way enforced or standardised) convention is to capitalise the constructor: many expositions of the concept go something like this:

function Shape(){}

var shape = new Shape();

The assumption is that in a file where you only have one shape, shape will be meaningful enough a reference. In a situation where you have several instances, you will probably want to reference them by their particular application-specific distinguishing features (ie square, circle).

Mithril sidesteps this convention by mostly taking initialisation out of user space — initialisation is left to Mithril core and the instance is received as an argument in the view — and instead sets up a convention whereby an instance of a controller (which is almost always referenced explicitly, if at all, as a key on a component object) is generically referred to as ctrl.

Although it doesn't consciously bother me, I must admit that I've screwed up in the past by defining or referencing a component as { ctrl, view }. This is interesting when we compare mine and @lawrence-dol's coding styles: I like verbose descriptive terms for all my application-specific references, whereas Lawrence prefers highly abbreviated, usually vowel-less references.

In a way, the controller => ctrl convention violates both our coding styles.

On a related note, @StephanHoyer recently gave a talk at BerlinJS in which he demod a todo app where he never refers to ctrl: he does use the just-as-generic term scope where the context just serves to agnosticly pass the controller instance downstream, but when it comes to invoking its methods he gives it a reference descriptive of its functionality.

I think this is informative in trying to move away from generic straw man scenarios and into real application space challenges: when you come to name this reference, you should do so in function of what it means to you WRT your use case. Without examples like this, where there is a use case to analyse, we risk a continuous chicken-and-egg dilemma whereby we try to describe and rationalise things in relation to their nebulous relationships to other parts of the API.

Seeing as many of us make use of controllers all the time, maybe we'd find this conversation easier if we posted examples of our own controller-related code and analysed the semantics based on those?

l-cornelius-dol commented 9 years ago

@mindeavor : Really interesting anecdotal evidence.

I think changing the name controller to init will make teaching Mithril easier.

There's an implication in this statement that it will also, for the same reason, make learning Mithril easier.

I realize I may be alone in this to a large degree, but I think names of things really matter in programming. If something embodies a specific concept it's important to have a specific name; but if it embodies a general concept it's just as important to have a general name, else the name serves to artificially narrow the concept and creates cognitive impedance every time it's encountered, especially in a use-case where the reality differs from the concept named, e.g. using it as a view-model when it's named a controller.

@barneycarroll makes good points about most of the references being in application land and that does mitigate the problem somewhat. This change would allow the problem to be eliminated entirely without artificial boilerplate in the view, but it would not necessitate any change for those happy with the status quo unless and until the support for controller is removed as clarified in my comment on #505.

Thanks to @ciscoheat for keeping the thoughts around MVC grounded. While I appreciate that you consider that MVC is "the pattern for UI", I and others like me will amicably disagree. Positioning Mithril as MV* can only improve its acceptance, it seems to me.

l-cornelius-dol commented 9 years ago

@barneycarroll : Just as an aside, I use verbose names for the (in JS, logical) object/instance scope and names abbreviated as consonant triplets at the function/method scope. You've only ever seen examples of my code as self-contained functions. I also use a different form for indenting closing braces, both of which is why my code probably looks weird to you. So I would do function view(ctl) not function view(ctrl) or function view(controller).

ciscoheat commented 9 years ago

@lawrence-dol you're not alone regarding the importance of naming. We're having a very interesting discussion about this right now in the DCI group. Deep stuff about local symmetries and architecture. Check it out!

I'm not the "agree to disagree" kind of guy, I'd rather find a logical and factual reasoning and search for a common ground there. I mean, we're not discussing something subjective like our favorite colors and there's no score count for different opinions, so I'm curious what you disagree about in the sound research that has been done about MVC?

If you guys think this is off-topic, let's cut it here. If we want to just play around with some fun technology, it may be so. But if we want to write great software that put users in the front seat, it probably isn't.

l-cornelius-dol commented 9 years ago

@ciscoheat : While I'd love to have you try to convince me of the merits of pure MVC (perhaps we can swap emails to take it offline), I think the "it should be MVC" is OT. The main thing is that within the industry there is widespread disagreement on which of the MVx patterns is the "One way to rule them all", so Mithril positioned as "MVx" seems stronger to me than Mithril positioned as "MVC that can be coerced into some other flavor of MVx". That's all.

Much more important to me is that the thing called "controller" does not actually constitute a "controller", at least not for everyone using Mithril and/or not all the time.

I think I need to step back on these two issues other than responding if specifically addressed; I've said my piece and I'm beginning to feel a little strident. I'll let the others weigh in and think about it.

ciscoheat commented 9 years ago

Well, there's not much convincing I need to do; the research is there already, based on Kay, Engelbart, Alexander and other giants on which shoulders we should stand if we want to build great things, instead of standing on our tiptoes. I've done the studies (here's a good start for MVC with references in the articles), I don't think proponents of alternative models have. For example Gossman when introducing MVVM. He starts with

... the View is the responsibility of a designer rather than a classic developer ... The design is almost always done in a declarative form like HTML or XAML, and very often using a WYSIWYG tool such as Dreamweaver, Flash or Sparkle. ... In short, the UI part of the application is being developed using different tools, languages and by a different person than is the business logic or data backend.

Apparently the View and UI are now reduced to declarations. That's OO thrown out the window in the first sentences. (A funny thing btw, those claims are not true regarding Mithril. You're doing everything in the same, general-purpose language.)

Afterwards Gossman is getting rid of the Controller through data binding and additional abstractions (the ViewModel itself). Unfortunately that doesn't solve what Reenskaug mentions in the first reference:

An important aspect of the original MVC was that its Controller was responsible for creating and coordinating its subordinate views.

I cannot stress the importance of this sentence enough. The latest (March 2015) sanctioned explanation of MVC is available here in the second appendix, and the statement still stands:

The MVC Model is a representation of the user’s mental model. The User is in the driver’s seat and a View bridges the gap between his or her mind and the Model. Different Views show different aspects of the Model in a way that can be readily intuited by the human. The human intuition also makes it clear how to give input to the Model through the View. A Controller sets up one or more Views and coordinates them, e.g., by making a selection show itself in all Views simultaneously.

MVVM has no place for this "missing" (it was there all the time) piece of the puzzle, coordination, same with "The human intuition also makes it clear how to give input to the Model through the View." since the View in MVVM is just a declarative DSL. But that probably resonates well with those who craves separation of concerns at any cost. A final quote from another giant, James Coplien:

.. most software designers are clueless about mental models, modality, focus and locus of attention, GOMs analysis, and the things that really matter. Most programmers focus instead on polymorphism, coupling and cohesion, number of methods in the interface, and (horrors) lines of code.

So based on all this, removing the "C" will make things worse in the long run. We should rather restore it to its forgotten purpose instead of substituting it and thereby ignoring solid research over multiple areas.

I'll step back as well, I'm not much for arguing anyway (people learn best by choosing themselves). My email is ciscoheat at gmail if you Lawrence or anyone else wants to discuss further.

gilbert commented 9 years ago

Thank you @ciscoheat for this writeup, it was very informative. I enjoy reading the research and history of software terminology.

Just yesterday I finished teaching my students a design patterns lesson on MVP vs MVC in JavaScript. I demonstrated how to accomplish both using plain jQuery. In my code examples, the view was constructed with jQuery, and the controller was a set of static functions (the model was what anyone would expect a model to be). When it came time to introduce data-binding, I introduced the concept of a view-model, and modified the view to bind data to it. My reasoning was that the view-model held all transient, view-related state, while the controller was meant to house actions for the view to take.

Your phrase "separation of concerns at any cost" struck me to reevaluate my decision to separate the controller and the view-model. Are these two concepts really that distinct? Is there any real advantage in separating them?

I could not think of a good reason for the separation. I will admit, I have always separated view state from the controller only for the sake of "good software design practice", and never consciously considered the tangible benefits.

If we can all agree that it's actually ok for a controller to hold view-related state, and in turn have this evidence and reasoning in documentation, then the name controller for Mithril stands perfectly clear. We can then resolve and put this GitHub issue to rest.