11 Replies Latest reply on Dec 13, 2012 3:51 PM by tinu8805

    Callback function uses caller's variable scope instead of declared scope

    tinu8805 Level 1

      hi there

       

       

       

      Still on CF 9.0.1.x

       

      I have in the same directory 2 CFCs, each implementing an object that communicates with each other. Each CFC has the variables scope used. They are part of an emulator for a physical device. There are more cfc involved but I reduced it to the minimum test case.

       

      The parent is MCU which instantiates any number of TARGETS. A target communicates back to MCU with a callback function provided by the MCU. The callback function uses MCU's clock to log the incoming message.

       

      ---

      component MCU {

        variables.mcu.clock = 0;

      more of variables.mcu.*

       

        Init () {

      // Create all targets, call each target’s registerMCUListener to let them know whom to call back.

      variables.mcu.targets[i] = CreateObject ("component", "target").Init ();

      variables.mcu.targets[i].registerMCUListener (cbFromTarget);

        }

       

       

       

      private numeric function cbFromTarget (struct msg) {

       

      writedump (var=#variables#);  // <<<< This shows the variables scope of TARGET, expected MCU

       

      variables.mcu.events.list[variables.mcu.events.ptr] = {

      clock = variables.mcu.clock,  // CF chokes on this one because MCU is unknown

      msg = arguments.msg

      };

      return 0;

      }

      }

      ---

       

       

      ---

      component TARGET {

        variables.tg.clock = 0;

      more of variables.tg.*

       

       

      public void function registerMCUListener (any mculistener) {

      /*

      * register the mcu listener function to notify of any events

      */

      variables.tg.mcu.listener = arguments.mculistener;

      }

       

      private void function ToMCU (struct msg) {

      /*

      * use the registered mcu listener function to notify of any events

      */

      if (isCustomFunction (variables.tg.mcu.listener)) {

      variables.tg.mcu.listener (msg);

      }

      }

      }

      ---

       

       

      Idea: The cbFunc registers the message in the MCU’s variables scope

       

      But it does not. The marked variables.mcu.clock is throwing an error because mcu.clock does not exist in the variables scope.

       

      Observation: CF is using the variables scope of TARGET instead of MCU.

       

      I confirmed that by dumping the variables scope just before that line.

       

       

       

      Now: Is this a bug? Looks as if this is an compiler issue of binding *too late* …

       

       

      Because it's urgent (as usual) .. any workarounds? Be aware that even those two simple CFCs have to communicate in both directions MCU <-> TARGET .. and of course there are more CFCs involved ...

       

       

      PS: In CF 10 I could use the keyword function as a data type declaration. I did that but to no avail. Same error.

       

      Thanks

        • 1. Re: Callback function uses caller's variable scope instead of declared scope
          Dan Bracuk Level 5

          I don't think it's a bug.  You passed the cbFromTarget function to a function in the target cfc.  That means it is operating in the target cfc and reading the target cfc's variables scope.

           

          I notice that the cbFromTarget function can take a structure as an argument but I don't see you passing it one.  Maybe that's the path to the workaround you need.

          • 2. Re: Callback function uses caller's variable scope instead of declared scope
            tinu8805 Level 1

            Yes, you don't see how I use the regsitered function in TARGET .. it goes like this:

             

             

            ---

            toMCU ( { msgnum = msgTGHitCorrect,

                                     target = variables.tg.id,

                                     hand   = variables.tg.events.stack[evtptr].hand } );

            ---

             

            My undestandig of the problem is: Since CF does many things quite dynamically, it evaluates the expression variables.xxx at runtime and not at compile time. If I suppose at runtime, it's clear that varaible.xx is the noe of TARGET .. but I was not aware that it is REALLY at runtime.

             

            I considered compile time "dynamic" evaluation like this: CF tries to find unscoped variables first in this scoap, than in that scope finally in scop xyz .. but still at compile time. Hence the reason why I use fully scoped vairables.

             

            Martin

            • 3. Re: Callback function uses caller's variable scope instead of declared scope
              BKBK Adobe Community Professional & MVP

              I would define component instance variables explicitly. For example, like this

               

              component MCU {

              variables.mcu = structNew();

              ...

              etc., etc.

              }

               

              component TARGET{

              variables.tg= structNew();

              ...

              etc., etc.

              }

               

              I am assuming you have also initialized the targets array, using something like

               

              Init () {

              variables.mcu.targets = arrayNew(1);

                }

               

              It is unclear from your code whether or not cbFromTarget has an updated value of mcu. You might have to do this using this.init() or some other means.

               

              private numeric function cbFromTarget (struct msg) {

              this.init();

              ...

              etc., etc.

              }

               

              Finally, you get your function, cbFromTarget, to do 2 things at once. Namely to return 0(redundant) and to dump the variables struct. Improve the design by giving the function a returntype of struct, and changing the last line to return variables;.

              • 4. Re: Callback function uses caller's variable scope instead of declared scope
                tinu8805 Level 1

                MCUthe only thing I don't quite understand is your proposal in the callback to do a this.init();

                 

                I show you my TARGET.init()

                 

                ---

                public any function Init (numeric id, struct colors, struct timings) {

                      /*

                       * initialize the target with settings which are consistent for this bout.

                       *

                      */

                      variables.tg = {};

                      variables.tg.id = arguments.id;

                      variables.tg.colors = arguments.colors;

                      variables.tg.timings = arguments.timings;

                      variables.tg.clock = 0;

                      variables.tg.stacks = {

                                               tail = 1,

                                               head = 0,

                                               ptr  = 0,

                                               stack = []

                                            };              // stacks represent the expected hits to be dealt with .... can be any arbitrary number

                      variables.tg.events = {

                                               ptr = 0,

                                               stack = []

                                            };                // this array regsiters all hits to this target

                 

                      variables.tg.mcu.listener = {};         // this callback function is called on any need to send notification to the MCU

                 

                      return this;

                   }

                ---

                 

                 

                MCU.init()

                ---

                public any function Init (numeric cyclecount,

                                             numeric boutcount,

                                             numeric targetcount,

                                             struct  timings,

                                             string  targetsequence,

                                             struct  targetcolors

                                            ) {

                      /* Jab is an array of cycles */

                      /*

                      */

                 

                      try {

                         // initializue myself

                         variables.mcu = {};

                         variables.mcu.hitdefinition = arguments.targetsequence;

                         variables.mcu.hitcount      = ListLen (arguments.targetsequence);

                         variables.mcu.colors        = arguments.targetcolors;

                         variables.mcu.clock = 0;

                 

                         // normalize all timings to time grid

                         for (var i in arguments.timings) {

                            arguments.timings[i] = (arguments.timings[i] / this.msecsPerClock);

                         }

                         arguments.timings.active = arguments.timings.flash + arguments.timings.waiting;

                 

                         variables.mcu.timings = arguments.timings;

                 

                         // prepare the event list

                         variables.mcu.events = {

                                                   ptr = 0,

                                                   list = []

                                                 };

                 

                         // prepare the array of all target instances

                         variables.mcu.targetcount = arguments.targetcount;     // this property is redundant because it's ArrayLen () ... maybe we can drop it later

                         variables.mcu.targets = [];

                 

                         // Create all targets

                         for (var i = 1; i lte variables.mcu.targetcount; i++) {

                            try {

                               variables.mcu.targets[i] = CreateObject ("component", "target").Init (id = i,

                                                                                                     colors = variables.mcu.colors,

                                                                                                     timings = variables.mcu.timings);

                               variables.mcu.targets[i].registerMCUListener (cbFromTarget);

                 

                            } catch (any e) {

                               writedump (var=#e#);

                            }

                         }

                 

                         // now we populate the event queue with the starttime of all targets

                         setTargetStartEvents();

                 

                 

                      } catch (any e) {

                         dump();

                      }

                      return this;

                   }

                 

                ---

                 

                The MCU then is called from outsiede to emulate a clock tick. It will propagate each click down to all of its targets. everything works as expected unless that scope in the callback. I used callbacks for years, of course not so may times with CF.

                 

                To your proposal this.init(), maybe you can show a conceptual misunderstanging of mine ... however, it's clear to you what I want: The only thing the MCU must do is register the message of the target in its own event queue together with its own clock stamp.

                 

                Thank you for your help

                 

                Martin

                • 5. Re: Callback function uses caller's variable scope instead of declared scope
                  Adam Cameron. Level 5

                  Idea: The cbFunc registers the message in the MCU’s variables scope

                   

                  But it does not. The marked variables.mcu.clock is throwing an error because mcu.clock does not exist in the variables scope.

                   

                  Observation: CF is using the variables scope of TARGET instead of MCU.

                   

                  I confirmed that by dumping the variables scope just before that line.

                    

                  Now: Is this a bug?

                   

                  No, it's not a bug I'm afraid: it's expected behaviour.

                   

                  If I have waded through your code correctly, you are wanting cbFunc to be implemented as a closure, ie: the external variables it references are bound at declaration-time: the function is declared in MCU, so you expect the references to the variables scope in cbFunc to refer to the variables scope of MCU. This is not how CF works.

                   

                  Variable-binding in CF is done at runtime, and at runtime cbFunc is being called from within TARGET, so the variables-scope references in cbFunc are bound to TARGET's variables scope.

                   

                  CF10 can use functions which are implemented as closures, but not via the syntax you're using here (I'll not repeat the docs: http://help.adobe.com/en_US/ColdFusion/10.0/Developing/WSe61e35da8d31851842acbba1353e848b3 5-8000.html).

                   

                  --

                  Adam

                  • 6. Re: Callback function uses caller's variable scope instead of declared scope
                    tinu8805 Level 1

                    hi adam

                     

                    Thanks, that's what i figured out too.

                     

                    just to be sure: using this. instead of variables. would not be a remedy i assume?

                     

                    So, I could create another cfc as a queue manager so both MCU and TARGET use it to put/poll event messages ... this should no longer create problems, do you agree?

                     

                    Thanks

                     

                    Martin

                    • 7. Re: Callback function uses caller's variable scope instead of declared scope
                      Adam Cameron. Level 5

                      Nah, it would not matter which variables scope you use: they're all bound at runtime, and givent he function is "within" the target CFC, it'll be that CFC's scope that is bound.

                       

                      I think - if I get where you're suggesting - the idea of using an common intermediary CFC instance should work, yes.

                       

                      The other thing you could possibly do is to pass a reference to MCU's variables scope along with the callback, and then pass that reference into the callback when you actually call it.  It depends on what yo're doing as to what's gonna be a better approach here.

                       

                      I'm in a slight rush to get out of the office, and I have to say I'm scanned your code a few times now (hence the delay in responding) and I don't quite get what you're doing... not your fault, I'm not poring over the code very thoroughly, so am kinda just assuming what you're doing.

                       

                      But based on my passing understanding, I'd probably use your queue suggestion. I think.

                       

                      --

                      Adam

                      • 8. Re: Callback function uses caller's variable scope instead of declared scope
                        BKBK Adobe Community Professional & MVP

                        tinu8805 wrote:

                         

                        To your proposal this.init(), maybe you can show a conceptual misunderstanging of mine ...

                        I was simply suggesting a possible cause of the following issues you reported:

                        This shows the variables scope of TARGET, expected MCU... CF chokes on this one because MCU is unknown

                        If the function cbFromTarget() does not know variables.MCU it can only mean that the component does not have MCU as an instance variable. One way to define this variable is to call this.init(). In the context of the code in your original post, the this-keyword stands for the MCU component. This might be equivalent to MCU.init(cyclecount, boutcount, targetcount, timings, targetsequence, targetcolors) in the context of the code you last posted.

                        • 9. Re: Callback function uses caller's variable scope instead of declared scope
                          tinu8805 Level 1

                          Adam,

                           

                          Using a closure worked well. It was easy to transfer to that.

                           

                           

                          variables.mcu.targets[i].registerMCUListener (cbFromTarget());

                           

                           

                          and its definition is now

                           

                          private function function cbFromTarget () {

                             return function (struct msg) {

                                                  newEvent ();

                                                  writedump (var=#variables#);

                                                  variables.mcu.events.list[variables.mcu.events.ptr] = {

                          clock = variables.mcu.clock,

                          msg = arguments.msg

                                                                                                        };

                                                         };

                          }

                           

                          The rest didn’t need any reprogramming.

                           

                          The only thing I observed: a closure does not seem to be a function, because isCustomFuntion (..) returns false. Maybe this should be changed, I dunno.

                           

                          FYI, Martin Baur

                          • 10. Re: Callback function uses caller's variable scope instead of declared scope
                            BKBK Adobe Community Professional & MVP

                            tinu8805 wrote:

                             

                            The only thing I observed: a closure does not seem to be a function, because isCustomFuntion (..) returns false. Maybe this should be changed, I dunno.

                            ColdFusion treats a closure more like a variable, rather than a user-defined function. That is why isCustomFunction(some_closure) returns false.

                            • 11. Re: Callback function uses caller's variable scope instead of declared scope
                              tinu8805 Level 1

                              I justed learned that there is a new CF10 function: isClosure()