tags:

views:

455

answers:

7

When writing an API or reusable object, is there any technical reason why all method calls that return 'void' shouldn't just return 'this' (*this in C++)?

For example, using the string class, we can do this kind of thing:

string input= ...;
string.Join(input.TrimStart().TrimEnd().Split("|"), "-");

but we can't do this:

string.Join(input.TrimStart().TrimEnd().Split("|").Reverse(), "-");

..because Array.Reverse() returns void.

There are many other examples where an API has lots of void-returning operations, so code ends up looking like:

api.Method1();
api.Method2();
api.Method3();

..but it would be perfectly possible to write:

api.Method1().Method2().Method3()

..if the API designer had allowed this.

Is there a technical reason for following this route? Or is it just a style thing, to indicate mutability/new object?

(x-ref http://stackoverflow.com/questions/1240876/stylistic-question-concerning-returning-void)


EPILOGUE

I've accepted Luvieere's answer as I think this best represents the intention/design, but it seems there are popular API examples out there that deviate from this :

In C++ cout << setprecision(..) << number << setwidth(..) << othernumber; seems to alter the cout object in order to modify the next datum inserted.

In .NET, Stack.Pop() and Queue.Dequeue() both return an item but change the collection too.

Props to ChrisW and others for getting detailed on the actual performance costs.

+12  A: 

If you had Reverse() return a string, then it wouldn't be obvious to a user of the API whether it returned a new string or the same-one, reversed in-place.

string my_string = "hello";
string your_string = my_string.reverse(); // is my_string reversed or not?

That is why, for instance, in Python, list.sort() returns None; it distinguishes the in-place sort from sorted(my_list).

Mark Rushakoff
+17  A: 

Methods that return void state more clearly that they have side effects. The ones that return the modified result are supposed to have no side effects, including modifying the original input. Making a method return void implies that it changes its input or some other internal state of the API.

luvieere
In C++, there are other ways to express this - const member functions, for example..?
JBRWilkinson
@JBRWilkinson The question's scope is broader than C++, it also has a C# tag. What I've said was meant as a language-agnostic API practice.
luvieere
Okay - point taken.
JBRWilkinson
Stack.Pop() (http://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx) is an example of an API that returns a value yet also changes the object. Is this bad design (should've been : use Peek() to read, use Pop() to remove) or a legitimate exception to the rule?
JBRWilkinson
@JBRWilkinson The semantic definition of a stack, given in VDM, describes the following operations: init: -> Stack; push: N x Stack -> Stack; top: Stack -> (N U ERROR); remove: Stack -> Stack; isempty: Stack -> Boolean none of which removes AND returns something. This tells me that the original intent was to clearly distinguish between side-effect inducing calls and pure calls. The Pop() method is therefore a syntactic convenience, rather than a formal practice.
luvieere
+2  A: 

Is there a technical reason for following this route?

One of the C++ design guidelines is "don't pay for features you don't use"; returning this would have some (slight) performance penalty, for a feature which many people (I, for one) wouldn't be inclined to make use of.

ChrisW
What's the performance penalty in C++? Isn't the this pointer on the stack anyhow?
JBRWilkinson
JBRWilkinson Wherever the this pointer is, it ought to be the this pointer of the calling process by the time it returns to the calling process. Actually, using MSVC, the `this` pointer is in the `ecx` register, and return code is in the `eax` register: so the performance penalty would be an extra `mov ecx,eax` instruction at the end of the/every called subroutine.
ChrisW
@JBRWilkinson The extra opcode could be optimized away when it's not used, only when the called routine knows where it's being called from, i.e. when it's inlined (but inlining everything would have performance problems of its own).
ChrisW
@ChrisW - thanks for the detail. This is reason enough in a high-performance API.
JBRWilkinson
A: 

Besides the design reasons, there is also a slight performance cost (both in speed and space) for returning this.

C. Dragon 76
Can you give some specifics?
JBRWilkinson
ChrisW provided some great comments in his answer. Every language/platform should be pretty similar in this respect. For example, in C#/.NET, the compiler will have to add an IL return instruction to the asssembly and the JIT compiler should in most cases convert that to a `mov eax,ecx` instruction just like the C++ compiler in his example.
C. Dragon 76
+3  A: 

The technical principal that many others have mentioned (that void emphasizes the fact the function has a side-effect) is known as Command-Query Separation.

While there are pros and cons to this principle, e.g., (subjectively) clearer intent vs. more concise API, the most important part is to be consistent.

Hank Gay
The Wiki article seems a bit muddy. It is entirely normal for commands to return an indication of whether something unexpected happened. Something like the incrementAndReturnValue function behaves as a command, but returns a value for the purpose of allowing the caller to know what happened. One could pass an "expected" value and have the function return a boolean saying whether things were "as expected", but it's easier for the caller to do the comparison itself.
supercat
+2  A: 

I'd imagine one reason might be simplicity. Quite simply, an API should generally be as minimal as possible. It should be clear with every aspect of it, what it is for.

If I see a function that returns void, I know that the return type is not important. Whatever the function does, it doesn't return anything for me to work with.

If a function returns something non-void, I have to stop and wonder why. What is this object that might be returned? Why is it returned? Can I assume that this is always returned, or will it sometimes be null? Or an entirely different object? And so on.

In a third-party API, I'd prefer if that kind of questions just never arise.

If the function doesn't need to return anything, it shouldn't return anything.

jalf
+1. It'd be easy enough to write a method chaining wrapper over it if a convenient syntax is desired. I'd prefer that to pessimizing all kinds of functions by returning this/*this when not necessary in favor of a debatable stylistic shorthand.
+2  A: 

If you intend your API to be called from F#, please do return void unless you're convinced that this particular method call is going to be chained with another nearly every time it's used.

If you don't care about making your API easy to use from F#, you can stop reading here.

F# is more strict than C# in certain areas - it wants you to be explicit about whether you're calling a method in order to get a value, or purely for its side-effects. As a result, calling a method for its side-effects when that method also returns a value becomes awkward, because the returned value has to be explicitly ignored in order to avoid compiler errors. This makes "fluent interfaces" somewhat awkward to use from F#, which has its own superior syntax for chaining a series of calls together.

For example, suppose we have a logging system with a Log method that returns this to allow for some sort of method chaining:

let add x y =
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) // #1
    x + y                                                 // #2

In F#, because line #1 is a method call that returns a value, and we're not doing anything with that value, the add function is considered to take two values and return that Logger instance. However, line #2 not only also returns a value, it appears after what F# considers to be the "return" statement for the function, effectively making two "return" statements. This will cause a compiler error, and we need to explicitly ignore the return value of the Log method to avoid this error, so that our add method has only a single return statement.

let add x y =
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) |> ignore
    x + y

As you might guess, making lots of "Fluent API" calls that are mainly about side-effects becomes a somewhat frustrating exercise in sprinkling lots of ignore statements all over the place.

You can, of course, have the best of both worlds and please both C# and F# developers by providing both a fluent API and an F# module for working with your code. But if you're not going to do that, and you intend your API for public consumption, please think twice before returning this from every single method.

Joel Mueller
Interesting to see how other languages actually kind of depend on this pattern. Thanks for the great answer!
JBRWilkinson
This reminds me of Yegge's "Rhinos and Tigers", which discusses (among other things) if and how VMs can support calling between multiple languages. "And they're like (waving hands) 'Ooooh, we'll gloss over it, gloss over it, smooth it over.' And the reply is: 'You can't. This is fundamental. These languages work differently!'"
Ken