Why do most programming languages only return a single value from a function?
arstechnica.com
arstechnica.com
Content curation is a tried and true technique that some people love. I like reading the SO articles by arstechnica because I don't have to sift through the whole thread _and_ because they are stable, so you know what you link to.
YMMV, but it is definitely more than just copying and excerpt and setting a link, especially as the orginal platform is involved.
(let x (f 12) ...) ; bind x to the first value returned by (f 12) and silently drop the others
(let (x y) (f 12) ...) ; bind x to the first value and y to the second.
That way, I can return more information without breaking existing code. That point is really important: in Python I can gracefully add an extra argument, from def foo(x):
...
to def foo(x, y=None):
...
but I can't return an extra value without changing (and uglifying!) all calling code to handle the tuple.The answer to the OP is probably that all computer languages derive from Fortran, and Fortran derived from maths, so it chose f(x, y) as the notation for function application. With that notation, there's no elegant way of handling more return values, and language designers might feel that the extra complexity/ugliness isn't worth it. That, and intellectual inertia.
(let (x y) (f 12) ...)
but (multiple-value-bind (x y) (f 12) ...)
(There are also multiple-value-call and multiple-value-list, which are more powerful than multiple-value-bind because they let you get all the values returned without knowing in advance how many there are, but usually less convenient.)Ditto for Perl:
sub f { 1..$_[0] }
my ($x) = f(12); # $x = first value, rest are dropped
...
my ($x, $y) = f(12); # $x & $y have first two values, rest are dropped
...
my ($x, $y, @rest) = f(12); # $x=1, $y=2 & @rest=3..12The real answer is "most languages can return multiple values", since whenever structs were introduced. And if you're bugged by the fact that there are multiple input parameters, well, just think of the list of params as a tuple. Which you could picture as anonymous structs anyways.
So that in the end, all functions take one tuple and return one tuple, where tuples can be called structs, classes, or, well, tuples.
The real question being, why is it that there is a syntactic sugar for anonymous input structs but that the sugar for output structs is just being introduced.
And there I have to admit I have no idea why.
Assume a language where functions do not only return one value. Now, consider each function in this language. It either returns the same number of values each time, where each has a specific meaning, and they are distinguished either by order or by name. Or it returns an arbitrary number of values, where the semantics of each are the same. But that means the return values are equivalent to a tuple, a dictionary, or an array, which are single values. Therefore we have a contradiction and such a language does not exist. (Yes, I know about values in Lisp and this can be a bit nice. Mostly joking.)
More seriously, ask the question: what relationship between multiple values is useful for returning from functions but not useful enough to be a data structure that can be used in other parts of code?
Semantically I think it's a lot cleaner to make every function 1-in 1-out. Multiple parameters are just a disguise of passing a tuple. In dynamically typed languages, though, it's difficult to make it efficient.
Any metadata about that particular function call.
E.g. function success / effectiveness (did state change?). Runtime. Memory usage.
EDIT: I think co-dependants are still the primary use case, im just suggesting another less obvious use case.
Improves cohesion. Seperates responsibilities. Removes duplication and redundancy.
When you pass the metadata off for aggregation or other processing with this multi return model, you dont pull along a chunk of irrelevant business data at the time.
Sometimes there are pieces of data which are hard to create a cohesive type around. That is, you can compose an new type from other types, but you can't really add any behavior to that new type. This is exemplified by the Tuple type in many languages: it's basically a hack to treat disparate pieces of data as one, and it doesn't offer any way to operate on its own data.
Here's an example from a relatively typical idiom of parsing an int from a string:
int parsedInt;
if(int.TryParse(s, out parsedInt))
{
Console.WriteLine("Parsed {0}",parsedInt);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
The problem here is that TryParse() really returns two values, one of which will be tested to see if the parse was successful, and the other of which is assigned to parseInt. You could test that the string can be parsed and then get the parsed value in two separate functions, but this would be inefficient because you'd essentially have to parse the string twice. I'm on my Mac so I'm not sure I'm remembering this correctly, but I think C# forces you to put the "out" there, which I think is good because it clarifies that parsedInt will be assigned. However, I think there are a number of ways in which the following (hypothetical) redefinition of TryParse (and C# syntax) feels a lot clearer: var parseSucceeded, parsedInt = int.TryParse(s);
if(parseSucceeded)
{
Console.WriteLine("Parsed {0}",parsedInt);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
First of all, the programmer has been forced to assign the success boolean to a clarifying variable. Second, the call has a clearer structure: the input are on the right side of the = and the outputs are on the left.Let's look at some other possible solutions here:
var parseTuple = int.TryParse(s);
if(parseTuple.Item1)
{
Console.WriteLine("Parsed {0}",parseTuple.Item2);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
This results in some confusing code. Item1 and Item2 are very poor names for the things they represent. IntegerParseResult parseResult = int.TryParse(s); // Didn't use var here to clarify what this code means.
if(parseResult.ParseSucceeded)
{
Console.WriteLine("Parsed {0}",parseTuple.ParsedInt);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
This is a little better, but the creation of a whole new type seems heavyweight, especially given that this new type provides no real functionality. It's tying together two pieces of data which are about to be separated and never used together again (consider, for example, that instead of printing to the console it might return).I guess the argument I'm making here is that just because a function returns to outputs doesn't mean those outputs have a meaningful relationship that would justify having a new type.
case (tryParse s) of
Just n -> putStrLn "Parsed " ++ (show n)
Nothing -> putStrLn (s ++ " was not an integer")
Nothing is a very convenient little concept: It's a value which isn't a 'legitimate' value in any context. It's always a flag value. In C, for example, there's no way to have a flag value of type int in this context, because tryParse could conceivably return any value of type int as its legitimate result. It's also wonderfully conceptually clean: The function either just returns a legitimate value or it returns nothing legitimate at all, and you always know which it did. The point is, however, it only returns one value, and doesn't rely on any out-of-band communications through errno or similar.If you want to get fancy, you can use the Either monad. This can allow you to say why a parse failed, for example:
case (tryParse s) of
Left n -> putStrLn "Parsed " ++ (show n)
Right m -> putStrLn "Parse of " ++ s ++ " failed because " ++ m
In this case, tryParse would have the type: tryParse :: String -> Either Int String
The point here is, you still only return one value, but they can be either of two different kinds of value, as distinguished by being tagged either Left or Right, and can even be different types. Again, one value, no out-of-band signalling.And, of course, Haskell has tuples. You can do interesting things without them, however.
int? result = int.TryParse(s);
if(result.HasValue)
{
Console.WriteLine("Parsed {0}",result.Value);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
And in C#, you could do the second kinda like: object result = int.TryParse(s);
if(result is int)
{
Console.WriteLine("Parsed {0}",result);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
Though there's nowhere simple to make it clear that this TryParse either returns the parsed int, or a string describing why the parse failed. And returning object kinda goes against the spirit of the language, IMHO.Modulus calculations like
a,b = mod(x,n)
where a is the remainder modulo n, and b is the residual, so that x = a + b*n.The two outputs from the quadratic formula.
All the matrix factorizations, such as
L,U,P = lu(A)
where A = P L U, and L and U are triangular, and P is a permutation.The remedy of having a data structure to hold the a and b integers above, or the two roots of a quadratic equation, is asinine.
It's exactly like that in Haskell. If it makes sense to return 2 values — return tuple. Works really well with pattern matching. Syntactic overhead = 0.
The persistence of features in programming languages you're describing isn't a cargo cult because the resulting products work. In addition, there's several benefits of conservative language development: it's generally easier to write compilers; existing programmers will find it easier to adopt a language that mostly resembles ones they already know; and some unfashionable but useful features may be retained.
This is more of a "worse is better" situation than a "cargo cult" one.
List context works really well with list assignment, which lets you assign multiple return values directly to muliple scalar variables. It also works well with passing multiple return values to the input of another function, of course.
http://docs.racket-lang.org/reference/eval-model.html#%28par...
Forth functions (or words, if you insist) can return multiple values as easily as they can return a single value, but the stack metaphor makes this a lot more natural. This is also true of Joy and RPL and other languages which are conceptually organized around mutating a (notional) stack. Multiple return values only become unnatural when you choose an abstraction other than the stack.
(Of course you'd have to dump anything beyond that onto the stack!)
I imagine the world would be a lot different (or at least a bit less buggy!) if C had allowed for returning an error code in addition to a return value.
Once you get to higher level languages though, the need sort of falls away. Put data into a container of some type, call it a day.
I'll make the argument that in Haskell, functions BOTH support only a single input value and a single output value. For example, consider the function (+) which takes two numbers and returns their sum. The type of (+) is (+) :: Num a => a -> (a -> a). I have added the parentheses to emphasize the syntax of the type. (+) is a function which takes a single number as input and gives a single function as output!
Now, we can do something like f :: (a,b) -> (x,y,z) and write:
let (x0,y0,z0) = f (a0,b0)
But this isn't really multiple input or return values. We are just inputting or returning a struct (with constructor (,) or (,,), respectively), and we can pattern match against it for the return value.I think that any elegant language should support as few "primitive features" as possible - it should be simple, and let a few powerful ideas (e.g., first-class functions, currying, and pattern matching) let the rest fall out. I think Haskell really embodies this idea.
I work mostly in C#, and the lack of multiple return values hits me about once a month or so, and I usually have to create a new class just for the purpose of returning two values from one function one time that will never be used again. There's several other possibilities, most of which are even more awkward and annoying - out and ref params, which need some variables declared separately; arrays, lists, and dictionaries, which need either the same type or casting and index by numbers or a string, and tuples, which have strong typing, but name everything Item1, Item2, etc. It might be a good solution if there was support for anonymous functions as return types, but I don't think you can do that right now without using dynamic.
In C#, and I expect Java and most other more statically typed languages, you run into all kinds of awkwardness doing this. Arrays and other data structures are strongly typed by default, and number and type of method returns must be declared. You could return a object[], which is easy enough to declare, but then on the call side, you'd have to declare a object[] to receive the return, then individually cast each element to whatever type it's supposed to be before you can do anything with it. Kinda awkward, and against the spirit of the language.
Declaring multiple returns of different types doesn't seem to help much, either, since you can't declare 2 variables of different types on the same line, so you have to declare them both somewhere else, making it about as awkward as using out params.
The best solution I can think of that fits with the spirit of the language is to use anonymous types. You can already do:
var something = new { Foo = a, Bar = b }; something.Foo.DoStuff(); something.Bar.DoStuff();
In that case, the compiler creates a class for "something" for you invisibly, based on the properties you assigned. What's neat about that is that it maintains strong typing of everything, since it figured out the types from the declaration, so you can still do things with the Foo and Bar properties, and it knows what types they are and enforces all of the standard compile-time type checking.
The downside is that you can't return these anonymous types from a method, as the method return type must normally be statically declared. You could return dynamic, but then you lose all of that static type checking, and are back to a slightly cleaner version of returning object[]. I suppose the language authors figured that it's too complex to return anonymous types from methods, as far as figuring out at compile-time what you're actually calling and what it would return.
And now that I think I've successfully argued myself out of what I originally wanted to do with a ridiculous number of words, I'll just give up.
Python only returns a single value - a tuple. It's just that there is symantic sugar on the functions side to fold multiple values into a tuple, and on the side that's returned to, it's simple value unpacking.
You can validate this in the following way:
>>> def foo():
... return 1, 2
>>> x = foo()
>>> isinstance(x, tuple)
True
The unpacking of tuples into arguments is simply more syntactic sugar, if very useful syntactic sugar.In short, while I love Python, it's still returning just a single value - a tuple, that gives the appearance of returning multiple values.
I've come across the thought myself from time to time... usually have to end up creating objects/structs to then return, though in the past when I've been lazy (on iOS) I've chunked things into NSArray/NSDictionary.
But darn, I can't remember specific examples right now, I know I've definitely had moments where returning multiple things would have been nice lol.
std::pair<T, boolean> was like a gift from the gods when I found out about it
It's hard to see how this is indistinguishable from returning a list/array/tuple plus supporting list destructuring syntactically, even in a limited way. This works in Ruby, for example:
name, email = ["Jesse Farmer", "jesse@20bits.com"]
So, you can have a function which returns an Array and the caller can "act" as if it's returning multiple values of mixed types. Python can do the same thing. Does this count as supporting "multiple return values?"One might think about this mathematically "as if" returning multiple values were the (functional) composition of returning a list and list destructuring. If your language supports lists, you'll always have the ability to return a list, so in a world where one wants to support returning multiple values, why add support for it "directly" when you can add support for something simpler and more broadly applicable and get the same results?
I think that's the practical, language-designy answer. I don't like appealing to the mathematical definition of a function, since that doesn't account for languages like Ruby and Python which presumably wouldn't fall into the original poster's bucket of languages that support "returning multiple values."
And yet, I could (in principle) return a tuple of a different length each time I called a function in either language. This means functions in Python and Ruby are about as far away from the mathematical definition of a function as one can get, so that couldn't be why they don't support multiple return values in the sense that the original poster meant.
Lua is one of the few languages I know which differentiates both syntactically and semantically between "returning a list" and "returning multiple values."
function return_multiple()
return 1,2,3
end
function return_list()
return {1,2,3}
end
a,b,c = return_multiple()
print(a) -- prints out "1"
a,b,c = return_list()
print(a) -- prints out, e.g., "table: 0x7fc991500d20"
Note that I'm using the word "list" where Lua would use the word "table" and using the word "multiple return values" where Lua would use the word "list." That is, in Lua "return 1,2,3" is "returning a list" and "return {1,2,3}" is "returning a table." However, lists aren't values in Lua, i.e., I can't assign a Lua-list to a variable and pass it around, so for clarity's sake I'm using terms that make more sense in non-Lua languages.The interesting thing about Lua's "multiple return values" is that they almost behave like splatted lists in Ruby — like a chunk of syntax.
function add(a,b)
return a + b
end
function return_multiple()
return 1,2
end
sum = add(return_multiple())
-- sum is now 3
-- add({1,2}) would throw an error, however
In Lua, one can use "unpack" to destructure/splat lists like {1,2,3}, so sum = add(unpack({1,2}))
would work.At the end of the day, these approaches all tend to be isomorphic to each other. Given Lua's design considerations (small, fast, easy to move in and out of C, etc.) I'm guessing it makes these distinctions for performance/memory management reasons, but I don't know enough about Lua's internals to say whether that's the actual justification. Outside of that I think Lua's choices are strange since you now have functions which don't return a particular value, but rather these non-syntactic entities called "multiple return values."
With this design you get zero-overhead (you save the construction of a table) multiple return values for free, since it simply requires a trivial bit of syntax and no error checking to verify that a function only left zero or one values on the stack. Lists aren't exposed as concrete things that can be passed around because the really aren't things at all.
> return (function() end)(), 5
nil 5
> return (function() return 1,2,3,4 end)(), 5
1 5
> return 5, (function() end)()
5
> return 5, (function() return 1,2,3,4 end)()
5 1 2 3 4
> return 5, (function() return nil end)()
5 nil