3

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.lang.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?

2
  • g.asInstanceOf[Function1[Int,String]](8) works but I don't think it's a good answer... not that there is any. Commented Jan 6, 2011 at 17:58
  • Supposing the syntax in the other question (not your answer, but the actual original question) actually worked, what does the code that calls the functions in the map look like? How does he intend to send the right type to the right function after performing the Map lookup? Commented Jan 6, 2011 at 18:02

2 Answers 2

3
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
Sign up to request clarification or add additional context in comments.

Comments

1

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]).

1 Comment

You can call collect on a collection to safely fix the type, though. It will, though, silently filter out any non-matching elements -- unless you put a default clause throwing an exception.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.