Better constructors for Lucee

388 views
Skip to first unread message

Walter Seethaler

unread,
Mar 27, 2015, 2:33:10 PM3/27/15
to lu...@googlegroups.com
Hello everyone,

do you like the init methods (constructors) in CF? I use CFCs a lot and ran into some issues with the way CF instantiates its objects. Especially on a framework level, where you provide CFCs for instantiation or subclassing and have no guarantee, how the people are using your classes. When I looked into the Java constructor specification, I realized that it is a very robust definition that avoid every little issue I ran into. So I tried to translated the spec to CF, solve the backward compatibility issues and provide a robust long-term solution. I hope this is a good time and place to share it.

In OO languages it is almost a standard to have a special method, dedicated to the object creation - the constructor. In CF the closest thing we have is the pseudo constructor, whereas the init method is mainly treated as a normal method. Only the new operator partially treats init as a constructor (but new does not call super.init() automatically). I would love to have more robust (better) constructors in Lucee.

I will discuss 3 topics:
  1. Motivation
  2. Specification
  3. Backward compatibility

1. Motivation
  1. The constructor contains the code that sets up the instance. It should be guaranteed that the constructor is called, even when the class is part of inheritance.
    • In CF you can either hope that everybody calls your init method, or try to assure it programmatically.
  2. The instantiation of a CFC should not depend on how the class is created at runtime. The instantiation should be the same, no matter how the class is created.
    • The init method and the init methods of all base classes should be called under all circumstances, not matter if you use new, createObject, cfobject or cfinvoke.
  3. It should not be possible to call a constructor multiple times, this just inflicts unnecessary problems.
    • When I program CFCs that are used by others, I tend to avoid that programatically. It should not be necessary.
  4. The init method should always return the instance.
    • As init can return something else, I tend to specify the return type (for clarity), which adds an unnecessary type check at runtime.
    • The return this; should not be necessary (as in ACF). 
  5. It should not be possible to concurrently call the constructor.
    • It is theoretically possible and I wasted time thinking about it.
  6. You should be able to enforce the behavior off your CFCs.
    • With the final keyword and a guaranteed constructor call, you can control the behavior of your CFC pretty well, even if the class is extended.
  7. A programming language that is easy to use an to learn, should: be intuitive, provide good error messages and have a good documentation. Those 3 points are not true for constructors in CF (in my opinion).
  8. The problems above make it more difficult to provide high software quality (e.g. in OS frameworks, tools, libraries).

2. Specification
  1. Constructors can not be overridden.
  2. super() calls the constructor of the base class.
  3. If super() is not defined in the constructor source, the compiler adds it on top of the constructor source.
  4. If the super() call (without arguments) is not possible - because the base constructor has at least 1 required argument (without default value) - the compiler throws an error.
  5. Constructors do not have a return value.
  6. If no constructor is defined, the compiler adds the standard constructor:  function init(){ super(); }
  7. Constructors can only be called once.
  8. (It is guaranteed that) constructors are called on instantiation.
  9. Constructors can not be defined in an included .cfm.
    • As constructors are a compile time feature, I guess dynamic includes would cause problems.
  10. getComponentMetaData() and other reflection methods do not call the constructor.
    • afaik getComponentMetaData() calls the pseudo-constructor in Lucee (which is false) - while ACF does not.
  11. Access modifiers can be: remote, public, package or private.
    • Not sure about remote and private.
    • As private methods can be called from subclasses in CF, private would make a class abstract.
  12. Access modifier public is default and can be omitted.
  13. Pseudo constructor (the source code outside of the functions).
    1. Either pseudo code is not allowed anymore (clean approach),
    2. or it is called prior to the constructor.
  14. Lucee.cfc does not call super().
  15. Syntax (the compiler must be able to detect the new syntax to provide backward compatibility for the old syntax: function init()).
    • If a new constructor is present in a CFC the compiler and the CFC instance must honor this spec.
    • If it is not present, everything should behave backward compatible.
    • component{

         
      public this( required numeric foo ){
             
      super( foo+1 );
         
      }

      }
    • I personally do not like that Java uses the classname as the constructor name, because the classname in CF is the filename and thus I can easily rename my CFC without the need to change the source of the CFC. So I came up with this syntax.
  16. Execution order
    • Not sure about this one. The questions are:
      • When are properties instantiated (default values).
      • When is the constructor and pseudo constructor called?
      • Are the other methods available in the constructor?
        • In Java the compiler gives a warning, if the constructor uses a method that can be overwritten in subclasses. So I guess in Java it is possible to use a method in the constructor that is overridden at runtime.
        • With the final keyword in Lucee 5, every developer can decide to only use methods in the constructors, that can not be overwritten (declared as final).
    • My try:
      1. ...
      2. make base methods available and override base+1 methods
      3. make methods available and override base methods
      4. ...
      5. make base properties available and override...
      6. make properties available and override...
      7. ...
      8. call base pseudo constructor (see 13.2)
      9. call pseudo constructor (see 13.2)
      10. call constructor (which calls super() as specified) 
  17. Object creation
    1. new Foo() - creates the instance and calls the constructor as defined in 16.
    2. createObject("component", "Foo")  - creates the instance and calls the constructor as defined in 16.
    3. <cfobject> - creates the instance and calls the constructor as defined in 16.
    4. <cfinvoke> - creates the instance and calls the constructor as defined in 16 and calls the method specified.

