Inline components in Lucee 6

We are working to support for inline components in Lucee 6 and they can be defined in a similar way you define closures inside a .cfc or .cfm template today.

test=component {
   function init(caller) {
      variables.caller=caller;
   }
   function getCaller() {
      return variables.caller;
   }
}
dump(test.getCaller());

Like a closure the inline component should “see” the caller scope (birth place). But in contrast to a closure this is not happening implicitly, because we do not want to break encapsulation of the component. Because of that the caller scope is passed as part of the init function (if defined) as you can see in the example above.
Like a closure you need to assign the component directly to a variable. it is NOT possible to load it with a new operator afterwards, but of course you can clone it via duplicate.

What do you think?

Question: why would you implement special handling of this caller thing at all? Why make these behave any different from any other CFC / object, in that if a method needs a value from its calling context, then it passes the value in? I’m not being obtuse (well: not on purpose), I simply am missing why this is a consideration at all. But I probably am missing something.

Looking at how the same thing is done in JS and PHP, there doesn’t seem to be any consideration of this, so wonder maybe if I’m not missing something…?

First of all, the init function of course is optional, you don’t have to define it.
Remember, we do not have a explicit constructor for a inline component, you cannot do

test=component {
   function init(a,b,c) {
      variables.caller=caller;
   }
}(1,2,3);

At least it is not planned, but all is up for debate at this point :wink:
Sure you could do it as following

test=component {
   function init(caller) {
      variables.caller=caller;
   }
}
test.init(variables);

BUT this is not the same, as with the scope in a closure, the caller scope is not the same as the variables scope, the caller scope will point to the local and argument scope of the birthplace as well, if created within a function. so this is possible.


function test(fromArg=0) {
  var fromLocal=1;
  variables.fromVariables=2;
  
  var test=component {
    function init(caller) {
        variables.caller=caller;
    }
    function getData() {
        return variables.caller.fromArg&variables.caller.fromLocal&variables.caller.fromVariables;
    }
  }
  return test;
}

dump(test(0).getData());

so you CAN do it exactly the same as a closure when it comes to scope handling, but the good thing is, it is optional, if you do not need this, simply do no init method.

Sure it is not necessary, in contrast to a closure, you can pass the data in after the creation, so maybe that would be the cleaner approach.

Gotcha, yeah this makes sense. I was thinking too much from the perspective of a traditionally-defined component (which was dumb and wrong-headed of me).

I still think passing the “caller” into the constructor method is not quite nailing it though, but I can’t put my finger onto why.

I wonder if this would be a thing:

function test(fromArg=0) {
  var fromLocal=1;
  variables.fromVariables=2;
  
  var test=component bind context {

    function getData() {
        return context.arguments.fromArg & context.local.fromLocal & context.variables.fromVariables;
    }
  }
  return test;
}

There’s two things there:
a) how the context is bound to the CFC instance. I’m not in love with the nomenclature “caller”: I know there’s precedent with it in custom tags, but it sounds hokey when used here. But that’s a minor thing.
b) How elements from within that context are referenced; rather than flattening them out, preserve a reference to their original scope?

Thinking about it now, is it actually a problem to automatically avail a caller / context to the object without needing the concrete “pass it via init” or my example of explicitly binding it?

Components already automatically have this, variables, super available. What’s wrong with automatically also providing caller when it’s an inline component? You mention “we do not want to break encapsulation”, but I’m not so certain this is breaking encapsulation; any more than closure is breaking encapsulation in function expressions? The context is part of the function expression; why would it not be part of a component expression?

I wonder if we can attract @seancorfield along to this thread? He’d have some valuable thoughts on it, I’m sure.

NB: not disagreeing with you at all - you’ve sold me that there’s merit in your mooted approach - it’s just made me think about it some more :wink:

Cheers.

I’ve been giving this some thought on and off today since Adam tagged me… and I don’t like either proposed approach.

I think Adam’s proposed syntax is just ugly and I don’t think there’s really a precedent for introducing a variable name like that?

I sympathize with @micstriit’s justification for his proposal but it still feels like boilerplate. In the absence of better initialization syntax, I guess it’s reasonable, however.

