Reputation: 1623
I encounter an issue that an implicit ()=>X
conversion happens when I define and use a unary operator. Below is a minimal example:
class Gate {
def unary_!(): Gate = this
}
class Foo {
private def foo(f: () => Gate) = 1
val gate = new Gate
// Compiles, -Xprint:typer shows it becomes
// Foo.this.foo({
// (() => Foo.this.gate.unary_!())
// })
foo(!gate)
// Does not compile, we get
// error: type mismatch;
// found : Gate
// required: () => Gate
// foo(gate)
foo(gate)
}
Where does this () => Gate
conversion happens and why does it only happen with unary_!
?
Edited
Thanks for the answer! I raised this question because the Eta expansion (from X
to () => X
blocks another implicit conversion we defined for X
and wanted to happen. Removing the unnecessary parentheses from unary_!
solves the problem for us. Thanks!
Upvotes: 1
Views: 213
Reputation: 39577
This is just eta-expansion, which turns a method into a function.
http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#eta-expansion
The trigger is that the expected type is a function.
The link is ambiguous in the spec, but the expansion is at 6.26.5.
Compare these two forms:
scala> foo(gate.unary_!())
<console>:11: error: type mismatch;
found : Gate
required: () => Gate
foo(gate.unary_!())
^
scala> foo(gate.unary_!)
res3: Int = 1
The first case is an application of the function. Doesn't type check.
What is the second case? Instead of adding parens implicitly to turn it into an application, it will first eta-expand to make a function, which type checks.
It's been suggested that adding parens ("empty application" in the spec) should come first, so that it would behave the same as the first case.
Here's the spec wording for how the prefix op is handled:
http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#prefix-operations
A prefix operation op;e consists of a prefix operator op, which must be one of the identifiers ‘+’, ‘-’, ‘!’ or ‘~’. The expression op;e is equivalent to the postfix method application e.unary_op.
But this example shows that it's a member selection, but not an application.
Here's the counterexample without parens on your method definition:
scala> class Gate { def unary_! : Gate = this }
defined class Gate
scala> def foo(f: () => Gate) = 1
foo: (f: () => Gate)Int
scala> val gate = new Gate
gate: Gate = Gate@2db0f6b2
scala> foo(!gate)
<console>:11: error: type mismatch;
found : Gate
required: () => Gate
foo(!gate)
^
There you get simple evaluation first, the first conversion in 6.26.2.
More examples on the related ticket.
A comment there on a linked ticket suggests not only changing the order of implicits, but disabling eta-expansion for this case:
I'd be inclined to go further and deprecate eta-expansion given an expected type of Function0, and avoid triggering it with SAM types of the same shape from the outset.
Which is too bad, because this would have made a nice little puzzler.
Upvotes: 5