Scala - 具有泛型类型的函数的映射

时间:2015-07-13 22:23:11

标签: scala

如何使用泛型类型为函数创建字符串映射?例如,我希望能够在编译时创建一个映射:

var unaryFunctionMap: Map[String, Any => Any] = Map[String, Any => Any]()

然后我想添加可以接受StringIntDouble等类型组合的函数。可能的方式如下:

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
}

因此,absneg都允许Numeric类型,而rev仅允许String s。当我使用地图时,我希望能够在必要时指定输入。像这样:

(unaryFunctionMap.get("abs").get)[Int](-45)

或者,如果不可能,

(unaryFunctionMap.get("abs").get)(Int, -45)

如何修改代码以实现此功能?

2 个答案:

答案 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 => BA的函数为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我的库模式。如果要在一堆不相关的类型上实现相同的操作,请查看类型类。