12

Python has (1,) for a single element tuple. In Scala, (1,2) works for Tuple2(1,2) but we must use Tuple1(1) to get a single element tuple. This may seem like a small issue but designing APIs that expect a Product is a pain to deal for users that are passing single elements since they have to write Tuple1(1).

Maybe this is a small issue, but a major selling point of Scala is more typing with less typing. But in this case it seems it's more typing with more typing.

Please tell me: 1) I've missed this and it exists in another form, or 2) It will be added to a future version of the language (and they'll accept patches).

Oscar Boykin
  • 1,974
  • 2
  • 11
  • 16
  • I don't understand what's the purpose of Tuple1. – paradigmatic Jun 24 '11 at 07:06
  • 2
    @paradigmatic It allows a value to implement the `Product` interface (e.g. so that you can pass it in to a method that takes a `Product` as an argument). Of course, you lose static typing this way. – Aaron Novstrup Jun 24 '11 at 15:20
  • @paradigmatic Sometimes third-party tools implement things that require tuples of any arity as input. For example, when using breeze.linalg.DenseMatrix to create numeric matrices, one of the constructors takes a variadic argument list of tuples, where each tuple corresponds to a row. If you want to create an Nx1 matrix (single column, but typed as a Matrix and not as a Vector), then you might want to pass in a variadic list of single-element tuples to that constructor. The point is that even if one can't anticipate why someone might need this, it's too shortsighted to omit it. – ely Aug 15 '18 at 14:04

3 Answers3

4

You can define an implicit conversion:

implicit def value2tuple[T](x: T): Tuple1[T] = Tuple1(x)

The implicit conversion will only apply if the argument's static type does not already conform to the method parameter's type. Assuming your method takes a Product argument

def m(v: Product) = // ...

the conversion will apply to a non-product value but will not apply to a Tuple2, for example. Warning: all case classes extend the Product trait, so the conversion will not apply to them either. Instead, the product elements will be the constructor parameters of the case class.

Product is the least upper bound of the TupleX classes, but you can use a type class if you want to apply the implicit Tuple1 conversion to all non-tuples:

// given a Tupleable[T], you can call apply to convert T to a Product
sealed abstract class Tupleable[T] extends (T => Product)
sealed class ValueTupler[T] extends Tupleable[T] { 
   def apply(x: T) = Tuple1(x) 
}
sealed class TupleTupler[T <: Product] extends Tupleable[T] { 
   def apply(x: T) = x 
}

// implicit conversions
trait LowPriorityTuple {
   // this provides a Tupleable[T] for any type T, but is the 
   // lowest priority conversion
   implicit def anyIsTupleable[T]: Tupleable[T] = new ValueTupler
}
object Tupleable extends LowPriorityTuple {
   implicit def tuple2isTuple[T1, T2]: Tupleable[Tuple2[T1,T2]] = new TupleTupler
   implicit def tuple3isTuple[T1, T2, T3]: Tupleable[Tuple3[T1,T2,T3]] = new TupleTupler
   // ... etc ...
}

You can use this type class in your API as follows:

def m[T: Tupleable](v: T) = { 
   val p = implicitly[Tupleable[T]](v) 
   // ... do something with p
}

If you have your method return the product, you can see how the conversions are being applied:

scala> def m[T: Tupleable](v: T) = implicitly[Tupleable[T]](v)
m: [T](v: T)(implicit evidence$1: Tupleable[T])Product

scala> m("asdf") // as Tuple1
res12: Product = (asdf,)

scala> m(Person("a", "n")) // also as Tuple1, *not* as (String, String)
res13: Product = (Person(a,n),)

scala> m((1,2)) // as Tuple2
res14: Product = (1,2)
Aaron Novstrup
  • 20,967
  • 7
  • 70
  • 108
  • This approach seems complete, but I wonder why not just use an implicit parameter, for instance have toTuple take (implicit m: TupleConverter[T]) which has implicit values doing similar to what you did. That seems like it would be fewer lines of code. Comments on this approach? – Oscar Boykin Jun 25 '11 at 21:46
  • I'm not sure I follow what you're suggesting, but it sounds like you mean that your API method would still take a `Product` argument. If so, case classes would _not_ be wrapped in a Tuple1 since they already extend the `Product` trait (and therefore have no need of conversion). – Aaron Novstrup Jun 26 '11 at 00:54
  • @Oscar Boykin [This answer](http://stackoverflow.com/questions/6514380/define-a-function-with-tuples/6515307#6515307) might offer a simpler approach. Maybe this is what you had in mind with your comment? – Aaron Novstrup Jul 03 '11 at 01:57
  • Thanks, Aaron. That example is almost perfectly relevant. – Oscar Boykin Jul 04 '11 at 17:43
3

You could, of course, add an implicit conversion to your API:

implicit def value2tuple[A](x: A) = Tuple1(x)

I do find it odd that Tuple1.toString includes the trailing comma:

scala> Tuple1(1)
res0: (Int,) = (1,)
Nate Nystrom
  • 475
  • 2
  • 6
  • Darn, you beat me by seconds. :) – Aaron Novstrup Jun 23 '11 at 22:14
  • What if you're making a DSL that wants to accept either single items, or tuples of items. If I do value2tuple, a tuple2 -> Tuple1[Tuple2[A,B]], which is not what I want. I want non tuple -> Tuple1[A], and TupleX -> TupleX. Can I do this with some kind of constraints on the type in value2tuple? – Oscar Boykin Jun 24 '11 at 18:46
2

Python is not statically typed, so tuples there act more like fixed-size collections. That is not true of Scala, where each element of a tuple has a distinct type. Tuples, in Scala, doesn't have the same uses as in Python.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 1
    The static typing seems irrelevant to me. When you print a Tuple1 in scala it is printed as (1,). It seems inconsistent that you can not use that as a literal. – Oscar Boykin Jun 25 '11 at 21:48
  • @Oscar The mkString of Scala types are not valid literals. For instance, `List("abc")` is printed as `List(abc)`. Instead, they target readability. In this case, the extra comma is there just to indicate it represents a tuple. By the way, the trailing comma _was_ valid up to a few versions back, and was removed because it added too much complexity for too little gain. – Daniel C. Sobral Jun 26 '11 at 23:53
  • @Oscar And the static typing point is that `Tuple1` is not used in Scala. In Python, it has use as a fixed width collection of size 1, but tuples in Scala are _not_ collections, so there's no use for it. – Daniel C. Sobral Jun 26 '11 at 23:55
  • 1
    Tuples are Product instances, which are a bit like collections, so I'm not sure I agree with this argument. – Oscar Boykin Jun 27 '11 at 23:31
  • @Oscar All product provides is an iterator. An iterator is not a collection, and no one uses tuples for its `Product`. – Daniel C. Sobral Jun 28 '11 at 00:44