Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

btilly (5037)

btilly
  (email not shown publicly)

Journal of btilly (5037)

Monday June 23, 2008
03:26 PM

I hate context

[ #36756 ]

I've hated Perl's notion of context for a long time. So this weekend was just confirmation.

The problem is simple. Perl's notion of context requires that we think about what we want to do in array and scalar contexts, and potentially do different interesting things. This automatically doubles all APIs. Now it is true that sometimes there is something useful you can hang on this hook. But in my experience it is more often true that nothing really is obvious. And in that case with depressing frequency you get design decisions that age poorly.

For example at one point I read an article suggesting that it was a good idea to return a reference to an array in scalar context. I was briefly convinced and did this in Text::xSV. And now I curse myself every time I write:

    my $name = $csv->extract("name");

and it does what I don't want.

Now it is easy to say that this is a poorly thought through design decision. And it was. But I've noticed that attempts to be clever with context frequently lead to bad design decisions. And result in making APIs more complex than they need to be. Sure, context is occasionally handy. But when I compare Perl to Ruby or JavaScript, I find that on balance context doesn't seem worthwhile to me.

This is old hat. What prompted this is something specific. On the Rose::DB::Object list we had a discussion about a bug between Rose::DB::Object and Template Toolkit. Here is the bug in simplest possible form:

#!perl -w

package MainObject;
sub new { bless {}, shift }
sub subobjects {
        my @data = SubObject->new("world");
        # no problem when there are more than 1 subobjects
        # push @data, SubObject->new("Hello");
        return wantarray ? @data : \@data;;
}

package SubObject;
sub new { bless {somevalue => $_[-1]}, shift}
sub somevalue {
        return shift->{somevalue};
}

package main;
use strict;
use Template;

my $template = Template->new();
$template->process(\*DATA, {mainobject => MainObject->new()}) or die $template->error;
__END__

[% FOREACH subobject IN mainobject.subobjects.reverse %]
        still not printed: [% subobject.somevalue %]
[% END %]

This prints some blank lines. If there are multiple subobjects it does what it is supposed to.

Well, OK. Obviously a weird context problem of some sort. Template::Stash::Context is supposed to help resolve those. So we try it:

#!perl -w

package MainObject;
sub new { bless {}, shift }
sub subobjects {
        my @data = SubObject->new("hello");
        # no problem when there are more than 1 subobjects
        return wantarray ? @data : \@data;;
}

package SubObject;
sub new { bless {somevalue => $_[-1]}, shift}
sub somevalue {
        return shift->{somevalue};
}

package main;
use strict;
use Template;
use Template::Stash::Context;

my $stash = Template::Stash::Context->new();
my $template = Template->new({STASH=>$stash});
$template->process(\*DATA, {mainobject => MainObject->new()}) or die $template->error;

__END__

[% FOREACH subobject IN mainobject.subobjects.reverse %]
        still not printed: [% subobject.somevalue %]
[% END %]

This time it dies a horrible flaming death because it can't locate object method "reverse" via package "SubObject". WTF?

After some hacking around I came up with the following patch that makes Template::Stash::Context work properly:

--- /Library/Perl/5.8.8/darwin-thread-multi-2level/Template/Stash/Context.pm 2007-04-27
10:56:05.000000000 -0700
+++ Context.pm 2008-06-23 00:32:17.000000000 -0700
@@ -508,9 +508,8 @@
                                                                                                        $returnRef,
$scalarContext);
                                return $retVal if ( $ret ); ## RETURN
                      }
- elsif (UNIVERSAL::isa($root, 'ARRAY')
- && ($value = $LIST_OPS->{ $item })) {
- @result = &$value($root, @$args);
+ elsif ( defined($value = $LIST_OPS->{ $item })) {
+ @result = &$value([$root], @$args); ## @result
                      }
                      else {
                              @result = (undef, $@);

It turns out that Template::Stash::Context added the ability to do list operations only to native scalar types, and not to scalar objects. This fixes that.

Of course there is the real underlying problem, which is why returning 1 thing is so different from returning many things. Digging further the cause of that problem is that TT does not internally maintain a notion of context. Because of that, it winds up with the same internal data structure for the return out of scalar context and the return of one thing in list context. Then when it has to thaw it out, it has no choice but to treat them as being the same thing. I found at least one place that assumption was encoded, but there are more, and it doesn't look easy to fix.

Now it is easy to say "poor design decision within TT". It is. However there is an old design principle. Which is that when people make repeated mistakes involving one part of the interface, at some point you have to ask whether the real mistake is in the design of the interface itself. (This is not, incidentally, a software principle. Read The Design of Everyday Things for more on it. I also saw it repeatedly emphasized in a book about industrial disasters.)

It seems to me that there are repeated mistakes made by good people involving the notion of context. Which I take as evidence that the problem is with the idea of context itself. And then I conduct a sanity check. There are a lot of languages out there that deliberately borrowed a lot of ideas from Perl. How many have chosen to borrow the idea of context? Why not? :-)

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • Seriously, I think the greater problem about identifiers rather than context. To stay with your example of "extract", that's a verb. When a method has a verb as a name, I expect it to do whatever the verb says. As the return value, I wouldn't (ever) expect a list, but instead some true value if the action has succeeded, and a false value if it hasn't.

    A method that returns something, should have a noun as its name, that describes what it returns. Even if it's something as simple and perhaps ambiguous as "fi

    • You're right that good design starts with good naming. Unfortunately the idea of systemically basing your method and class names on grammatical notions is one that I was not exposed to until I read Perl Best Practices. Which did not immediately sink in because it was formulated in a very abstract manner and it took a while for the advice to sink in.

      Needless to say I wrote that module before this point.

      Still when you're facing a design mistake, it is worth thinking about all of the potential contributing f