3. Backward compatibility
  • Without a new constructor present, everything behaves as before.
  • With the new constructor present in the source code.
    • If a method with the name init is present, it is treated as a normal function.
    • new, createObject, cfobject and cfinvoke honor the spec.
    • It is not possible to instantiate a CFC without honoring the spec.
  • Inheritance between CFCs with old and new constructor should work well.
    • Most important is, that CFCs with the new constructor can extend CFC with the old init constructor.


What do you think?

Tristan Lee

unread,
Mar 27, 2015, 3:49:38 PM3/27/15
to lu...@googlegroups.com
In section 17, it sounds like you are anticipating always calling the constructor.

new Foo(); allows you to specify arguments. What about for createObject() and the tag-based solutions? createObject("Foo")()?

What about in cases where a compont does have a constructor, but it doesn't need to be invoked to access other methods, such as those that may be interpreted as "static"?

Walter Seethaler

unread,
Mar 27, 2015, 4:52:44 PM3/27/15
to lu...@googlegroups.com
Am Freitag, 27. März 2015 20:49:38 UTC+1 schrieb Tristan Lee:
In section 17, it sounds like you are anticipating always calling the constructor.

For the new constructors, yes. The dev has the guarantee that the constructor is called under all circumstances and doesn't need to think about how the class behaves if the constructor is not called. I don't see a use case where someone builds a class with a constructor that sometimes needs to be called and sometimes not. Any other method could be used for that.
 
new Foo(); allows you to specify arguments. What about for createObject() and the tag-based solutions? createObject("Foo")()?

I only use new Foo(), even in cfml, e.g. <cfset new Foo().doSomething()>. So I am not sure if the other variants should be deprecated or extended to support the new constructors. My main focus is OO in cfscript based components. 

A possible solution would be a 3rd Argument for createObject() and a initArgs attribute for cfobject and cfinvoke. 
 
What about in cases where a compont does have a constructor, but it doesn't need to be invoked to access other methods, such as those that may be interpreted as "static"?

I would say, I you have a class where some methods only work after call of init and other methods are designed to work without init, this is not the greatest design. The stateless methods should be refactored to its own class which doesn't need a constructor or the methods could be declared as static (I think thats what you would do in Java). So no, this would not work anymore. 

I think static methods wouldn't trigger instantiation at all and thus not call the constructor nor the init method.

Michael Offner

unread,
Mar 27, 2015, 5:29:26 PM3/27/15
to lu...@googlegroups.com
Great post! Please see my comments between the lines. 

Micha 

