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?

3 Answers3

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)
{
    ...
}
David Arno
  • 39,599
  • 9
  • 94
  • 129
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;
    };
});
sfiss
  • 955
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.

Joop Eggen
  • 2,629