2013-02-02 91 views
2

假设我有一个函数列表像这样:与功能匹配值基于类型

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

(其中func1,等人在别处定义。)

我想写一个方法是将取值并根据确切类型将其匹配到正确的功能(匹配a: Afunc1: A => T),或者在没有匹配功能时抛出异常。

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

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

谢谢。

回答

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) 
     ... 
+0

非常酷。感谢您提供使用Manifests的具体示例! – aaronlevin

+0

这对于Scala 2.9和2.10都适用吗? – aaronlevin

+1

是的。尽管你现在应该(在2.10中)赞成在'Manifest'上使用'TypeTag',但它确实有效。 –

2

我认为你误解了List是如何输入的。 List需要单个类型的参数,这是的全部类型列表中的元素。当您编写时

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

编译器会推断出类似funcList : List[A with B with C => 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?

+0

我可以让你的建议,是因为职能的顺序和数量取决于实现服务的客户。不过,您先前的笔记非常有帮助,谢谢。 – aaronlevin

3

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

围绕类型擦除有办法,你会发现堆栈溢出本身的大量讨论。他们中的一些人将价值类型信息存储在某个地方,这样你就可以匹配。

另一种可能的解决方案是简单地放弃使用参数化类型(用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没有类型参数,因此您可以匹配它,并且可以轻松创建此类型的函数。说这两个ATInt,那么你就可以做到这一点,例如:

F1(_ * 2) 
+0

谢谢丹尼尔。很有趣的解决方案另外:我喜欢你的博客。 :) – aaronlevin