0

There are languages for which functions take exactly one argument and return one argument. This is nice and symmetrical, and there is no need to accept more than one argument because we can use currying: If we want a a function to take two arguments, we can take the first and return a new function (that has the fist embedded in it), that takes the second.

Is there a way to allow returning of more than one value?

  • 4
    There are plenty of ways; the most straightforward way is to return an object containing multiple items (like a tuple). Some languages like C# allow you to declare a composite return type. – Robert Harvey May 2 at 22:35
  • I agree with @RobertHarvey about multiple return values being wrapped in an object--it's not just cleaner (one return object) but it is inherently better defined. I have very few use cases where returning multiple, possibly somewhat unrelated values, feels more that a bit kludgey; like it was tacked on by someone 5 years after initial code release. – Peter Rowell May 3 at 0:26
  • 2
    Mathematically, you can think of a multi-argument function as a function taking a single argument that is a tuple (for clarity, this is different than currying); as other's have described, returning multiple values often comes down to the same idea - you return a tuple, or a composite object of some kind. But what do you mean by currying the return value (assuming it's not itself a function)? – Filip Milovanović May 3 at 3:58
2

There are multiple ways of supporting the returning of more than one value from a function. A really bad way that used to be popular in C libraries many years ago (no idea if it's still used), was to set a global value in addition to the return value. So a function that returned a point to eg a file handle, would return 0 on error and set an global error code to indicate what went wrong:

if (fp = fopen(some file))
{
    // do file io
}
else
{
    // use errno to identify specific error type
}

A common way still used in C for example, is to supply a block of memory via a parameter:

p = malloc(sizeof(some struct));
if (func(&p)) 
{
    // *p now has returned data in addition to the boolean return value
}

In eg C#, a similar approach is provided using "out parameters":

if (Method(out var p)) {
    // p is set via the Method in addition to the boolean return value

These days though, there is a growing interest in functional programming and thus in how it handles returning multiple values, without the side effects of the above approaches. Whilst a curried function may only take in one value via its one parameter and return just one value, there is no restriction on what those values are. And so that value can be compound value, ie some sequence of sub values. A common such sequence is to use a tuple. And tuples may even be used for single parameter functions when that one parameter also is a compound value, eg:

(x, y) = polarToCartesian(r, θ)

Whilst tuples have their place, it can often make sense to replace that informal collection with some sort of struct, record or class, so we might then still have a single return value, but that value is eg an object:

class Polar
{
    Polar(double radius, double theta) => (Radius, Theta) = (radius, theta);

    double Radius { get; } 
    double Theta { get; } 
}

class Cartesian
{
    Cartesian(double x, double y) => (X, Y) = (x, y);

    double X{ get; } 
    double Y{ get; } 
}

public Cartesian 

Cartesian PolarToCartesian(Polar polar)
{
    ...
}
1

I agree with David Arno that you would commonly use either a tuple or better a data object to hold multiple return values. Other methods:

  • Modify some state that the caller of your method has access to.
  • Use something like C#'s out parameter see here

Alternative: Pass a callback-function that uses the additional "return value"

R1 myMethod(Consumer<R2> callback) {
    R1 result1 = ...
    R2 result2 = ...
    callback.accept(result2)
    return result1;
}

But most of all, would not the equivalent of currying be returning a function? So, as a first draft (using Java, but should generalize):

// myMethod inlined, so we can focus on the caller/consumer of the return values
Consumer<BiConsumer<R1, R2>> result = c -> c.accept(new R1(), new R2());

result.accept((r1, r2) -> {
    // do something with r1, r2
});

Second attempt (since a BiConsumer is not really "currying" the return values):

// myMethod inlined, so we can focus on the caller/consumer of the return values
final Consumer<Function<R2, Function<R1, Void>>> result = f1 -> {
    final Function<R1, Void> f2 = f1.apply(new R2());
    f2.apply(new R1());
};

result.accept(r2 -> {
    return r1 -> {
        // do something with r1 and r2
        return null;
    };
});
1

There is the continuation-passing style where instead of a function returning a result, one adds a parameter with a continuation function. As such one could add the continuation's number of parameters (for instance).

Currying:

  • f(3)(4)
  • g(4)
  • h

The "reverse" of currying I would call the generation of a sequence, a loop with a yield of a next single item, a recursive data structure. A simple tuple as result would do.

  • seq = f()
  • x = seq.head
  • seq2 = seq.tail
  • y = seq2.head

Or in the function body:

 def f(n)
     n == 0 => *tuple()
     n == 1 => *tuple(1)
     n > 1 => *tuple(n mod 2, f(n div 2))

Using a constructor instead of a function.

I do not dare to state something more concrete.

  • I see several ideas in here, but it is hard to understand. I think same titles would help. – ctrl-alt-delor May 3 at 19:26
  • Indeed, one could write an essay on this topic. I am afraid that "hard to understand" is largely founded on my unclear perception. I just saw some related concepts. What I intentionally called a constructor is indeed the inverse of a reducing function: tuple, complex, matrix, person, bank_account. So returning such a function (currying) and returning a constructing expression might be called inverses too. Sorry, I have to go on. ;) – Joop Eggen May 6 at 7:01
  • You seem to be full of good ideas, but when I got a little passed “"hard to understand"”, my english language parser crashed. (I have no idea, what you are saying) – ctrl-alt-delor May 6 at 8:50
  • Sorry, every developer should be able to explain oneself, certainly to another colleague. If one does return list(2, 3, 5, 7, 11, 13) then list is not a function but constructor. sum(2, 3, 5, 7, 11, 13) would be a function with an evaluation (reduction), whereas list creates data: list would in java correspond to new ArrayList() or such. But admittedly: this is much blabla from my side. Something else one cannot expect from such almost science philosophical question. @DavidArno has a more concrete answer. – Joop Eggen May 6 at 9:08

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.