Am Freitag, 27. März 2015 schrieb Walter Seethaler :
Hello everyone,

do you like the init methods (constructors) in CF? I use CFCs a lot and ran into some issues with the way CF instantiates its objects. Especially on a framework level, where you provide CFCs for instantiation or subclassing and have no guarantee, how the people are using your classes. When I looked into the Java constructor specification, I realized that it is a very robust definition that avoid every little issue I ran into. So I tried to translated the spec to CF, solve the backward compatibility issues and provide a robust long-term solution. I hope this is a good time and place to share it.

In OO languages it is almost a standard to have a special method, dedicated to the object creation - the constructor. In CF the closest thing we have is the pseudo constructor, whereas the init method is mainly treated as a normal method. Only the new operator partially treats init as a constructor (but new does not call super.init() automatically). I would love to have more robust (better) constructors in Lucee.

I will discuss 3 topics:
  1. Motivation
  2. Specification
  3. Backward compatibility

1. Motivation
  1. The constructor contains the code that sets up the instance. It should be guaranteed that the constructor is called, even when the class is part of inheritance.
    • In CF you can either hope that everybody calls your init method, or try to assure it programmatically.
The problem here is historical, coldfusion 6 indroduced components but without the new operator and no init method.
Later (version 7,8,9?) added support for the new operator and init. But for backward compatibility the old way to instantiate components make no use of this new pseudo constructor.
In Lucee 5 we deprecate the function createObject,cfinvoke and cfobject, so change this functionality makes no sense anymore, best avoid them. 
  1. The instantiation of a CFC should not depend on how the class is created at runtime. The instantiation should be the same, no matter how the class is created.
    • The init method and the init methods of all base classes should be called under all circumstances, not matter if you use new, createObject, cfobject or cfinvoke.
Make sense, we need to think about this with the new dialect. 
  1. It should not be possible to call a constructor multiple times, this just inflicts unnecessary problems.
    • When I program CFCs that are used by others, I tend to avoid that programatically. It should not be necessary.
