Implementing native Mixin functionality for Components

From [LDEV-46] - Lucee

See Adam Cameron's Dev Blog: Looking at PHP's OOP from a CFMLer's perspective: traits

Both PHP and Ruby (and no doubt various other languages) have a formal concept of mix-ins.

PHP does it with traits PHP: Traits - Manual; Ruby does it with modules: Programming Ruby: The Pragmatic Programmer's Guide.

Having messed around with both, I find it a good technique for
composing a class, and would alleviate a lot of the artificial
dependency injection CFMLers need to do to effect similar results.

We can already use include "some.cfc"; from within a CFC file to dynamically mixin functionality from other components, but what else, if anything, can we do to the language to make dynamic mixins work for developers natively in the language?

To show how CFML frameworks are already handling this, WireBox has supported the following for quite some time:

// map with mixins
map("MyService")
	.to("model.UserService")
	.mixins("/helpers/base");

// map with mixins as list
map("MyService")
	.to("model.UserService")
	.mixins("/helpers/base, /helpers/model");

// map with mixins as array
map("MyService")
	.to("model.UserService")
	.mixins( ["/helpers/base", "/helpers/model"] );


// Via annotation
component mixins="/helpers/base"{
}

http://wirebox.ortusbooks.com/content/runtime_mixins/index.html

What’s that doing under the hood: include-ing the specified files I guess. Is it doing anything else beyond that? Is /helpers/base (etc) a CFC?

Adam, WireBox actually supports mixins from either a cfm or a cfc file. The functionality I showed above assumes that /helpers/base is a .cfm file and it cfincludes them in a special mixer class and then adds in the ones that don’t already exist.

If you want to mix in methods from another CFC, we have something called virtual inheritance that allows one class to virtually extend a base class at runtime. I also use this sometimes when I’m not doing a true “is a” relationship but rather want to “aggregate” some base functionality that’s defined in a CFC. This is how Coldbox interceptors and event handlers automatically extend the framework’s base class without the developer needing to specify extends in their CFC.

map("UserService")
	.to("model.users.UserService")
	.virtualInheritance("BaseModel");

http://wirebox.ortusbooks.com/content/virtual_inheritance/index.html

So it’s a <cfinclude>, basically (yes, I get that you’re adding more “stuff”, but still). I’m not entirely sure your *box observation has much relevance to this discussion, given it’s about adding a new language-level construct.

I’ll elaborate separately, but I thought I’d add this here as I’m getting slightly worn down by you acting like Ortus Solutions’ version of Clippy: “It looks like you’re doing [SOMETHING ELSE, FFS]: there’s a *box product for that”. Can you possibly spare us? (where that applies to all places you see an open door that you can figuratively wedge your salesman’s foot into).

Thanks Dom. I did’t want to railroad my way into this initially so as to let other ppl think about it.

A coupla things mixins would do that simple includes don’t:

  • mixins are resolved at compile time, so can provide an implementation for an interface. Perhaps this could also work at runtime, but I think the interface-fulfilment check would need to be delayed until “needtime”. I think there’s a lot of backlash as to whether interfaces have merit in dynamic languages, but I think that’s a different discussion: they do exist in CFML, and as it stands I presume they will exist in .lucee. So having mixins be able to provision ducktyping would be useful.
  • mixin syntax would allow mixing-in a function with a different name, eg:
use someMixin {
    mixinFunctionA as neededFunction1
    mixinFunctionB as otherNeededFunction2
    // etc
}

In this example someMixin might define a bunch of functions, but our class only need two of them… and it needs them to be called something different. This is formalised here.

There’s other syntactical nuance too, but those are the main ones.

We mostly use traits (in PHP) for fulfilling interfaces.

Now this can all be effected after a fashion with other code, but it’s the “after a fashion” bit that is what’s being mitigated here.

I don’t think it’s a vital feature, and is definitely not a glamorous one. But might be worth evaluating.

On the other hand I don’t mean to suggest it for the CFML implementation… and maybe the .lucee implemention could do without the situation which “necessitates” it? Or on the other hand is it a cleverer way to deal with the idea of “includes”, going forward?

I’m sorry you feel that way Adam. I think there’s a great deal of value in the precedence set already by frameworks. Part of the TAG’s internal discussion on this already has been whether this even deserves to be part of the language or up to libraries to implement. It was after those internal comments that I added my first response here as its contents were very applicable in that context. And finally, my second comment was in reply to your question asking for more details about how WireBox mixed in UDFs from cfm/cfcs.

What about simply allowing extends to be a list.
a.cfc:` component extends=“b,c,d” { … }

@edatodi: Java (and thus CFML) is single-inheritance because of the diamond problem . I don’t think it’s a good idea to go the direction of multiple-inheritance.

I understand the order of merging would need to be decided on; but, this and variables are each merged scopes between the inherited classes; so, it seems like that should be able to work itself out. Method super calls, could run-time recuse, or detection of identical function definitions along the super chain could be used to trigger a “recursive super” error message. It doesn’t need to be implemented identically to a Java class. Just because Lucee is written in Java does not mean it needs to be the same as (or limited by the limitations of) Java.