In Scala, the pattern matching anonymous function keeps surprising me. I just discovered the following behaviors, and I wanna know how to understand it.
It seems, the parameter signature of the anonymous function { case (a, b) => (b, a) } can be annotated both as a single tuple parameter and a tuple of parameters:
scala> ({ case (a, b) => (b, a) }: ((Int, Int)) => (Int, Int))
val res1: ((Int, Int)) => (Int, Int) = $Lambda$1160/0x0000000801127040@689fe2a3
scala> ({ case (a, b) => (b, a) }: (Int, Int) => (Int, Int))
val res2: (Int, Int) => (Int, Int) = $Lambda$1161/0x000000080106e840@1784d711
Notice res1 has a tuple parameter, while res2 has two parameters. Why is that? Is this behavior defined in language specs? (Sorry didn't check out yet, it seems too dense for me at the moment.)
Also, the res1 function magically accepts both a tuple argument and two arguments at call-site.
scala> res1((1,2))
val res3: (Int, Int) = (2,1)
scala> res1(1,2)
val res4: (Int, Int) = (2,1)
In comparison, res2 won't accept a tuple argment:
scala> res2((1,2))
^
error: not enough arguments for method apply: (v1: Int, v2: Int): (Int, Int) in trait Function2.
Unspecified value parameter v2.
What's going on here? Why res1 can change its signature as it sees fit?
Is this behaviour defined in language specs?
It does not seem to be specified according to open issue Spec doesn't mention automatic tupling #3583
What's going on here? Why
res1can change its signature as it sees fit?
This feature is called auto-tupling. It might make some call-sites more natural as discussed in Remove Two Warts: Auto-Tupling and Multi-Parameter Infix Operations #4311
(x, y) == z
z == ((x, y)) // !yuck
Many argue for its removal/restriction, for example, Let’s drop auto-tupling.
Compiler flag -Xlint:adapted-args can warn when auto tupling happens, for example
➜ ~ scala
Welcome to Scala 2.13.3 (OpenJDK 64-Bit Server VM, Java 1.8.0_265).
Type in expressions for evaluation. Or try :help.
scala> def foo (x: (Int, Int)) = x
def foo(x: (Int, Int)): (Int, Int)
scala> foo (1,2)
val res0: (Int, Int) = (1,2)
scala> :replay -Xlint:adapted-args
replay> def foo (x: (Int, Int)) = x
def foo(x: (Int, Int)): (Int, Int)
replay> foo (1,2)
foo (1,2)
^
On line 2: warning: adapted the argument list to the expected 2-tuple: add additional parens instead
signature: foo(x: (Int, Int)): (Int, Int)
given arguments: 1, 2
after adaptation: foo((1, 2): (Int, Int))
def foo(x: (Int, Int)): (Int, Int)
val res0: (Int, Int) = (1,2)
Another related flag is -Xlint:multiarg-infix, for example
➜ ~ scala
Welcome to Scala 2.13.3 (OpenJDK 64-Bit Server VM, Java 1.8.0_265).
Type in expressions for evaluation. Or try :help.
scala> def % (a: Int, b: Int) = (a,b)
def $percent(a: Int, b: Int): (Int, Int)
scala> :replay -Xlint:multiarg-infix
replay> def % (a: Int, b: Int) = (a,b)
^
warning: multiarg infix syntax looks like a tuple and will be deprecated
def $percent(a: Int, b: Int): (Int, Int)
Scala will automatically convert an argument list to a tuple if required:
def f(arg: (Int, Int)) = ???
f(1, 2) // OK
It will not do the reverse conversion
def g(a: Int, b: Int) = ???
g((1, 2)) // Error "not enough arguments for method"
Needless to say there is some debate over this feature...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With