Problem here is that if you load the component via createObject you call init after construction 
    • The init method should always return the instance.
      • As init can return something else, I tend to specify the return type (for clarity), which adds an unnecessary type check at runtime.
      • The return this; should not be necessary (as in ACF). 
    The return type is not necessary! Lucee acts exact the same as acf here 
      • It should not be possible to concurrently call the constructor.
        • It is theoretically possible and I wasted time thinking about it.
      • You should be able to enforce the behavior off your CFCs.
        • With the final keyword and a guaranteed constructor call, you can control the behavior of your CFC pretty well, even if the class is extended.
      • A programming language that is easy to use an to learn, should: be intuitive, provide good error messages and have a good documentation. Those 3 points are not true for constructors in CF (in my opinion).
      In my opinion the best way is to deprecate createObject, cfinvoke ,cfobject and remove the. For the doc . 
      1. The problems above make it more difficult to provide high software quality (e.g. in OS frameworks, tools, libraries).

      2. Specification
      1. Constructors can not be overridden.
      What you mean with this? 
      1. super() calls the constructor of the base class.
      +1 
      1. If super() is not defined in the constructor source, the compiler adds it on top of the constructor source.
      +1 
      1. If the super() call (without arguments) is not possible - because the base constructor has at least 1 required argument (without default value) - the compiler throws an error.
      Problem is the compiler cannot decide this, Lucee is not a static language like for example Java, things like this need to be decided at runtime. 
      1. Constructors do not have a return value.
      It was that way in earlier version, we changed that for comaptibility to acf, but this should be the case for the new dialect 
      1. If no constructor is defined, the compiler adds the standard constructor:  function init(){ super(); }
      2. Constructors can only be called once.
      3. (It is guaranteed that) constructors are called on instantiation.
      Init is called after construction, changing this could break existing code, the body of the component it the only real constructor. 
      1. Constructors can not be defined in an included .cfm.
        • As constructors are a compile time feature, I guess dynamic includes would cause problems.
      You cannot turn Lucee in a static language, I strongly disagree on this 
      1. getComponentMetaData() and other reflection methods do not call the constructor.
        • afaik getComponentMetaData() calls the pseudo-constructor in Lucee (which is false) - while ACF does not.
      No this methods are not producing a instance of the component 
      1. Access modifiers can be: remote, public, package or private.
        • Not sure about remote and private.
        • As private methods can be called from subclasses in CF, private would make a class abstract.
      Lucee 5 indroduces the keyword static, with that the modifier private makes a lot of sense, for example to do a singleton 
      Component {
      private function init(){} 
       Public static getInstance(){if(isnull(static.instance))static.instance=new Test(); return static.instance;}
        • Access modifier public is default and can be omitted.
        • Pseudo constructor (the source code outside of the functions).
          1. Either pseudo code is not allowed anymore (clean approach),
          2. or it is called prior to the constructor.
        • Lucee.cfc does not call super().
        • Syntax (the compiler must be able to detect the new syntax to provide backward compatibility for the old syntax: function init()).
          • If a new constructor is present in a CFC the compiler and the CFC instance must honor this spec.
          • If it is not present, everything should behave backward compatible.
          • component{

               
            public this( required numeric foo ){
                   
            super( foo+1 );
               
            }

            }
          • I personally do not like that Java uses the classname as the constructor name, because the classname in CF is the filename and thus I can easily rename my CFC without the need to change the source of the CFC. So I came up with this syntax.
        +1 
        You haven given that a lot of thoughts and it makes a lot of sense, I like it and it is definitely worth consider this approach for the Lucee dialect, we need to check what this men's in detail to implement it. 

        --
        You received this message because you are subscribed to the Google Groups "Lucee" group.
        To unsubscribe from this group and stop receiving emails from it, send an email to lucee+un...@googlegroups.com.
        To post to this group, send email to lu...@googlegroups.com.
        To view this discussion on the web visit https://groups.google.com/d/msgid/lucee/169c34c3-b5c9-450d-97e1-d549c2a24997%40googlegroups.com.
        For more options, visit https://groups.google.com/d/optout.

        Walter Seethaler

        unread,
        Mar 27, 2015, 7:36:34 PM3/27/15
        to lu...@googlegroups.com
        Thanks for the feedback Micha, appreciate it.


        Deprecating createObject, cfobject and cfinvoke was a good choice.


        The return type is not necessary! Lucee acts exact the same as acf here 
          Not the return type, but the return statement. I think the following constructor returns the instance in ACF but null in Lucee - when called via new Foo(). Not sure, have to check it.
          public function init(){}


          1. Constructors can not be overridden.
          What you mean with this?  

          If a normal method is overridden it replaces the method from the base class, whereas the constructor is always there and always called. The statement just makes clear, that inheritance works different with constructors.
          component{
             
          function foo(){ return "no"; }
             
          function bar(){ foo(); }
          }

          component
          extends="BaseClass"{
             
          function foo(){ return "yes"; }
          }

          SubClass.bar()  //returns "yes"


          1. getComponentMetaData() and other reflection methods do not call the constructor.
          • afaik getComponentMetaData() calls the pseudo-constructor in Lucee (which is false) - while ACF does not.
          No this methods are not producing a instance of the component 

          Will recheck this and report a bug if I am right. I think I once got a runtime error (Foo does not exist in request) when I called getComponentMetaData() on a CFC like this

          component{
             request
          .foo;
          }




            To unsubscribe from this group and stop receiving emails from it, send an email to lucee+unsubscribe@googlegroups.com.

            Jochem van Dieten

            unread,
            Mar 28, 2015, 1:14:12 PM3/28/15
            to lu...@googlegroups.com
            On Fri, Mar 27, 2015 at 7:33 PM, Walter Seethaler wrote:
            • With the new constructor present in the source code.
              • If a method with the name init is present, it is treated as a normal function.
              • new, createObject, cfobject and cfinvoke honor the spec.
              • It is not possible to instantiate a CFC without honoring the spec.
            And entityNew()? And especially entityNew(entity, props)?
             
            • Inheritance between CFCs with old and new constructor should work well.
              • Most important is, that CFCs with the new constructor can extend CFC with the old init constructor.
            IIRC entityNew(entity, props) currently bypasses the setters for properties. But you suggest setting up the setters before the properties so that won't work. How is that going to work when new extends old?

            Jochem

            --

            Walter Seethaler

            unread,
            Mar 29, 2015, 10:33:35 AM3/29/15
            to lu...@googlegroups.com
            Very good point, completely forgot entityNew(). Have not used it much, so I am not sure about it. Feel free to disagree.


            And entityNew()? And especially entityNew(entity, props)?

            Just looked into some source codes and the ColdFusion ORM book (see Gotchas | Constructor Arguments) to understand the current implementation. If no init method is present, the properties are populated directly (some ORM standard constructor?). If the init method is declared, it is called (at least for entityLoad - so I guess the init method is called for entityNew too). The arguments of the init method need to be optional.

            In my opinion entityNew(entity, props) should exactly behave like new Entity(props). It it just another form to instantiate a CFC - with a different way to identify a CFC (classpath vs entity name). If the constructor is declared, it must be used, if it is not declared the compiler must add an ORM constructor, based on the property declarations. Railo 4 added the implicit constructors, which seems to be the same as an ORM constructor would be.

            This is off topic and I am not sure about it, but we could even try to get rid of entityNew(). In OO you wire classes together via classpath and import statement. In ORM we use the entity name (which may differ from the filename/type) to access ORM CFCs. While this is a bit handy, it adds complexity to the language and makes the source code less clear. But again, not sure here.
            Foo function newBoo(){
               
            return entityNew("Boo");
            }
            //Classname and entity name may differ.



            • Inheritance between CFCs with old and new constructor should work well.
              • Most important is, that CFCs with the new constructor can extend CFC with the old init constructor.
            IIRC entityNew(entity, props) currently bypasses the setters for properties. But you suggest setting up the setters before the properties so that won't work. How is that going to work when new extends old?

            Not sure if I understood your point. Do you mean if 16. Execution order changes with the new constructors, how that would match together with the old execution order in case of inheritance? Could not answer it anyway, I am not even sure how the exact execution order is right now. Whatever the exact execution order is, it must be well documented.

            Jochem van Dieten

            unread,
            Mar 29, 2015, 2:14:56 PM3/29/15
            to lu...@googlegroups.com
            On Sun, Mar 29, 2015 at 4:33 PM, Walter Seethaler wrote:
            This is off topic and I am not sure about it, but we could even try to get rid of entityNew().

            IIRC Adobe's rationale for entityNew() was to provide a shorthand for the full package name used in other ways to instantiate a component. Apart from the obvious backward compatibility concerns, I would much rather get rid of it in favor of explicit import statements. 
             

            • Inheritance between CFCs with old and new constructor should work well.
              • Most important is, that CFCs with the new constructor can extend CFC with the old init constructor.
            IIRC entityNew(entity, props) currently bypasses the setters for properties. But you suggest setting up the setters before the properties so that won't work. How is that going to work when new extends old?
            Not sure if I understood your point. Do you mean if 16. Execution order changes with the new constructors, how that would match together with the old execution order in case of inheritance?

            Yes.

             
            Could not answer it anyway, I am not even sure how the exact execution order is right now.

            Different from what you propose. Hence my question how this should work when old and new are both used in an inheritance hierarchy.

            Jochem

            ML

            unread,
            Apr 1, 2015, 9:09:24 AM4/1/15
            to lu...@googlegroups.com
            Hi Micha,

            If createObject is deprecated, is there an alternative to instantiating a dynamic object, ie.

            pathToComponent = "component.utility.tools";
            obj
            = createObject( "component", pathToComponent ).init();

            This below throws an error:
            obj = new #pathToComponent#();



            Regards,
            ML
            To unsubscribe from this group and stop receiving emails from it, send an email to lucee+unsubscribe@googlegroups.com.

            Walter Seethaler

            unread,
            Apr 1, 2015, 9:21:03 AM4/1/15
            to lu...@googlegroups.com
            IIRC you can use:

            new "#pathToComponent#"();

            ML

            unread,
            Apr 1, 2015, 9:28:25 AM4/1/15
            to lu...@googlegroups.com
            Oh, yes.  That works.  Thanks!

            Sean Corfield

            unread,
            Apr 1, 2015, 12:37:57 PM4/1/15
            to lu...@googlegroups.com
            Yes, both FW/1 and TestBox do this — so it’s portable across Railo/ACF as well.

            Sean

            Michael Offner

            unread,
            Apr 2, 2015, 2:52:31 AM4/2/15
            to lucee
            this is working new "#pathToComponent#"(); but i don't like it at all, Lucee 5 indroduces a new function "createComponent" that also invokes the "init" function the same way as the new operator does:
            createComponent(pathToComponent,[1,2,3]); 
            createComponent(pathToComponent,{lastname:"Sorglos"}); 

            Micha

            To unsubscribe from this group and stop receiving emails from it, send an email to lucee+un...@googlegroups.com.

            To post to this group, send email to lu...@googlegroups.com.

            Siegfried Wagner

            unread,
            Apr 2, 2015, 11:44:33 AM4/2/15
            to lu...@googlegroups.com
            Hi,

            just want to throw in an other thought:
            does init() have to be the constructor method?

            As it has been just a regular function in the past and also been used that way, i.e. with arguments and possible different return values (than the object itself), it would be better for backwards compatibility if this won't be changed.
            I would suggest using a constructor function similar to other programming languages where the constructor is automatically determined by having the same name as the "class", ie. the component name.

            Dominic Watson

            unread,
            Apr 2, 2015, 11:51:14 AM4/2/15
            to lu...@googlegroups.com
            I'd say for backward compatibility the reverse is true. Using init() as the constructor has been the convention from before I started coding CFML (not a great deal of time, but around 12 years ago).

            I agree that it isn't necessarily perfect, but anyone programming CFML with their finger anywhere near the pulse will have been using init() in this way and this has then been solidified some years ago with the introduction of the 'new' operator.

            I think changing it now would bring plenty of tears and not a great deal of benefit. It may differ from Java and other languages, but the 'init' convention is pretty clear and easily documented.



            --
            You received this message because you are subscribed to the Google Groups "Lucee" group.
            To unsubscribe from this group and stop receiving emails from it, send an email to lucee+un...@googlegroups.com.
            To post to this group, send email to lu...@googlegroups.com.

            For more options, visit https://groups.google.com/d/optout.



            --
            Pixl8 Interactive, 3 Tun Yard, Peardon Street, London
            SW8 3HT, United Kingdom

            T: +44 [0] 845 260 0726 W: www.pixl8.co.uk E: in...@pixl8.co.uk

            Follow us on: Facebook Twitter LinkedIn
            CONFIDENTIAL AND PRIVILEGED - This e-mail and any attachment is intended solely for the addressee, is strictly confidential and may also be subject to legal, professional or other privilege or may be protected by work product immunity or other legal rules. If you are not the addressee please do not read, print, re-transmit, store or act in reliance on it or any attachments. Instead, please email it back to the sender and then immediately permanently delete it. Pixl8 Interactive Ltd Registered in England. Registered number: 04336501. Registered office: 8 Spur Road, Cosham, Portsmouth, Hampshire, PO6 3EB

            Andrew Penhorwood

            unread,
            Apr 2, 2015, 7:18:48 PM4/2/15
            to lu...@googlegroups.com
            I have 15 years of CF under my belt and I would disagree with you on init() being called is cut and dried.  I have never used the new operator.  While I don't have a problem with the init() method being called automatically it will require me to review a number of pages.   Personally I always use createObject() when using a CFC.

            Andrew Penhorwood
            Reply all
            Reply to author
            Forward
            0 new messages