Gilad Bracha on tuples

Java was actually designed to have tuples from the start, but they never quite got in. At one point, I tried to add a tuple-like construct as part of JSR-65. We wanted to extend array initializers to full blown expressions. Eventually, that effort got quashed; I didn’t really resist, since the extension was painful, as such things always are in Java. We should have just done straight tuples as a separate construct - but at the time, that was frowned upon too.

Not surprisingly, Gilad thinks that tuples are great.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Tuples for returning multiple values?

Hmmm, I'm not sure I buy that tuples (at least as I understand what he means by that) "subsumes" multiple-value returns. To me the beauty of returning multiple-values (as it works in Common Lisp) is that the caller doesn't have to care that the callee returns multiple values unless the caller actually cares about the extra values. If you return a tuple, and the caller only cares about the "primary" value, it still has to unpack the tuple. That seems less good. Or I'm missing something.

Tuples as multiple values

A language could implicitly translate x = foo() to something like (x, _, _) = foo() if that's considered desirable. Not all multiple value mechanisms work this way, though; Scheme's doesn't, for example.

Whether any actual packing or unpacking occurs internally is also an implementation-dependent issue, e.g. a tuple's values can be returned in registers, so that accessing a single value is just a register access for the caller. This is easier to arrange if you have some static type information, though.

ML and Haskell are classic examples of languages which use tuples as multiple values. All functions accept a single argument and return a single value, but they can be tuples. It means that "argument lists" as well as multiple-value returns are both first-class values, which can be quite useful. You don't need a Lisp-style 'apply' function to apply a function to a tuple of arguments, for example, and you can store a multiple value return as a single value for later use, without having to convert to or from some alternate first-class representation.

Currying

While elegant, there is a downside when using tuples uniformly for arguments and return values as in ML: currying no longer fits nicely into this framework. This is no problem for Java because it doesn't offer currying.

actually the use of tuples

actually the use of tuples in SML code is moreso a by product of the language semantics making tuple arguments more efficient that curried arguments. (In contrast, with Ocaml, the evaluation strategy that is used means that curried arguments are essentially as efficient as tupled arguments). Granted, this differential in parameter passing styles can be eliminated in a whole program compilation context, but thats a whole other story.

Byproduct?

Not sure what you are referring to, I'm not aware of anything in the SML semantics that would make currying intrinsically more expensive than tupling, or would require WPC to get rid of. Nor is this the case in most SML implementations, AFAICT.

In fact, I think your conjecture is not correct: the preference for tupling in SML is quite intentional design. Although it probably is mostly for historic reason, there still are certain advantages to tupling. For instance, the symmetry between multiple function arguments and results that Anton was alluding to, and which eases certain forms of composition. Treatment of algebraic datatypes becomes more uniform. Some people also favour the more "conventional" application syntax it results in.

Personally, I prefer currying, because it has more practical advantages. But I also like the language to be consistent about it (SML is, Haskell is, OCaml isn't for constructors).

Byproduct!

The issue is that in the expression

f x y

you have to evaluate f, then x, then the result of applying f to x (might have side effects, you see), then y, then the result of applying the result of f x to y.

Moscow ML ignores this rule and evaluates f, x and y in that order, and then dives into the definition of f. You can see the difference with

val yref = ref 2;
fun f x = (yref := !yref + 1; (fn y => x + y))

val _ = print (Int.toString (f 2 (!yref)) ^ "\n")

Moscow ML prints 4. I'm moderately confident that MLton would print 5 (but don't have a working installation to check that right now).

Michael.

Hamlet prints 5, I'm pleased

Hamlet prints 5, I'm pleased to report.

Michael.

Evaluation order

Moscow ML prints 4.

Interesting, I wasn't aware of that. That clearly is a bug, most likely due to the fact that Moscow ML is built on the Caml runtime, which obviously isn't designed for SML.

Thanks for reminding me of the evaluation order issue. I must have been asleep.

I did a quick experiment with MLton, SML/NJ, Poly/ML and Moscow ML. The former three showed no measurable difference in performance between a typical recursive loop using a curried function vs one using tupled arguments. Moscow ML actually was twice as fast on the curried version.

However, making the recursive call first-class indeed shows significant degradation for the curried version (about 50%). Here is the tupled version of that code, the curried is analogous:

datatype fix = F of fix * int * int -> unit
val x0 = 100000
fun f(_, 0, 0) = ()
  | f(ff as F g, 0, y) = g(ff, x0, y-1)
  | f(ff as F g, x, y) = g(ff, x-1, y)
val _ = f(F f, x0, 10000)

If you have a language that

If you have a language that supports method dispatch based on the return value, you could make a method that only returns a tuple then more than one return value is expected.

That's debatable: when the

That's debatable: when the function returns a tuple and the caller treats only a single value, it could also be a mistake in the code..

Requiring the programmer to write it 'x = first(f())' doesn't seem too bad, I think.

It might be a little strong

It might be a little strong to include C++ in the statement "[tuples]
are lacking in mainstream languages like Java, C#, C++ etc." because
Boost endows C++ with good tuple functionality (albeit as a template
library rather than a core language feature).