【问题标题】:Scala - Map of functions with generic typesScala - 具有泛型类型的函数映射
【发布时间】:2015-07-14 13:13:42
【问题描述】:

如何创建字符串映射到具有泛型类型的函数?例如,我希望能够在编译时创建地图:

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 只允许 Strings。当我使用地图时,我希望能够在必要时指定类型。像这样的:

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

或者,如果这不可能,

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

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

【问题讨论】:

  • 你想用这个做什么?这种Any => Any 映射通常是Scala 中要避免的反模式。了解您的用例可能有助于回答者确定在此处使用的更好的设计模式。
  • 我想创建一个函数名到函数的映射。这些函数可以是String => StringInt => StringDouble => Double等,所以我使用了Any => 。这些函数通常也是T: Numeric => T,所以我希望能够在运行时传入T是什么类型。
  • 这很清楚。我正在尝试更好地了解您想将此地图用于什么目的。
  • 我正在使用解析器将用户的表达式解析为函数和操作数。该映射包含该语言中的所有有效函数。
  • 我不确定这是否是一个更好的问题,但更具体地说,这是我目前正在努力实现的目标:stackoverflow.com/questions/31395488/…

标签: scala


【解决方案1】:

将函数存储为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。如果您希望函数为任意ABA => B,则需要将第二种类型添加到地图并相应地更新这两种方法。

【讨论】:

    【解决方案2】:

    听起来你想要的是类型类。特别是对于数字,您实际上不需要自定义类型类,因为 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
    

    只需对字符串重复相同的模式,这是我的库模式的正常皮条客。如果您想对一堆不相关的类型实现相同的操作,请查看类型类。

    【讨论】:

    • 我不确定我是否理解您的建议。如何将其实现到我的功能映射中?我是否需要这样做:``` functionMap += ("Neg" -> (value => value.abs()) )? When I try this, I get a compilation error because abs`` 不是Any 的成员。跨度>
    猜你喜欢
    • 2015-07-09
    • 2020-03-27
    • 2012-05-12
    • 2012-09-23
    • 1970-01-01
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    • 2018-10-23
    相关资源
    最近更新 更多