Elliott's Blog | Life Through Math, Algorithms and Code

Jul/09

16

ColdFusion arguments.callee

Last night Ben Nadel sent me an email asking if there was any way to get the currently executing function so you could get the metadata from it.

<cffunction name="test" myAttribute="1">
  <--- How can we get the myAttribute value? --->
</cffunction>

The first obvious attempt at this is to use getMetaData(test).myAttribute, and that works fine until you pass the function as a pointer and then it’s not called test anymore. Instead we need a different way to get the current function.

I had looked into this for implementing closures some time ago, and even talked about this at BFusion 2008. My original workaround was to create an API around the function pointer. For example executeFunction(func) that passes the function pointer in as an argument. Unfotunately, this also means you can’t just pass this function around transparently to anything that expects a function pointer.

Last night, however, I had a eureka moment and figured this one out. To get a reference to the current function we’re going to harness exceptions and the information we can get from the stack.

<cffunction name="getStackFunction" access="public" returntype="any" output="false">
   <cfargument name="name" type="string" required="true">
   <cfargument name="depth" type="numeric" required="false" default="1">

   <cfset var TemplateClassLoader = createObject("java","coldfusion.runtime.TemplateClassLoader")>
   <cfset var servletContext = getPageContext().getServletContext()>
   <cfset var templatePath = "">
   <cfset var TemplateClass = "">
   <cfset var field = "">

   <cftry>
      <cfthrow type="Exception">

   <cfcatch type="any">
      <cfset templatePath = cfcatch.tagContext[depth+1].template>
   </cfcatch>
   </cftry>

   <cfset TemplateClass = TemplateClassLoader.findClass(servletContext,templatePath)>
   <cfset field = TemplateClass.getDeclaredField(arguments.name)>
   <cfset field.setAccessible(true)>

   <cfreturn field.get(TemplateClass.newInstance())>
</cffunction>

We can then use this code with the (case sensitive) name of the function in the current stack to get a pointer to that function.

<cffunction name="test" myAttribute="1" output="false">
   <cfargument name="x" type="numeric">
   <cfset arguments.callee = getStackFunction("test")>
   <cfreturn arguments.x + getMetaData(arguments.callee).myAttribute>
</cffunction>

You can use the depth parameter to get functions farther down the stack as well.

I’ll go over how to build proper closure constructs using this technique, and some other novel ones that don’t even require runtime magic in an upcoming post.

Oh, and hats off to Ben for sparking my interest in this issue again! :)

No tags

12 comments on “ColdFusion arguments.callee

  1. Steve Bryant on said:

    I have been thinking about this problem recently as well so it is nice to see a solution. Somehow, though, using exception handling just seems… wrong. How is the performance on that?

    I was thinking of implementing a method that uses the named arguments of the method that calls it (long story). Basically, I want to be able to programmatically get a list of the arguments for a method. I could pass in This and the method name, but I wanted something with a cleaner API.

    Anyway, this is really great to see and I am eager to see what else you do with this.

  2. Adam Cameron on said:

    Hi Elliott
    This seems rather similar (although more complicated) than a UDF I mentioned to you in “another forum” a few months back. Good to see a slightly different technique though.

    It’s probably also worth while pointing people to the issue you raised with Adobe on this one, now that the bugbase is public:
    http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=73010

    It’d be good to get more votes.


    Adam

  3. Elliott on said:

    @Adam

    I don’t remember you posting anything remotely like this. My suggested function for getting the called name also doesn’t address the use case for the function in this blog post.

    arguments.udfs.func1 = myFunction;
    arguments.udfs.func1(1);

    The called name here is func1, but knowing it doesn’t let us get a reference to ourself from inside that function since there’s no way to know the UDF is inside another struct without scanning every scope.

    I’m curious to know what function you wrote that you think solves this as there’s no other way to get a function pointer to yourself regardless of the calling context.

    Especially in a case like this which models passing a function to another function like map or fold.

    // template A.cfm
    function a() {}
    request.a = a;

    // template B.cfm
    b = request.a;
    request.a = 1;
    b();

    What “technique” do you think is more simple that solves this?

  4. Elliott on said:

    @Adam

    Okay I just found your function in the “other forum”. Your getCurrentFunctionName() does *not* do what this blog post is about, or what the entire thread is about that you posted that function on. It doesn’t address the ER either.

    Your post does *not* return the called function name. It returns the declared function name.

    I’d suggest you reread the ER and this blog post again. You seem to be confused. :)

  5. Adam Cameron on said:

    Yep, as per other discussion, you’re dead right. Sorry for the confusion (albeit it’s mostly on my part, it seems ;-)

    Cheers.


    Adam

  6. Elliott on said:

    Hah, it’s all good. Btw, do you have a blog? You’re kind of a ghost around these parts.

  7. Ben Nadel on said:

    Elliott,

    Very interesting stuff! I failed after 2 hours and I’m satisfied in saying that I would NEVER have come up with this at all :D Nice work!

  8. Adam Cameron on said:

    Nope: no blog, me. Well I do, but not a technical one. I keep thinking about it, but I so seldom have anything worthwhile saying, I don’t see the point.

    Most of the things I think of to say stem from what I read on other people’s blogs, so I just stick my oar in as-and-when. Not that today’s effort was a shining example: I’m usually better than that!


    Adam

  9. aMeen on said:

    Got Lost … I gave up
    :(

  10. Ben Nadel on said:

    I can’t seem to get this to work if the function pointer is in another scope such as:

    At this point, it looks like “test” is not a declared field in the template taken from the exception tag context.

    Thoughts??

  11. Elliott on said:

    @Ben

    Seems my blog ate your code sample. Doh! I need to dump wordpress or upgrade and get code comments and blog post code working. It was a real pain to make this post too.

    Can you post your code on http://gist.github.com/ perhaps?

    I don’t know what you mean by a different scope. I have a bunch of test cases here and they all work.

  12. This got me part the way:
    thisCFCPage = GetPageContext().getFusionContext().getPagePath();

    Here’s what’s sad: There is this gem:

    GetPageContext().getFusionContext().methodCalledName

    but it’s a private property and there is no getter function. ARRRGGGHHHHH!!!!!!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<<

>>

Theme Design by devolux.nh2.me