【问题标题】:Match Value with Function based on Type根据类型将值与函数匹配
【发布时间】:2013-02-02 01:32:18
【问题描述】:

假设我有一个这样的函数列表:

val funcList = List(func1: A => T, func2: B => T, func2: C => T)

(其中func1等在别处定义)

我想编写一个方法,该方法将获取一个值并根据确切类型将其匹配到正确的函数(匹配a: Afunc1: A => T),或者如果没有匹配的函数则抛出异常。

有没有简单的方法来做到这一点?

这类似于 PartialFunction 所做的,但我无法将 funcList 中的函数列表更改为 PartialFunctions。我在想我必须将函数隐式转换为一个特殊的类,该类知道它可以处理的类型并能够对其进行模式匹配(基本上将这些函数提升为专门的PartialFunction)。但是,我不知道如何识别每个函数的“域”。

谢谢。

【问题讨论】:

    标签: scala


    【解决方案1】:

    您无法识别每个函数的域,因为它们在运行时被擦除。如果您想要更多信息,请查找擦除,但缺点是您想要的信息不存在。

    类型擦除有多种方法,您会发现很多关于 Stack Overflow 本身的讨论。其中一些归结为将类型信息作为值存储在某处,以便您可以匹配。

    另一种可能的解决方案是简单地放弃使用参数化类型(Java 用语中的泛型)为您自己的自定义类型。也就是说,做类似的事情:

    abstract class F1 extends (A => T)
    object F1 { 
      def apply(f: A => T): F1 = new F1 { 
        def apply(n: A): T = f(n) 
      } 
    }
    

    等等。由于F1 没有类型参数,你可以在上面匹配,你可以很容易地创建这个类型的函数。假设AT 都是Int,那么你可以这样做,例如:

    F1(_ * 2)
    

    【讨论】:

    • 谢谢丹尼尔。非常有趣的解决方案。另外:我喜欢你的博客。 :)
    【解决方案2】:

    解决类型擦除的通常答案是使用清单的帮助。在您的情况下,您可以执行以下操作:

    abstract class TypedFunc[-A:Manifest,+R:Manifest] extends (A => R) {
      val retType: Manifest[_] = manifest[R]
      val argType: Manifest[_] = manifest[A]
    }
    object TypedFunc {
      implicit def apply[A:Manifest, R:Manifest]( f: A => R ): TypedFunc[A, R] = {
        f match {
          case tf: TypedFunc[A, R]  => tf
          case _ => new TypedFunc[A, R] { final def apply( arg: A ): R = f( arg ) }
        }
      }
    }
    
    def applyFunc[A, R, T >: A : Manifest]( funcs: Traversable[TypedFunc[A,R]] )( arg: T ): R = {
      funcs.find{ f => f.argType <:< manifest[T] } match {
        case Some( f ) => f( arg.asInstanceOf[A] )
        case _ => sys.error("Could not find function with argument matching type " + manifest[T])
      }
    }
    
    val func1 = { s: String => s.length }
    val func2 = { l: Long => l.toInt }
    val func3 = { s: Symbol => s.name.length }
    val funcList = List(func1: TypedFunc[String,Int], func2: TypedFunc[Long, Int], func3: TypedFunc[Symbol, Int])
    

    在 REPL 中测试:

    scala> applyFunc( funcList )( 'hello )
    res22: Int = 5
    scala> applyFunc( funcList )( "azerty" )
    res23: Int = 6
    scala> applyFunc( funcList )( 123L )
    res24: Int = 123
    scala> applyFunc( funcList )( 123 )
    java.lang.RuntimeException: Could not find function with argument matching type Int
            at scala.sys.package$.error(package.scala:27)
            at .applyFunc(<console>:27)
            at .<init>(<console>:14)
            ...
    

    【讨论】:

    • 非常酷。感谢您提供使用清单的具体示例!
    • 是的。尽管您现在(在 2.10 中)应该支持 TypeTag 而不是 Manifest,但它确实有效。
    【解决方案3】:

    我认为您误解了 List 的输入方式。 List 采用单个类型参数,它是列表中所有 元素的类型。当你写

    val funcList = List(func1: A => T, func2: B => T, func2: C => T)
    

    编译器会推断出像funcList : List[A with B with C =&gt; T] 这样的类型。

    这意味着funcList 中的每个函数都采用一个参数,该参数是所有 ABC 的成员。

    除此之外,由于类型擦除,您不能(直接)匹配函数类型。

    您可以做的是匹配a 本身,并为该类型调用适当的函数:

    a match {
        case x : A => func1(x)
        case x : B => func2(x)
        case x : C => func3(x)
        case _ => throw new Exception
    }
    

    (当然,ABC 在类型擦除后必须保持不同。)

    如果您需要它是动态的,那么您基本上是在使用反射。不幸的是,Scala 的反射功能在不断变化,几周前发布了 2.10 版本,因此目前的实现方式的文档较少;见How do the new Scala TypeTags improve the (deprecated) Manifests?

    【讨论】:

    • 我可以提出您的建议,因为功能的顺序和数量取决于实现服务的客户端。不过,您之前的笔记确实很有帮助,谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-08
    • 1970-01-01
    • 2018-06-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多