How do you invoke a Function1[_, String] in Scala?
I answered a question about a map of functions in Defining a Map from String to Function in Scala which led to a Function1[_, String]
which I believe is correct as far as the typing question but possibly useless because I don't know how to invoke such a function:
scala> def f(x: Int) = x.toString
f: (x: Int)java.lang.String
scala> f(8)
res0: java.lan开发者_开发技巧g.String = 8
scala> val g: Function1[_, String] = f _
g: Function1[_, String] = <function1>
scala> g(8)
<console>:8: error: type mismatch;
found : Int(8)
required: _$1 where type _$1
g(8)
^
scala> val h: Function1[Int, String] = g
<console>:7: error: type mismatch;
found : (_$1) => String where type _$1
required: (Int) => String
val h: Function1[Int, String] = g
Is there any way to use g
?
scala> g.asInstanceOf[Any => String](5)
res3: String = 5
It will work because all functions erase to the same thing: Function1[AnyRef, AnyRef]
. When you specify it as Any
, then passing an AnyVal
will auto-box it on call (and it will be auto-unboxed at the method).
However, you do have to pass the correct parameter type. Or else...
scala> g.asInstanceOf[Any => String](true)
java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.Integer
I would say that it's like if you've cast an object of type String
as Any
, if you want to use a method defined in String
you have to cast it back as a String
.
You cast the function as a function that takes an argument of an existential type (which is what _
in a type context means), so you can't use it as a function that takes an Int
. To use it as a function that takes an Int
you have to cast it back.
The same problem exists when pattern matching with collections, or other generic classes:
def firstLength(collection: Any): Int ={
collection match {
// this is how you would have liked to write it
// case list: List[String] => list.head.length
// case map: Map[String, String] => map.values.head.length
// but this is how you have to write it because of type erasure
case list: List[_] => list.asInstanceOf[List[String]].head.length
case map: Map[_, _] => map.asInstanceOf[Map[String, String]].values.head.length
}
}
The type information isn't there, so you can't match on List[String]
, instead you have to match on the existential type List[_]
(might be wrong on how you say that, it's not the generic type that is existential, I think) and then cast. This is more or less exactly the problem you have, the type you're after has been erased, and there's no way to get it back (unless you can use the same trick with ClassManifest
that can be used to get around type erasure in cases like the one above [but not actually the case above, because it's a bit sloppy]).
精彩评论