Yaroslav
Yaroslav

Reputation: 4669

Scala implicit def resolution by number of parameters

I have two implicit conversions that add an apply method.

implicit def f1(foo: Foo) = new {
  def apply(x: Int) = ???
}

implicit def f2(foo: Foo) = new {
  def apply(x: Int, y: Int) = ???
}

But I can't use them, because the compiler complains about ambiguous implicit conversions

foo(1) // compile error

Why does it complain if it is clear which one should be used?

Upvotes: 0

Views: 162

Answers (2)

Evgeny Veretennikov
Evgeny Veretennikov

Reputation: 4249

If the problem lies in existing implicit inside Predef, you should disable Predef import like described here: Override Predef's implicit conversions

By example, let's try to make new apply function for String.

scala> implicit def stringToFunction(s: String) = new {
     |   def apply(x1: Int) = ???
     | }
stringToFunction: (s: String)AnyRef{def apply(x1: Int): Nothing}

scala> "123"(15)
<console>:13: error: type mismatch;
 found   : String("123")
 required: ?{def apply: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method augmentString in object Predef of type (x: String)scala.collection.immutable.StringOps
 and method stringToFunction of type (s: String)AnyRef{def apply(x1: Int): Nothing}
 are possible conversion functions from String("123") to ?{def apply: ?}
       "123"(15)
       ^
<console>:13: error: String("123") does not take parameters
       "123"(15)
            ^

So, we should disable augmentString import from Predef:

scala> import Predef.{augmentString => _, _}
import Predef.{augmentString=>_, _}

scala> "123"(15)
<console>:14: error: type mismatch;
 found   : String("123")
 required: ?{def apply: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method wrapString in class LowPriorityImplicits of type (s: String)scala.collection.immutable.WrappedString
 and method stringToFunction of type (s: String)AnyRef{def apply(x1: Int): Nothing}
 are possible conversion functions from String("123") to ?{def apply: ?}
       "123"(15)
       ^
<console>:14: error: String("123") does not take parameters
       "123"(15)
            ^

Let's disable wrapString too, that will finally achieve what we wanted to do:

scala> import Predef.{augmentString => _, wrapString => _, _}
import Predef.{augmentString=>_, wrapString=>_, _}

scala> "123"(15)
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:284)
  at $anon$1.apply(<console>:12)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  ... 31 elided

You can do the same for implicit conversions from your class Foo, following compiler complaints about ambigious conversions.

Upvotes: 1

Evgeny Veretennikov
Evgeny Veretennikov

Reputation: 4249

You should include both apply()'s into one implicit:

implicit def f1(foo: Foo) = new {
  def apply(x: Int) = ???
  def apply(x: Int, y: Int) = ???
}

From http://docs.scala-lang.org/tutorials/tour/implicit-conversions:

An implicit conversion from type S to type T is defined by an implicit value which has function type S => T, or by an implicit method convertible to a value of that type.

So, you should have exactly one implicit method converting Foo to function.

How it works in your example case:

  1. Compiler sees foo(1) invocation.
  2. It replaces it with foo.apply(1).
  3. It founds, that class Foo doesn't have method apply and tries to find implicit conversion to class with this method.
  4. It founds two conversions, f1 and f2, and gives up on it.

Upvotes: 3

Related Questions