scala coalesces multiple function call parameters

2019-01-18 13:49发布

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)

6条回答
在下西门庆
2楼-- · 2019-01-18 14:31

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.

查看更多
Rolldiameter
3楼-- · 2019-01-18 14:34

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).

查看更多
▲ chillily
4楼-- · 2019-01-18 14:38

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")
  }
}
查看更多
不美不萌又怎样
5楼-- · 2019-01-18 14:39

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.

查看更多
我只想做你的唯一
6楼-- · 2019-01-18 14:40

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.

查看更多
男人必须洒脱
7楼-- · 2019-01-18 14:52

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? :)

查看更多
登录 后发表回答