Ignoring the throwing of exceptions, these seem to be the common ways of normally exiting a function (one which returns a value, specifically; void functions aren't as interesting) and transferring control back to the caller:
- Explicit return statement which simultaneously a) binds result to invocation in caller and b) exits, transferring control back to caller. Found in CeeLanguage, CeePlusPlus, JavaLanguage, etc. FallingOffTheEnd may or may not be allowed.
- Last statement in function must contain the return value;
- a) function only exits after last statement is evaluated/executed. No "early exit" allowed.
- b) Last statement contains return value, but can 'early exit' through a return function. Found in CommonLisp, Ruby, Dylan, and Perl
- A particular variable name (such as the name of the function, or a pseudo-identifier like "retval") refers to the to-be-returned value. By assigning to "retval", the function can set its return value. Multiple assignments to retval are possible. ZERO assignments to retval are also possible; in which case either a default value is returned, an undefined value is returned, or the compiler disallows this.
- a) Early exit is not allowed; functions exits after last statement is evaluated.
- b) Early exit is allowed. Found in VisualBasic through VB 6.0. Also in AssemblyLanguage?
- There's no "statement", there are just expressions computing values. A function is just a compound expression. There is no notion of "return" or function exit. [The absence of SideEffects, of course, makes this easy. Those of us who work in languages with side effects do have to deal with sequencing the side effects; and in many cases the return value is computed before the last side effect is caused--ScottJohnson]. Sure, have a look at ObjectiveCaml, no return statement (there are no statements) but plenty of side effects at your disposal (though good style is to avoid them as much as possible). Actually in any language with exceptions you can fake return by throwing, or even better, you can use continuations (see CallWithCurrentContinuation).
: what about non-procedural languages like the "make" language? Do they fit somewhere in the above categories, or do they need a new category? Make's macro language is sorta functional, though it allows identifiers to be rebound; and makes it easy to write an endless loop; i.e. FOO=$(FOO). In that sense, it is like #4. The commands in a Make rule are entirely procedural; the only return value is an error code from the shell. If any shell command in a rule returns an error (unelss preceded by -), then the rule terminates and Make exits. Other than that, no return value to consider.--ScottJohnson
I prefer 1), though 2b) has advantages too. I like seeing explicitly where the function can exit and what the return value will be in each case. The return statement is a visual marker for this that works particularly well with syntax coloring. I also prefer it when statements take the form of verbs: 'return someValue;' satisfies this, but 'someValue;' looks awkward and alone. The exception is in pure FunctionalProgrammingLanguage
s, where you don't want statements to look like imperative sentences. For these, 2) is fine. -- JonathanTang
I strongly prefer 3) personally, and tend to write code that fakes that behavior when I don't have it (i.e. all the languages I normally use). In fact, I use that very name "retval" most of the time (or "result" when I feel like writing something that more closely resembles English). -- KarlKnechtel
I feel uneasy about considering what is best without also considering how non-fatal errors are dealt with.
I very strongly prefer 1) as the universal approach to returning values from a function. If the logic of a function concludes that a value has been ascertained then there is no need to do more processing; simply return at that point with the solution value. This can happen in multiple places within a function. The code follows my logic design. -- MartySchrader
I don't think the best ways are covered in the list above, probably because computer languages don't directly support the real need. I would suggest the following:
5) Return a status and a value. Having to return a null, 0, or -1 as a pseudo-value and remember to check for it is highly error prone.
6) Return multiple values. Many operations return more than one value. Why should I have to do programming hand stands to get all the values back?
You can do both of these already, surely? C++ has std::pair and boost::tuple. Python has tuples. You just make it easy to package multiple values together in a single item. For instance for (5), your function returns something of type std::pair<t_StatusValue,t_ReturnValue>. Presumably this can be done in other languages too.
Both of these arguments ignore the basic premise behind the processing of a function: to return a single result. If you need to process and/or return multiple values then you need to pass in and return something other than a simple type. In this case the function's return value can simply indicate success or failure, but the operatives need to be handled a different way.
The whole idea of a function
is, just like in mathematics, to take exactly one parameter and perform some logical translation on it. The result is exactly one product. If you have an operation that affects more than one result then it isn't a function, is it? It's a procedure
, and your code design should reflect that.
While it's true that functions in mathematics map exactly one value in the range to exactly one value in the domain, those values can come from the cross product of any well-ordered family of sets. This effectively allows for these functions to take and return multiple values. It should be noted that options 5 and 6 do exactly that.
"Hulk break out! HULK SMASH!!