7

This is a troublesome violation of type safety in my project, so I'm looking for a way to disable it. It seems that if a function takes an AnyRef (or a java.lang.Object), you can call the function with any combination of parameters, and Scala will coalesce the parameters into a Tuple object and invoke the function.

In my case the function isn't expecting a Tuple, and fails at runtime. I would expect this situation to be caught at compile time.

object WhyTuple {
 def main(args: Array[String]): Unit = {
  fooIt("foo", "bar")
 }
 def fooIt(o: AnyRef) {
  println(o.toString)
 }
}

Output:

(foo,bar)
retronym
  • 54,768
  • 12
  • 155
  • 168
Landon Kuhn
  • 76,451
  • 45
  • 104
  • 130
  • 4
    Well if a function takes an `AnyRef` as an argument, it expects the argument to be anything, no? I mean even if scala didn't automatically pack the arguments into a tuple, you'd still be able to pass a tuple explicitly, which is of course perfectly type-safe because your function takes anything and tuples are anythings. If your function can only handle certain types of arguments it should either be declared to only take these kinds of arguments or check the argument type dynamically. – sepp2k May 17 '10 at 16:41
  • So what does it expect? You might have to break it up into the various specialist. Of course, if you're expecting anything but Tuple, that'd be no help. – sblundy May 17 '10 at 16:44
  • 1
    Fair enough... but generally the contract (in other programming languages) is if a function expects 1 parameter and is called with 2, then the compile will fail. – Landon Kuhn May 17 '10 at 16:47
  • Could an implicit be doing this? – sblundy May 17 '10 at 16:51
  • If so, it's built into the Scala runtime. How could I find and disable it? – Landon Kuhn May 17 '10 at 16:53
  • 1
    Can you avoid using AnyRef? You're throwing the Scala type system through the window :) It may come with some performance penalty, but have you consider using structural types? – GClaramunt May 18 '10 at 12:27
  • You could not use AnyRef, but note that even println(1, 2) exhibits this behaviour/ – Duncan McGregor May 13 '11 at 20:41

6 Answers6

7

No implicits or Predef at play here at all -- just good old fashioned compiler magic. You can find it in the type checker. I can't locate it in the spec right now.

If you're motivated enough, you could add a -X option to the compiler prevent this.

Alternatively, you could avoid writing arity-1 methods that accept a supertype of TupleN.

retronym
  • 54,768
  • 12
  • 155
  • 168
4

Edit: According to people better informed than me, the following answer is actually wrong: see this answer. Thanks Aaron Novstrup for pointing this out.

This is actually a quirk of the parser, not of the type system or the compiler. Scala allows zero- or one-arg functions to be invoked without parentheses, but not functions with more than one argument. So as Fred Haslam says, what you've written isn't an invocation with two arguments, it's an invocation with one tuple-valued argument. However, if the method did take two arguments, the invocation would be a two-arg invocation. It seems like the meaning of the code affects how it parses (which is a bit suckful).

As for what you can actually do about this, that's tricky. If the method really did require two arguments, this problem would go away (i.e. if someone then mistakenly tried to call it with one argument or with three, they'd get a compile error as you expect). Don't suppose there's some extra parameter you've been putting off adding to that method? :)

Community
  • 1
  • 1
Sam Stokes
  • 14,617
  • 7
  • 36
  • 33
  • 2
    FYI, if you add more parameters to the *call* you just end up with higher level tuples: fooIt("foo", "bar", "jam") produces Tuple3. {commenting on the call, not the method signature} – Fred Haslam May 17 '10 at 18:15
  • 1
    It's unfortunate that this answer wound up with the most up-votes (at the time of this posting). It's actually incorrect -- see [this](http://stackoverflow.com/questions/2850902/scala-coalesces-multiple-function-call-parameters-into-a-tuple-can-this-be-dis/2852147#2852147) and [this](http://stackoverflow.com/questions/5997553/why-and-how-is-scala-treating-a-tuple-specially-when-calling-a-one-arg-function/5998559#5998559) – Aaron Novstrup May 13 '11 at 23:05
4

What about something like this:

object Qx2 {
    @deprecated def callingWithATupleProducesAWarning(a: Product) = 2
    def callingWithATupleProducesAWarning(a: Any) = 3
}

Tuples have the Product trait, so any call to callingWithATupleProducesAWarning that passes a tuple will produce a deprecation warning.

James Moore
  • 8,636
  • 5
  • 71
  • 90
  • 1
    Cute, but he may legitimately want to pass a value of type `Product` (think any case class) to the method. – retronym May 17 '10 at 19:34
  • True - I'm being lazy. I think you'll have to add n individual methods for all the TupleN types you want to protect against if you need to pass a Product in normal use. – James Moore May 17 '10 at 22:36
1

The compile is capable of interpreting methods without round brackets. So it takes the round brackets in the fooIt to mean Tuple. Your call is the same as:

fooIt( ("foo","bar") )

That being said, you can cause the method to exclude the call, and retrieve the value if you use some wrapper like Some(AnyRef) or Tuple1(AnyRef).

Fred Haslam
  • 8,873
  • 5
  • 31
  • 31
  • 3
    Unless I'm missing something this isn't an answer so much as a summary of the question's premise. – sepp2k May 17 '10 at 16:42
0

I think the definition of (x, y) in Predef is responsible. The "-Yno-predefs" compiler flag might be of some use, assuming you're willing to do the work of manually importing any implicits you otherwise need. By that I mean that you'll have to add import scala.Predef._ all over the place.

sblundy
  • 60,628
  • 22
  • 121
  • 123
0

Could you also add a two-param override, which would prevent the compiler applying the syntactic sugar? By making the types taking suitably obscure you're unlikely to get false positives. E.g:

object WhyTuple {

  ...

  class DummyType

  def fooIt(a: DummyType, b: DummyType) {
    throw new UnsupportedOperationException("Dummy function - should not be called")
  }
}
pdbartlett
  • 1,521
  • 10
  • 18
  • Of course, this isn't very scalable if you have many such methods, or if you need to do it for more than 2 params. – pdbartlett May 18 '10 at 09:04