Defining a Map from String to Function in Scala

2020-05-26 18:20发布

问题:

I am trying to define a Map literal with key: String, value: (Any)=>String. I tried the following, but get a syntax error:

def foo(x: Int): String = /...
def bar(x: Boolean): String = /...
val m = Map[String, (Any) => String]("hello" -> foo, "goodbye" -> bar)

回答1:

Funny that no one actually gave a type that would work. Here's one such:

def foo(x: Int): String = x.toString
def bar(x: Boolean): String = x.toString
val m = Map[String, (Nothing) => String]("hello" -> foo, "goodbye" -> bar)

The reason why it works this way is because Function1 is contra-variant on the input, so (Nothing) => String is a superclass of (Int) => String. It is also co-variant on the output, so (Nothing) => Any would be a superclass to any other Function1.

Of course, you can't use it like that. Without manifests, you can't even uncover what the original type of Function1 is. You could try something like this, though:

def f[T : Manifest](v: T) = v -> manifest[T]
val m = Map[String, ((Nothing) => String, Manifest[_])]("hello" -> f(foo), "goodbye" -> f(bar))

val IntManifest = manifest[Int]
val BooleanManifest = manifest[Boolean]
val StringManifest = manifest[String]
m("hello")._2.typeArguments match {
    case List(IntManifest, StringManifest) =>
        m("hello")._1.asInstanceOf[(Int) => String](5)
    case List(BooleanManifest, StringManifest) =>
        m("hello")._1.asInstanceOf[(Boolean) => String](true)
    case _ => "Unknown function type"
}


回答2:

Int => String is not a subclass of Any => String, rather, the contrary. You can't put (replace) an Int => String function when a code expects Any => String, since that code can apply the function with, say, "hi".

@Ben suggestion works, but how is it useful? you can't invoke the function once you get it from the Map.

If you really want to do this, maybe define foo as a partial function:

val foo: PartialFunction[Any, String] = {case i: Int => ....}

Obviously, this will fail at runtime if you pass it a string, but you can always test if the function is ok for use with your parameter by using isDefinedAt. (another alternative may be manifests, but I don't see the value here)



回答3:

If I let the compiler infer it I seem to get an illegal type:

scala> val m = Map("hello" -> foo _, "goodbye" -> bar _)
m: scala.collection.immutable.Map[java.lang.String,(Boolean with Int) => String] =
                Map((hello,<function1>), (goodbye,<function1>))

scala> m("hello")(8)
<console>:9: error: type mismatch;
 found   : Int(8)
 required: Boolean with Int
       m("hello")(8)
scala> var q = new Boolean with Int
<console>:5: error: illegal inheritance from final class Boolean
       var q = new Boolean with Int

Anyway, what you want is not the type Any but a generic of "any type" which is _:

scala> val mm = Map[String, (_) => String]("hello" -> foo _, "goodbye" -> bar _)
mm: scala.collection.immutable.Map[String,Function1[_, String]] =
               Map((hello,<function1>), (goodbye,<function1>))

I just posted a question about how to invoke such functions because I don't actually know.



回答4:

Trait Function1 is contravariant for parameter, so def foo(x: Int): String is not a (Any) => String. So the following would work:

scala> def baz(x: Any): String = "baz"                         
baz: (x: Any)String

scala> val m2 = Map[String, (String) => String]("hello" -> baz)
m2: scala.collection.immutable.Map[String,(String) => String] = Map((hello,<function1>))


回答5:

This is how I did it to fulfill a similar requirement.

object MapToMethods {
private def increment(x: Int): Int = x+1
private def decrement(x: Int): Int = x-1

val m: Map[String, Int => Int] =Map("increment" -> increment, "decrement" ->decrement)

println(m("increment")(2)) //prints 3
println(m("decrement")(3)) //prints 2
}


标签: scala types map