I think the most direct answer to this question is: rarely.
Incorrect Usage
A great majority of the appearances of Return
that I see are either entirely superfluous or an indication of code in need of refactoring. For example I frequently see something like:
fn[x_] :=
Module[{vec},
vec = {1, 2, 3};
Do[vec[[i]] *= x, {i, 1, Length[vec]}];
Return[vec]
]
Return
is superfluous here as omitting it will produce the same result. And of course the rest this code would be written far more simply which is often true in such cases.
Correct Usage
Return
does have its place, as does its sister function Break
. These functions are the most direct way to exit an ongoing incremental evaluation, and optionally return a value. It is important to understand how they work but there are already questions on that subject:
Although mostly undocumented I often, even typically, use these functions with additional parameters:
This not only extends the application of Return
and Break
but it more clearly defines the behavior of the operation which may make it more robust through version changes.
One must understand the order of evaluation and operation of Mathematica functions to effectively use Return
and Break
. For example it is not possible to Break[]
out of a Map
operation because Map
first applies the function, then evaluates the expression, as explained here:
However Scan
does use an incremental evaluation which means that we can Return
or Break
from it:
Scan[
If[# > 2, Return[#]] &,
{1, 2, 3, 4, 5}
]
3
Critically, though not illustrated, the Function
is never applied to elements 4
and 5
.
The question title is not when should I use Return
but rather when must I use return and that is even more rare, if ever. The operation above can also be written using Throw
and Catch
:
Scan[
If[# > 2, Throw[#]] &,
{1, 2, 3, 4, 5}
] // Catch
3
(Like Return
using the second parameter of Throw
and Catch
will make this operation more robust.)
Exclusive Usage
I am not prepared to say that there is ever a case where one must use Return
as there are so often clever alternative methods in Mathematica. However one example where it is at least far easier to use Return
is if it is not practical to insert a Catch
at an arbitrary place in the evaluation.
Suppose we have a function with multiple internal operations which itself accepts a function:
func[f_, dat_] := Array[f, #, #2] & ~MapIndexed~ dat ~Flatten~ {2}
func[f, {4, 1, 3}]
{{f[1], f[2], f[3]}, {f[2], f[4]}, {f[3], f[5]}, {f[4]}}
Without modifying func
we can use Return
to affect the evaluation in different ways:
func[If[# > 4, Return[{0, 7, 9}, Array], #] &, {4, 1, 3}]
func[If[# > 4, Return[{{1, 2}, {3}}, MapIndexed], #] &, {4, 1, 3}]
func[If[# > 4, Return["foo", func], #] &, {4, 1, 3}]
{{1, 2, 0}, {2, 7}, {3, 9}, {4}}
{{1, 3}, {2}}
"foo"
I apologize for obfuscation from what was perhaps a poorly chosen example function, but the point is that we can exit from and return a value for an individual use of Array
, or the one call to MapIndexed
, or the entire function func
.
Note that there was no example of Flatten
for the parameter of Return
because it does not work:
func[If[# > 4, Return["x", Flatten], #] &, {4, 1, 3}]
Return::nofunc: Function Flatten not found enclosing Return[x,Flatten]. >>
Hold[Return["x", Flatten]]
The reason for this is complicated and it is the subject of Rojo's answer found in the second bulleted link from the top of this post.