Let's drop auto-tupling

It already warns. But there are what I consider false positives. An example from today:

import Ordering.Implicits._

(h, m) <= (21, 0)

Arguably, infix notation should not allow multiple arguments. But it does, so you can write:

List(1, 2, 3, 4) slice (1, 3)

I would argue that that looks like tuple notation more than method argument parentheses. But scalac doesn’t see it that way, apparently.

There are other use cases that I run into often enough.

1 Like

Do you mean -Ywarn-adapted-args? A warning that tells “this may not be what you want” I don’t think should exist. It should just not compile.

Basically anything that’s currently using auto-tupling would need more parens, if we dropped it. It’s an interesting example too because as you suggest, the second set of parens visually looks likes tuple literal, but it’s actually just method application parens.

Would the language be more consistent if we had Tupple.apply, similar to List.apply?

scala> List(1, 2) <= List(21, 0)
res7: Boolean = true

scala> Tuple(1, 2) <= Tuple(21, 0)

Auto tupling seems more important when you use a space instead of a dot when calling methods. Maybe it can be made context sensitive?

An example is when adding values to a map.

x += (1, 2)
becomes
x.+=((1,2))
whereas
x.+=(1,2) wouldn’t be helpful to have auto tuple, and I wouldn’t mind an error.

Same comment as above. Tuple(1, 2) would clear it up. To put in an example, what should the following return?

scala> class Y {
         def +=[A](a: A): Int = 0
         def +=(t: Tuple2[Int, Int]) = 1
         def +=[A, B, C](a: A, b: B, c: C = 0): Int = 2
       }
defined class Y

scala> (new Y) += (1, 2)

Similar thread in old scala-debate.
The Magnet Pattern uses this feature, though I don’t know how many people use this pattern in real world.

1 Like

The drawbacks of anywhere-autotupling are documented in this thread but it would be a shame to lose what is effectively our only tool for safe mixed-typed variadics. It seems like if autotupling is going to mostly go away, it’d be nice to get some way to get it back via a request at the definition site.

1 Like

Why not use varargs?

Unfortunately, varargs can’t preserve the types of the individual items. Even if you use a generic, everything gets upcast to the least upper bound. The trio of generics, autotupling, and typeclasses allow you to actually write safe arity-generic methods (within any relevant tuple size limits).

There is the hacky pattern to get around this by implicit-converting everything to a “bundle” type of the actual argument and the corresponding typeclass instance, but it feels really icky.

2 Likes

Thanks for explaining. Yes, this use case would not be supported, but I’d argue it would be more useful to have direct HList support in the language (granted, that’s a hypothetical feature right now, but one I’d very much like to implement).

5 Likes

Oh for sure! Language-integrated HLists would be great! But aren’t the reworked tuples for Dotty basically HLists? And I still want to be able to write a method where the user calls me as foo(4, "x") but I get an HList like (4, "x").

If we are talking hypotheticals, what if there were a phantom typeclass scala.reflect.ParamList[_], so only if you have:

def foo[A: ParamList](params: A): Unit = ???

The compiler would pass in a tuple? Basically make it an opt-in thing.

4 Likes

Perhaps you could auto-tuple only when the upper bound of the parameter is a subtype of Product (since there is no Tuple trait… or alternatively introduce trait Tuple extends Product and make tuples subtypes of Tuple). When HLists ever make it to Scala, that would become HList instead of Product/Tuple.

It seems like the hypothetical new tuples in dotty have this Tuple trait, which is actually equivalent to HList.

4 Likes

At first you scared me with reflect but yes, if would reliably get a tuple type in A, that would actually be better than how it works now since autotupling doesn’t happen for single arguments presently. It passes muster for capability but I’m not sure that it would pass muster for “language integration”: we seem to mostly use symbolic sigils to alter argument-passing semantics (=> and *).

By the way, would (potential) dropping of auto-tupling contradict with dotty-way of having two-argument function passing to a thing expecting a tuple? I mean, as far as I remember it is supported (or planned to be supported) in dotty to be able to do this:

def apply2ArgToTupled[A, B, C](s: Seq[(A, B)], f: (A, B) => C): Seq[C] = s map f

instead of old-fashioned current

def apply2ArgToTupled[A, B, C](s: Seq[(A, B)], f: (A, B) => C): Seq[C] = s map { case (a, b) => f(a, b) }

I understand that from compiler perspective this case looks like just a syntax sugar for partial functions in one particular case, but in fact it is an auto-tupling which is even recently added (to dotty).

I’m in favor of that kind of tupling, but @joroKr21 pointed out that they also have the tupling that’s more like varags for method calls, except that you don’t explicitly request it. Any 1-argument method where a TupleN is a valid argument type (i.e., it’s polymorphic or takes an Any), can receive either that 1 argument, or N of them, boxed into a tuple. I haven’t verified this, but that’s my understanding.

I just turned auto-tupling off in the Dotty build and regression tests and got about 30 failing tests. I’ll try next to restrict auto-tupling to only the case where the expected type is already a tuple type. Let’s see what that that gives.

The reverse conversion mentioned by @buzden is called auto-untupling. It is completely independent.

3 Likes

So I experimented some more. The result is in https://github.com/lampepfl/dotty/pull/4311. After having done the experiment I came back with mixed feelings. We definitely have to make an exception for infix operators. In retrospect, it looks like the current rules in Dotty (which are stricter than scalac’s) are already sufficient to address the confusing situations with auto-tupling that were mentioned in this thread. In particular the Joda-time example would not auto-tuple and give an error. In light of this I am not sure we need to go further.

1 Like

Thank you for considering the feedback! I am optimistic that we can find a good path forward.

Note though that the one reported here still compiles in Dotty (at this moment in time).

That one throws conversion to Unit in the mix. It should be easy to exclude on top of the rules we have.