Problem with implicit conversions and higher-order apply methods in Scala
I am trying to extend String
with a new apply method that lets me apply an higher-order function on it. Example:
case class A(s:String, f: List[String] => List[String])
val f: List[String] => List[String] = { ... stuff ... }
"foo"{f} // == A("foo", f)
So I have defined an implicit conversion from String to something with an apply method that takes a List[String] => List[String]
function.
implicit def c(s:String) = new {
def apply(f: List[String] => List[String]) = A(s, f)
}
But when I try to use it, the conversion collides with the one in Predef that converts String
to StringOps
.
scala> "foo"{f}
<console>:19: error: type mismatch;
found : java.lang.String
required: ?{val apply: ?}
Note that implicit conversions are not applicable because they are ambiguous:
both method c in object $iw of type (s: String)java.lang.Object{def apply(f: (List[String]) => List[String]): A}
and method augmentString in object Predef of type (x: String)scala.collection.immutable.StringOps
are possible conversion functions from java.lang.String to ?{val apply: ?}
"foo"{f}
^
Why does it look for a generic apply method (required: ?{val apply: ?}
) and not one that takes an argument of m开发者_如何转开发y type (List[String] => List[String]
)?
Edit:
I solved this by refraining from using bare strings to express variables (in the project i'm working on github). So now it looks like this:
case class param(val v: String) {
def apply(f: Emit.Selector) = Variable(v, f)
}
val $foo = param("foo")
foo{selector} // works fine
And I don't need to use implicits.
Further update
It seems scala does look for type parameters in the result types of implicits when searching. I get this to work, but the scenario with the function parameter and the apply method does not work. How come?
scala> class A()
defined class A
scala> class B()
defined class B
scala> implicit def one(s:String) = new {
| def a(a:A) = s + " A"
| }
one: (s: String)java.lang.Object{def a(a: A): java.lang.String}
scala> implicit def another(s:String) = new {
| def a(b:B) = s + " B"
| }
another: (s: String)java.lang.Object{def a(b: B): java.lang.String}
scala> "hello" a new A
res1: java.lang.String = hello A
scala> "hello" a new B
res2: java.lang.String = hello B
When you write this:
"foo"{f}
The compiler will translate it to:
"foo".apply { f }
More generally: apply
and update
are the two special methods for which syntactic sugar exist in the compiler:
obj(arg) // gets translated to: obj.apply(arg)
obj(index) = arg // gets translated to: obj.update(index, arg)
StringOps already provides an apply(index: Int)
, and while looking for implicit conversions, the compiler looks for the first one that finally provides a member called apply
(regardless of the parameters). In your case, you have a conflict.
Maybe you can rename your method to something like
"foo" useFor { f }
By the way: it's good practice to always declare the return type of implicit conversions. Moreover, you'll want to avoid the new { def apply /* ... */ }
style in performance-critical situations, as any subsequent call to apply
happens through Java reflection, which makes it inefficient.
You can "disable" all standard imports (and thus all standard implicits) by passing -Yno-imports
to scalac (Doesn't work with the repl.). This will avoid the conflict, but then you will have to explicitly import everything you use.
You could change the of the conversion from c to augmentString in order to shadow it, but then will just find another lower priority conversion (wrapString) will get in the way. However if you both shadow the augmentString and adds this to an extension of LowPriorityImplicits:
object HighPriorityImplicits extends LowPriorityImplicits {
implicit def augmentString(s:String) = new {
def apply(f: List[String] => List[String]) = A(s, f)
}
}
Then it should work:
import HighPriorityImplicits._
"foo"{f}
See also: Is there a way to control which implicit conversion will be the default used?
精彩评论