如何使用泛型类型为函数创建字符串映射?例如,我希望能够在编译时创建一个映射:
var unaryFunctionMap: Map[String, Any => Any] = Map[String, Any => Any]()
然后我想添加可以接受String
,Int
,Double
等类型组合的函数。可能的方式如下:
unaryFunctionMap += ("Abs" -> Functions.add)
unaryFunctionMap += ("Neg" -> Functions.neg)
unaryFunctionMap += ("Rev" -> Functions.rev)
我在Functions
课程中实现实际功能的地方:
def abs[T: Numeric](value: T)(implicit n: Numeric[T]): T = {
n.abs(value)
}
def neg[T: Numeric](value: T)(implicit n: Numeric[T]): T = {
n.negate(value)
}
def rev(string: String): String = {
string.reverse
}
因此,abs
和neg
都允许Numeric
类型,而rev
仅允许String
s。当我使用地图时,我希望能够在必要时指定输入。像这样:
(unaryFunctionMap.get("abs").get)[Int](-45)
或者,如果不可能,
(unaryFunctionMap.get("abs").get)(Int, -45)
如何修改代码以实现此功能?
答案 0 :(得分:2)
将函数存储为Any => Any
的问题是函数在其参数类型中是逆变的,但在返回类型中是协变的,因此Int => Int
不是Any => Any
的子类型。您可以使用存在类型来解决此问题,但这仍然不能为您提供类型安全性。
作为一种解决方案,您可能希望使用Map[Type, Map[String, _ => _]]
并确保放入地图的每个条目仅使用相应类型的函数。
以下是草图而不是明确的解决方案;我不知道类型标签足以保证性能的正确性或理由(这需要反思才能工作)。
import scala.reflect.runtime.universe._
class UnaryFunctionMap {
private var internal: Map[Type, Map[String, _ => _]] = Map.empty
def +=[A : TypeTag] (key: String, function: A => A): Unit ={
val a: Map[String, _ => _] = internal.getOrElse(typeOf[A], Map.empty)
// Because of the TypeTag A we make sure that only functions A => A are put into the map where Type == typeOf[A]
internal += typeOf[A] -> (a + (key -> function))
}
def get[A: TypeTag](key: String): Option[A => A] = internal.get(typeOf[A]).flatMap(_.get(key).map(_.asInstanceOf[A => A]))
}
由于方法get
中的显式强制转换,这可能不安全,因此我们需要确保正确填充internal
。
使用示例:
object UnaryFunctionMap {
def main(args: Array[String]) {
val neg: Int => Int = i => -i
val rev: String => String = s => s.reverse
val map = new UnaryFunctionMap
map += ("neg", neg)
map += ("rev", rev)
println(map.get[Int]("neg").map(_.apply(-45))) // Some(45)
println(map.get[String]("neg").map(_.apply("reverto"))) // None
println(map.get[Int]("rev").map(_.apply(-45))) // None
println(map.get[String]("rev").map(_.apply("reverto"))) // Some(otrever)
}
}
请注意,对于任意类型的A => A
,我假设函数为A
。如果您希望任意A => B
和A
的函数为B
,则需要将第二种类型添加到地图中并相应地更新这两种方法。
答案 1 :(得分:0)
听起来你想要的是类型类。特别是对于数字,您实际上并不需要Scala已经提供的自定义类型类。如果你不想要所有类型之间的共享行为,你只需要扩充。
object Test {
// In this case you can probably even specialize to avoid unboxing.
implicit class NumericOps[T : Numeric](val obj: T) extends AnyVal {
def whatever(x: T): T = obj.add(x) // for instance
}
}
然后对于所有数字,你可以免费获得扩充:
import Test._
5.whatever(6)// = 11
对字符串重复相同的模式,这是一个普通的pimp我的库模式。如果要在一堆不相关的类型上实现相同的操作,请查看类型类。