4

Most modern programming languages do not allow multiple return types (excluding scenarios like C#'s out parameters or the newest use of Tuples). This is because all languages implement the concept of mathematical functions, which for a given number of input parameters, produce only one result (by one I don't necessarily mean a single integer but a single output, even it's a set, a double or anything else).

We can overcome this issue with the use of objects or in older languages (like C) with a struct of multiple values. I have seen even arrays employed in certain cases.

But intuitively, we programmers are been discouraged on creating a method that will end up needing to return more than one value; this is considered a bad practice.

But with all the updates in modern languages, does only the aforementioned concept in mathematics, justify why methods should return only one value? Is this only for readability and avoiding common misunderstandings between programmers, as also as compiler optimizations?

In other words, is this just a technical limitation or not?

EDIT: I have no example to show. Personally, I strongly believe that functions should return only ONE value. But how do you explain C#'s 7.0 new features that allow such a concept? As I said to @DFord in the comments section, if we just reason this as "the correct thing" without any explanation, we are effectively excommunicating these features and calling those who created them, idiots.

EDIT 2: I am lost in the comment section. There are those who are for the concept of one return value and those who are against it. If, according to some of you, functions in maths can return multiple values, then why languages were designed to return one value and the concept we are discussing came more lately? I can break down my thoughts into two categories:

  • Methods should only return one value, so using intermediate solutions like objects or structs just solve a problem that should not be there in the first place
  • Developers should return as many values as they want, so both the above as also C#'s out parameters and Tuples are completely justified and not just syntactic sugar.

Note: It's entirely different for developers to find alternatives to implement something that is unsupported (e.g. return an object) and for programming languages to officially support this by providing ways to do it via their syntax -thus making it "legit".

  • @DFord I have none. I was just curious, looking at C#'s newest features and ran into Tuples and out parameters. If we just consider them as a bad practice without any reasoning, then we are effectively "excommunicating" C#'s features and calling those who built them idiots. – Lefteris008 Dec 13 '17 at 14:27
  • 2
    Why do you consider them a bad practice? Both out and tuples can be converter into code with just one return type. They could very well be considered a syntactic sugar, that has no bearing on correctness of the code. – Euphoric Dec 13 '17 at 14:31
  • 5
    There is no such mathematical law that functions can only return a single value. In particular, there is only syntactic difference between returning a single object and returning a single tuple of multiple objects. Maths is very flexible – you can do anything as long as you first define what you're doing. – amon Dec 13 '17 at 14:32
  • OK guys, you are contradicting yourselves. Is it or is it not a good practice to return one value? And if it's not due to maths, then why the limitation? – Lefteris008 Dec 13 '17 at 14:34
  • Making functions return just one value and then leaving multiple-valued return on a type system and a programmer is just simpler for compiler implementation. out is wierd case, but if you have ref, which is different use case altogether, then out is simple. – Euphoric Dec 13 '17 at 14:40
5

The reason functions only return one value is a technical one. It has to do with how the stack works in processors (and in assembler languages).

First, a (tiny) lesson in how processors work. The processor in your computer has registers in it. Registers are small bits of memory that exist on the processor chip itself. Anything your processor does interacts with whatever is stored in those registers. But there are only so many registers in your processor (physically, the circuit can only hold so many register circuits, thus your processor only has a handful available to it). With a limited number of registers, your processor will have to swap values in and out of those registers to and from memory (RAM, cache, etc. We will just call it memory for the sake of this discussion, how all that works is immaterial to this.)

Your program has something in it called the stack. The stack is just memory space where your program can store values it needs to run while calling functions (such as local variables). Every time you call a function, a stack frame gets added to the stack. This is where you store things like arguments for the next function, which instruction comes next when the function returns, etc. One thing that happens often is that you have to save your current function's state there before you call another function. And that involves going all the way out to memory to save that information.

Since we use a ton of functions, you switch into and out of stack frames a lot. Part of that switch involves taking values we had to store on the stack and restore those back into registers. And, at a processor level, that switch is expensive (accessing stuff in registers is nearly instant, like measured in nanoseconds instant. Getting stuff from memory is many orders of magnitude slower. If you have to go out to disk, that's pretty much an eternity). So we want to have to swap stuff in and out of registers as little as possible.

So, when a function finishes and we are swapping out of its stack frame, we have to store that return value somewhere. Our choices are either a register or the stack. Much of the time, any value we get back from a function will be used immediately (either stored in a variable, used in a computation, passed to another function, etc). So the bright people that designed those processors decided to optimize for that case and keep the return value in a register and allocated one explicitly for that purpose. Which means you can only have one return value. (This also greatly simplifies how assembler language works, but that's another topic for another time.)

The "use an object to return multiple things" is a workaround to that hardware problem. When you return an object, you are really just stuffing a pointer to that object into that magic register (also not getting into how pointers work, because this is already long enough). The pointer basically lets you say "all the things are stored over in this place in memory, go get it there". And the compiled code knows that the thing in the magic register is a pointer and can handle it appropriately.

So languages were designed around having to deal with this hardware problem. And since there was an adequate workaround in place, it probably wasn't worth the effort of creating the extra language feature. (This probably also has to do a bit with how languages evolved from assembler to higher level languages. They were just building on top of what assembler provided, so that paradigm continued. And since those languages did it, so did the ones that spawned off of them, like C -> C++ -> Java, C#, etc.)

So when you see a language like C# that adds tuples for multiple return values, it's really just syntactic sugar to cover up the whole pointer-to-an-object-in-the-magic-register thing. Why other languages don't do this is purely a design decision by the language creators. Maybe they see no need for it. Maybe they think that since the values are likely to be related, an object will better describe that relationship and force you into that. Maybe it just isn't worth the time and effort when there are other, more useful features being requested. And that will be different for each language.

  • 2
    In other words, we took a hardware quirk and turned it into a "best practice." – Robert Harvey Dec 13 '17 at 18:28
  • @RobertHarvey Pretty much. Limitation turned into workaround that everyone got used to and it became best-practice because reasons. – Becuzz Dec 13 '17 at 18:31
  • 4
    nonsense. if you can return one value on the stack, you can return 2. – ddyer Dec 13 '17 at 18:34
  • 1
    Yes. And before Functions could return a value, we had Subroutines, which could not return a value (used Globals to store result). And before Subroutines, we used Goto Label and another Goto to get back to where we were. And before that... patch cords. Each of these innovations took years. Years. So, the answer to why anything in programming is usually 'history' - "that's the way it happened." – user251748 Dec 13 '17 at 18:39
  • 3
    Nowhere is is written that you have to use a register, or that there is only one. The call/return protocols at the hardware level are designed to give the required behavior, not constrained by some hardware detail. – ddyer Dec 13 '17 at 20:32
3

You are confused about the mathematical concept of functions only returning one value. This doesn't mean that an input can't return a very large aggregate output. It means that for the same input, you will always get the same output.

In other words, if f(1) == Set(1,2,3,...,1000000), f(1) should always return the same set. You don't get a million items the first time you call f with an argument of 1, but an empty set the second time you call it with the same argument. If you want different output, you have to give it different input.

Most programming languages don't restrict you to true mathematical functions. Because of side effects, you can write something like this:

var adder = 0

def f(x: Int): Int = {
  adder += 1
  return x + adder
}

This returns 1 the first time you call f(1), but 2 the second time you call f(1), etc. and is therefore not a true mathematical function. It's not the size of the return value that matters. It can be an infinite list. What matters is that it's the same output given the same input.

People don't recommend against returning aggregate values from functions, they recommend against functions doing more than one thing, having more than one responsibility. That's a very different thing.

1

Well, real-world processors have a more or less generous amount of registers, named places to store values. And those aren't neccessarily equal: The ISA generally restricts some (groups of) instructions to a subset of registers to save instruction codes and transistors, but still provide specialized instructions where it seems appropriate for speed and code density.

In addition to registers, an ISA, or at least the ABI on top of it, often implements a stack, a simple LIFO data structure which can be quite sizable.

Finally, there's always the possibility of passing in a pointer to some output-area, be it manually or hidden.

All those ways can and are used to return simple values fitting a register, complex values filling a whole struct, or even multiple results. The ABI defines the details.

So, if there is no technical restriction limiting us to at most one return-value, then why do few languages easily support more?

Well, the problem is with how you represent what should happen with the individual results, for example in f(g(x, y), z), is f called with 2 arguments, or how many?
And as the simplest way, just disallowing multiple returns does not restrict the generality at all, because the relatively few times multiple results are needed work-arounds work just fine and aren't necessarily more verbose, why bother?

  • +1 for explicitly mentioning ABIs. But regarding your example, that's just a language design problem that can be resolved. In Python tuples are ordinary objects, so f would be called with two args. In Perl, f would be called with 1 or more arguments, depending on what g returns. Similar in Go, except that the number of args is statically known. In a language with a static type system, chaining with multiple return values can be forbidden. Depending on the programming style multiple return values are extremely convenient, e.g. compare Go's ok, val := f(x) with C's status = f(x, &val). – amon Dec 14 '17 at 16:13
0

One answer from a programming language that natively supports multiple return values (Common LISP):

The LISP concept

In LISP, a function can return an arbitrarily long list of values. When using such a function call in a "normal" expression, only the first value is used and the others are lost. There are special constructs that e.g. allow multiple variables to receive the multiple return values.

A useful example is division. The floor function when called with 2 arguments, returns the integer part of the quotient as first result and the remainder as the second. This second result typically comes for free from the division process, isn't useful very often, but if it is, you don't need to do the division twice, you can just grab both results. And most of the time, when you're not interested in the remainder, you can use the floor function "normally", without even thinking about its second result value.

Implications

Most languages (including LISP) are based on the concept of an expression having one value. A function call giving multiple values doesn't fit into that concept, and to me that's the reason why that isn't widely supported.

Some languages use special "out" parameters that receive the values outside of the expression value.

LISP can be categorized as returning one prominent value from a function, normally going into the expression value, and offering additional values that can be captured by using special constructs. I think a concept like this might not be impossible to introduce in e.g. Java, allowing the same function to be used both in single-value expressions and in multiple-value situations, maybe with a syntax like this:

int quotient1 = Math.floor(3.14, 2);
{ int quotient2, double remainder2 } = Math.floor(3.14, 2);
  • Most languages (including LISP) are based on the concept of an expression having one value -- LISP gets away with this because its fundamental data type is a list. It's that list that is the single value that gets returned, as it is in every other programming language that supports returning collections. – Robert Harvey Dec 13 '17 at 17:19
  • 1
    @RobertHarvey That doesn't hit the point. In LISP, returning multiple values is different from returning a list. You might only compare it to returning something like a list that stands for its first element if not treated like a list. Trying to get that e.g. in Java would mean to have a ValuesCollection that automatically converts to its first element, like a kind of unboxing conversion. – Ralf Kleberhoff Dec 13 '17 at 18:03
-1

The reality is that its a simplification, that's all.

At the lowest level, If you return zero or one values, that one value can be left in a special place (say, in a register used for returned values) and that value can be used or not, depending on the callee's desire.

At a higher level, you don't need to clutter the syntax with optional syntax to receive multiple values, and you don't need to worry about the complications of mismatches between the number provided and the number desired.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.