• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers

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

Explorer ,
Dec 09, 2012 Dec 09, 2012

Copy link to clipboard

Copied

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 = CreateObject ("component", "target").Init ();

variables.mcu.targets.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

TOPICS
Advanced techniques

Views

2.3K

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

LEGEND , Dec 13, 2012 Dec 13, 2012

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

...

Likes

Translate

Translate
LEGEND ,
Dec 09, 2012 Dec 09, 2012

Copy link to clipboard

Copied

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.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Dec 09, 2012 Dec 09, 2012

Copy link to clipboard

Copied

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

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Dec 11, 2012 Dec 11, 2012

Copy link to clipboard

Copied

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;.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

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 = (arguments.timings / 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 = CreateObject ("component", "target").Init (id = i,

                                                                                     colors = variables.mcu.colors,

                                                                                     timings = variables.mcu.timings);

               variables.mcu.targets.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

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

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.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

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/WSe61e35da8d31851842acbba1353e848b35-8000.htm...).

--

Adam

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

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

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

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

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

Adam,

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

variables.mcu.targets.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

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

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.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Dec 13, 2012 Dec 13, 2012

Copy link to clipboard

Copied

LATEST

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

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Resources
Documentation