Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Comment on

( #3333=superdoc: print w/ replies, xml ) Need Help??
I was looking at the Marpa module and one thing about the API made me think "this needs to be improved". However, I'd like to reflect on the general class of problem involved, and discuss the best way it might be handled. Or, expose any limitations or tradeoffs in such methods. This might serve as a model for those who run into the same problem later, and hopefully will inspire the author (Jeffrey_Kegler) who is still working on the final form of the module's API.

Says Jeffrey:

I don't like inheritance, but we can get into that in the exchange. I'm likely to learn something, and others may profit as well.
The problem, in its general form, is that of supplying possibly large amounts of "callback" information, and furthermore the exact callbacks needed are not pre-determined. So I call it "Free-Form Delegation".

If you had one function that could be supplied by the caller (or object's configurator), you would just use a function argument. If you had several that were rarely but possibly overridable from the default behavior, being able to override known functions in the class might be the simplest for the author and easy to do when needed. If the behaviors were not rarely altered but a necessary part of the object's use, a delegated object might be easier to use than a derived class, but we know in Perl they are largely equivalent. Anyway, we can discuss those and sling it out.

In strongly typed languages, you can define an "interface" that the supplied implementation will use to supply all the functions that will be called.

But what if the names of the functions to be called are not known, in general, to the object? In Perl this is not a game-changer in terms of how we supply that functionality, but it might still be a conceptual shift in just what it is we are doing. In Marpa, these are the "actions" called by the parser. Which actions even exist are also part of the configuration, and are not pre-determined by the object we are supplying it to. That's why I call it "free form": we don't know the functions or their relationship to each other in advance.

Now Marpa supports two related ways to do it, which I think follow the road from the simplest toward an OO solution. The simplest uses Perl primitives that seem to be good for this task: The name of a package is given to Marpa. Marpa looks for all the named actions in that package. To deal with interrelationships, a stash is passed along with the specified callback data, in the form of a simple hash reference.

The second form refines this idea a little: Instead of a plain hash to use as a stash, it will call new in that package and use the resulting object as the extra argument. Hmm, now the callbacks look like the way Perl does Objects, passing $self as the first parameter. Except, he's still forming the callback by explicitly looking for a named function inside that package, not using Perl's dispatch mechanism. That's what I think is "wrong" or at least missing a feature.

Now the action named might not be present. So we don't want to just code $object->$action(@args), as it will try other things if the named action is not a function. So, if memory serves, the right way to code this is with can. In the early days, it was widely known that the return value was the method resolved, but that was not guaranteed! What is the proper, kosher, way of looking up what a dispatch would do without calling the function just yet? (Edit: chromatic's book shows using can to keep the return value as a code reference. ) The proper way would, of course, work with plain Perl blessed references as well as Moose and other fancy stuff, and overridden dispatchers and everything. There really should be an approved primitive, right?

Let me digress a moment and explain why I want inheritance to work. Suppose I have a base grammar that I parse with Marpa, but several options that turn different productions on or off. In fact, my translator object (built using Marpa internally) might provide its own extension mechanism that causes it to generate new actions and productions based on the configuration it reads. In more general terms, whatever solution is built using the object that takes free-form delegation might itself be derived from (problem-wise, not necessarily literal class derivation). To extend or alter the callbacks without messing up the original, inheritence does the trick!

And, given the model described above, why not use can instead of direct hard-coded package namespace lookups? Since it almost matches the use of Perl objects already, and the relationship of the callbacks will treat their "stash" like methods on the same instance, it would surprise people that inheritance wouldn't work.

If all the callbacks are expected to be there (or inherited from a supplied default implementation!), just calling $object->$action(@args) would do the trick. That's certainly easier than poking around in symbolically named packages at run time. But Marpa illustrates some further complications.

It might look for several different names: the can form handles that just fine, looking for $name1 and if it's not present trying $name2 etc. But in that case, the final function chosen is indeed a method in the class.

Another wrinkle is that the code called might be "somewhere else", not a method of the class. A straightforward thing would be to specify a code ref, rather than a name, for the Action. Now with classic Perl 5 objects, calling $object->$action(@args) still work just fine if $action is a code reference. It doesn't do any lookup but just calls it with $object as the first parameter. With fancier object systems, this still works, right? I don't see any reason why the code in that ref can't turn around and call methods on its first parameter.

If the ref passed in is not declared as an anonymous sub or other sub that simply expects such an argument, it might be more awkward. For example, passing in a ref to a method of a different class, where that class is created using some fancy object system. Well, don't do that. You can always pass a closure that calls the method on the desired class.

So, such a free-form delegation system can support names of methods (dispatched in the normal way) and code ref pseudomethods. Anything else can be handled, easily, using that. Now comes a deeper question: is this even the right approach? We followed a path starting with the most obvious use of a primitive Perl 5 feature (packages as collections of named functions) and followed basically the same road that Perl 5 objects did, to passing an object that will use whatever kind of dispatch the object is defined to use. But is that the wrong road? Is there another totally different way to pass in a bunch of free-formed delegated behavior? I think the requirements are the same as that used to make an object system, so the answer is no, and furthermore reinforces the high-level solution: pass an object and don't worry about how the object works internally.

So let me conclude with a straw-man recommendation for how such an API should look. Given is that other configuration information includes action names. Include a setting, say $d, which may be a string or a reference. If it is a non-code reference it is used as-is, $obj=$d, for later calls to $obj->$action or the more advanced can probes. If $d is a string, then $obj= $d->new.

For simple cases, where you don't need state between calls, or just want to use a simple scratchpad, you could allow new to not exist. In that case, it pretends that you wrote a simple new that just blesses an empty hash reference. That way the dispatch on the resulting $obj still works and you don't need different code to deal with plain packages. (in the Alpha version, Marpa has different variables for specifying plain packages or classes to be constructed) Finally, if it is code ref, it is called to return $obj. This deals with the case where you want to pass custom arguments to new (just write an anon sub around the call), use a different constructor function, clone another object, or whatever.

Extending the system a bit, I address my only concern: that it may call functions not meant as Actions but are there for internal use or leftover from other code that was harnessed. When the action names are static and listed in the configuration along with the class to call, it is not something worth worrying about. But if the call-ability is carried forward to other code that "discovers" the available callbacks, we want to be more explicit about what is to be called! Consider a plug-in for a scriptable system, for example. You list the available commands and it tells you all kinds of stuff that you can call if you so desire.

In this case, I'm thinking an attribute on the sub would do the trick. That would also let you classify different entry points for different purposes. For example, sub dir :Command { ... and sub mungeit :Filter { ... .

So, any thoughts on any of this?

Or at least help convince Jeffrey to use something like my strawman proposal and not the brutal direct lookups which don't inherit?

—John


In reply to Free-Form Delegated Behavior by John M. Dlugosz

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • Outside of code tags, you may need to use entities for some characters:
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others wandering the Monastery: (8)
    As of 2014-07-24 23:04 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      My favorite superfluous repetitious redundant duplicative phrase is:









      Results (167 votes), past polls