It would lead me to suggest something that is a bit inspired by Scala and some proposed changes for Java I think… Perhaps component(arg1,arg2..) could be a shortcut for an init() method that has those named arguments that simply assigns them to variables scope?

test=component(caller) {
    function getCaller() {
        return variables.caller;
    }
}
dump(test.getCaller());

I kinda like Adam’s naming, so it would be:

test=component(context) {
    function getData() {
        return variables.context;
    }
}
dump(test.getData());

One of the upside/downside for closures/lambda function (short named as function below) is that they always “see their birthplace”.

  • Upside it allows to do stuff that would otherwise no be possible, because they can see variables from the place they got created and because you cannot manipulate the inner state of a function this is useful.

  • Downside because EVERY function has that reference to that birthplace, what comes with an overhead to provide this and blocks memory from release (local and argument scope from birthplace) as long that function lives.

Back to inline components
So for me was important, if you don’t set an init function in the inline component, no caller scope is created, so we only do the overhead when requested by the component.
But as more as i think about, as more i do ask myself, where is the benefit in having that.
In opposite to a function where i cannot manipulate the inner state, with a component i can do. so following up on my example from before, i could also do

function test(fromArg=0) {
  var fromLocal=1;
  variables.fromVariables=2;
  
  var test=component {
    function set(key,value) {
        variables.caller[key]=value;
    }
    function getData() {
        return variables.caller.fromArg&variables.caller.fromLocal&variables.caller.fromVariables;
    }
  }
  test.set("fromArg", fromArg);
  test.set("fromLocal", fromLocal);
  test.set("fromVariables", fromVariables);
  return test;
}

I know that is a very bad example and nobody every will do that in rea live :wink: I simply wanna show how to achieve the exact same without that caller scope concept.

With inline components that caller scope concept is just a nice to have at best.
So i think we should NOT do it.

2 Likes

I agree that the caller scope(s) is an unnecessary addition, given that you can “workaround” the lack of it by using explicit setters.

Good discussion here. My experience with inline classes are mostly via so-called anonymous classes in Java. I’m not necessarily implying we just copy Java, but their anonymous classes…

  • do not have a constructor
  • can access any variables/method in the declaring block

And Java uses the syntax

new interface-or-class-name() { class-body }

So a good example would be something like

runnable r = new runnable() {
      public void run() {
          //code for the run method goes here
      }
};

I think I’m unlikely to use inline classes that much in the Lucee/CFML examples shown above as just one-off classes (unless I’m shown some compelling use cases I haven’t thought of yet) but I think I would find more use for it in the manner that Java does it where inline classes are used to either

  • extend an existing class on the fly
  • create a class on the fly which implements an existing interface.

This would be especially useful to me if I can use it to extend/implement Java classes/interfaces as well as CFML. I work with a lot of java libraries, especially in CommandBox, and it is a quite common pattern now to be offered an abstract class which needs to be extended and a specific method overridden to implement a behavior. There are several places where I’m forced to write my own stand alone java classes, compile them into a Jar, and add that jar to the class path of CommandBox simply to facilitate something as simple as Overriding jGit’s authentication mechanism. Real examples here and here. If I were writing a native Java app, both of those examples could have been accomplished with less boilerplate via an anonymous class and it pains me when using Java in CFML is actually more work than using Java in Java!

I am curious about the overhead of making the “birthplace” context implicitly available like closures. I think having the context available implicitly is an ideal scenario and I use it quite a lot in my Java anonymouys classes. Ex here. I hate to see the language design adding boilerplate to avoid the overhead. Are there some performance tests the team can share that demonstrate the actual measurable overhead of this and how big it is?

My overall take on this feature is yes, let’s do it. But don’t bother unless I can

  • extend existing classes
  • implement interfaces
  • and do so on either CFML or Java classes/interfaces

yes they can access all methods, but not variables, to access a variable they need to be final.

Yes, I am aware of that, but since CFML doesn’t have a final modifier I skipped over it since it didn’t really apply.

CFML may not support “final” but Lucee does

Sure not for variables :wink:

But there are good reasons Java only support this for final variables, in a static language like java you you only have to link the variables that are used within the anonymous class and in order to link them you have to make surethey are final.
In Lucee you have to link all the scopes, this add a lot of overhead and even worse it completely breaks encapsulation, because you implicit links everything, not explicitly a couple of variables.

In short the concept from java cannot be adapted to Lucee.

In Lucee we have 2 places where we produce an overhead i really really hate, but we cannot avoid.

  1. the function queryExecute always set the attribute “result” with the underlaying query tag, what always creates additional metadata, that is the main reason, i myself NEVER EVER use that function.
  2. closures and lambda function always link their birthplace, even i do not need it, i HATE that.

linking the birthplace in inline components would add #3 to my list :wink:

Final components never should have been added to Lucee IMO. That falls in the category of random Java features we thought would be cool, but really didn’t make sense in a dynamic language. But I digress-- as you said it doesn’t apply to variables anyway.

Yep, that’s a price we pay for being a dynamic language. To me, that doesn’t mean we don’t do it-- it just means can do more flexible things than Java and we have to weigh it against the price it comes with.

How so? There is no memory overhead since it’s nothing more than a reference to the scopes so long as the lifespan of the closure/inline class doesn’t outlive the original pc and therefore artificially expand the lifespan of the original pc as well. So, for example, if I create a closure and it only lives for that request, there is no memory “overhead”. Only if I create a closure, then persist it somewhere like the application scope does it force all of the original scopes from its birthplace to stay in memory as long as the closure is in memory. How much overhead that is depends on how many closures I keep in memory and how many unique birthplaces they are also holding in memory. But it is not guaranteed to be a lot of overhead in every case. Again, this is a trade off we pay.

If by “overhead” you are referring to the additional “scope hunting” that happens to climb up the scopes and find a variable, that’s a price CFML has always paid for our loose scopes. If I don’t use any of the bound scopes and only reference local variables, then I do not pay any price at all.

And? That’s a feature, not a bug. It is by design that a closure can access variables from it’s birth context, and quite frankly we do it all the time in CFML with scopes like this, variables, session, request, application, server, or cgi– all of which are accessible to a UDF without explicitly passing them into the arguments.

I disagree. You have laid out an opinion that you do not like the loose encapsulation and you dislike the potential for overhead in scope binding. That’s fine. But it doesn’t mean the feature cannot be accomplished. It means it comes with a price which we weigh against the benefits.

5 posts were split to a new topic: Queryexecute or cfquery best practice

@bdw429s great discussion, we have opposite views on the topic of birthplace vs. encapsulation, that is great to find a common ground. When i was implementing closures it really did hurt me to add that “unnecessary” (in my view) overhead and because I could not see the benefit pays out. I was against it, but sure i had to add it, it still bugs me everytime i do a closure, actually for that reason i prefer to do code like this if i do not need to have access to the birthplace

function myFilter(...) {
   ...
}
DirectoryList( path:path, filter=myFilter );

for that reason i was coming up with the idea to have the caller in the optional init function, that way i can control if i wanna have the caller or not. in my opinion this is one of the places we should not blindly follow Java. But on the other side i can understand the appeal.

CFML in general suffer from missing encapsulation, you can for example access all scope all the time, but is that a bug or feature, i think it is both.

Sorry, but this doesn’t look right.
It should follow the function syntax and be more like javascript handles classes.
This way you can create multiple components in a single file, then include and instantiate them:

component MyComponent() {
  function init(a, b) {
  }
}

mycomp = new MyComponent(a ,b)

...

component-lib.cfm

<cfscript>
  component Square() {
    ...
  }

  component Circle() {
    ...
  }
</cfscript>

...

my-template.cfm

<cfscript>
  include "component-lib.cfm"
  square - new Square()
  circle = new Circle()
</cfscript>

1 Like

Actually that is the syntax we choose for sub component, the difference between sub component and inline components are, inline have no name on their own. You could also call them anonymous components.
I see as a counterpart to closures that also define no name, you could also call closures “anonymous functions”.

1 Like

Great, that makes perfect sense. Powerful stuff!

this has been implemented in 6, have play with the latest snapshot!

https://luceeserver.atlassian.net/browse/LDEV-4125

2 Likes