Is it possible to have currying and variadic function at the same time

curryingfunctional programming

I am thinking about making currying and variadic functions both available in a dynamically-typed functional programming language, but I wonder if it is possible or not.

Here are some pseudocode:

sum = if @args.empty then 0 else @args.head + sum @args.tail

which is supposedly to sum all its arguments. Then, if sum itself is treated a number, then the result is 0. for example,

sum + 1

is equal to 1, assuming that + can only work on numbers. However, even sum == 0 is true, sum will still maintain its value and functional property no matter how many arguments are given (hence "partially applied" and "variadic" at the same time), for example, if I declare

g = sum 1 2 3

then g is equal to 6, however, we can still further apply g. For example, g 4 5 == 15 is true. In this case, we cannot replace the object g by a literal 6, because although they yield the same value when treated as an integer, they contain different codes inside.

If this design is used in a real programming language, will it cause any confusion or ambiguity?

Best Answer

How can varargs be implemented? We need some mechanism to signal the end of the argument list. This can either be

  • a special terminator value, or
  • the length of the vararg list passed as an extra parameter.

Both of these mechanisms can be used in the context of currying to implement varargs, but proper typing becomes a major issue. Let's assume that we are dealing with a function sum: ...int -> int, except that this function uses currying (so we actually have a type more like sum: int -> ... -> int -> int, except that we don't know the number of arguments).

Case: terminator value: Let end be the special terminator, and T be the type of sum. We now know that applied to end the function returns: sum: end -> int, and that applied to an int we get another sum-like function: sum: int -> T. Therefore T is the union of these types: T = (end -> int) | (int -> T). By substituting T, we get various possible types such as end -> int, int -> end -> int, int -> int -> end -> int, etc. However, most type systems do not accommodate such types.

Case: explicit length: The first argument to a vararg function is the number of varargs. So sum 0 : int, sum 1 : int -> int, sum 3 : int -> int -> int -> int etc. This is supported in some type systems and is an example of dependent typing. Actually, the number of arguments would be a type parameter and not a regular parameter – it would not make sense for the arity of the function to depend on a runtime value, s = ((sum (floor (rand 3))) 1) 2 is obviously ill-typed: this evaluates to either s = ((sum 0) 1) 2 = (0 1) 2, s = ((sum 1) 1) 2 = 1 2, or s = ((sum 2) 1) 2 = 3.

In practice, none of these techniques should be used since they are error-prone, and don't have a (meaningful) type in common type systems. Instead, just pass a list of values as one paramter: sum: [int] -> int.

Yes, it is possible for an object to appear as both a function and a value, e.g. in a type system with coercions. Let sum be a SumObj, which has two coercions:

  • coerce: SumObj -> int -> SumObj allows sum to be used as a function, and
  • coerce: SumObj -> int allows us to extract the result.

Technically, this is a variation of the terminator value case above, with T = SumObj, and coerce being a un-wrapper for the type. In many object-oriented languages, this is trivially implementable with operator overloading, e.g. C++:

#include <iostream>
using namespace std;

class sum {
  int value;
public:
  explicit sum() : sum(0) {}
  explicit sum(int x) : value(x) {}
  sum operator()(int x) const { return sum(value + x); }  // function call overload
  operator int() const { return value; } // integer cast overload
};

int main() {
  int zero = sum();
  cout << "zero sum as int: " << zero << '\n';
  int someSum = sum(1)(2)(4);
  cout << "some sum as int: " << someSum << '\n';
}
Related Topic