Method Chaining (Regarding A Recent Perlmonks Discussion)

Revelation on 2003-06-16T05:10:38

I'd like to discuss why method chaining is bad for widely distributed modules in response to this discussion. I've decided to put it in its own node, because I believe that the topic of why NOT to use it could be very important. I hope that if I share my rationale of the use of method chaining and how it hurts large packages, people will reconsider their belief in that technique. To begin with, it'd be nice to define method chaining- my current definition is chaining a series of methods that work off of a single object. It's used most often for attributes to an object, but I guess it could be used to actually call methods. It's major advantage is that it allows for great brevity, and demonstrates the interconnectedness of the attributes, as well as puts them in a single place.

All of these are good things, and as demerphq said, it seems to make sense to write:

print Data::Dumper->new([$foo],[qw(foo)])->Indent(2)->Purity(2)->Dump();

instead of:

print do{my $d=Data::Dumper->new([$foo],[qw(foo)]); $d->Indent(2); $d->Purity(2); $d->Dump()};

however, this is a rather short sighted consideration, when one looks at the cons of method chaining. If our criteria is as said "easy things should be easy, and hard things possible," we must adopt some sort of standard to make maintanance programming (one of those easy things) and extending packages (another one of those easy things) easy. Method chaining betrays the later, and even makes the former rather hard.

Consider even the previous example. A perl programmer, who is familiar with Data::Dumper, would automatically assume that you're modifying multiple attributes of the dumper object. However, a maintanance programmer, or basically anybody not familiar with it, would infer that the programmer is changing the purity of the indent, not the greater object. This is because programming standards (think of them as extended but unenforced grammer, the degree of scrictness after use strict;,) would dictate that as we go further from the left, we get more and more specific, that we are modifying the attribute.

But who cares about standards? One shouldn't have to contort oneself to fit into some arbitrary coding scheme when there is no reason to. We're basically selecting a design that isn't common. The problem with that is twofold, first that the majority of people would not consult a reference for such a small item of concern and they would further probably assume that the common standard is being used, and second that the very standard used is used for a reason...

It's the sad truth that method chaining leads to problems for people who don't read the documentation, but that it also leads to inflexibility and problems for maintanance programmers. The inflexibility is that of being forced to have seperate accessors and mutators, because accessors have to return the data accessed, wheras mutators are now returning the object modified. This can be delt with by just checking if $_[1] is defined and sending $self if it isn't, so I don't see much of a problem with it. However, method chaining at that moment makes even less intuitive sense, because I'm using the same method with completely differant results, one of which is the parent object, and the other of which is an internal part of that object.

But I can cope with that. No big deal. The second problem, however, is something I can't deal with. Consider, from perldoc:

$object->fullname('thadeus');

# But my system's gotten more complicated.
# so now I'm going to make fullname into an
# object of its own.

$object->fullname('Thadeus Barton')->christian('Thadusky');
$object->fullname('Frankeus Muss')->hobbessian('fransko');


You've created the maintanance/extensibility nightmare of all nightmares! Now it's necessary to document what is a chained method and what isn't. You've also made it possible to do: $object->size('large')->fullname('Frankeus Muss')->hobbessian('fransko');

It's a sad day for whoever has to work with that code. So we have to make a decision between the two? If there's a good chance we won't need to extend our work, why do so? Although being able to transparently extend objects (change how it's implemented) without changes usage is one of the major reasons people encapsulate data, it can be sacraficed for DWIM. To me, getting rid of that layer for a few silly strokes is futile. Especially when there's a perfectly valid alternative- all of this, because you were to lazy as a programmer to just have a single set method, which would be the only thing needing documentation:

print my $d=Data::Dumper->new([$foo],[qw(foo)])->set(Indent => 2, Purity => 2)->dump(); # If brevity is all too necessary.

my $d=Data::Dumper->new([$foo],[qw(foo)]); $d->set(Indent => 2, Purity => 2); print $d->dump();


At least this way you're not tredding on OO dogma.
Gyan Kapur
gkapur@myrealbox.com


On method chaining

merlyn on 2003-06-16T13:17:47

Method chaining is fine as long as it's clearly documented and consistent within a module. I would be hauled off to the looney bin if I were to make any kind of global recommendation, however.

In my new Alpaca book, I argue that for a getter/setter method, there are arguments in favor of any one of four return values from the setter method:

  • a simple true/false status (yes I was able to set this),
  • the newly set value, turning the setter into an immediate getter as well,
  • the previous value (ala umask and single-arg select), allowing flip-flop settings trivially, or
  • the object itself, allowing for chained settings.
For anything but the first, a die has to be used to signal error.

As long as it's documented, it's fine. Don't demand consistency. Any one of these four returns is fine.

For crying out loud

djberg96 on 2003-06-16T13:41:37

See Want.pm. Anyone who wants to support method chaining as a regular feature of their module ought to be using this. No more of this "do I return $self or a $value" nonsense.

Re:For crying out loud

Revelation on 2003-06-16T20:37:58

That doesn't really help too much. Want.pm can't distinguish between the different objects that could be wanted, getting a program to think metacognitively at that complexity is impossible. (If you've invented AI *that* good, Terminator 3 will soon become a reality...)

I could be wanting the object returned by $object->head, because you've made the head into its own greater object to allow for more methods, or I could want the object to be a chained method. How can you tell the difference? How can a maintanance programmer tell the difference? The problem with method chaining is that an idiom manifests itself in the same form as the standard, and that they stand for two completely different things. The contexts, for all intents and purposes, look _exactly_ the same. For a reader to read the documentation and check each method to see whether its chained, or can actually be returning a real object outweighs the gains created by a little extra brevity, and having all your manipulations of an object done at one time.

Smalltalk

malte on 2003-06-16T13:52:21

Smalltalk returns self by default if there is no explicit return value. I think that's part of the reason why well written Smalltalk programs look just beautiful.

The real problem ...

autarch on 2003-06-24T05:11:19

The real problem is that you're expecting your set methods to return the value that you just set (at least in your fullname) example. So you now you have a combined get/set method, which is icky.

If your set methods are are clearly distinct from your getters, then returning $self from setters makes perfect sense in _all_ cases.