考虑一下这个(非常难看的代码):
object ExternalReferences2 {
import java.util.regex._
implicit def symbol2string(sym: Symbol) = sym.name
object Mapping {
def fromXml(mapping: scala.xml.NodeSeq) = {
new Mapping(mapping \ 'vendor text,
mapping \ 'match text,
mapping \ 'format text)
}
}
case class Mapping(vendor: String,
matches: String,
format: String) extends PartialFunction[String, String] {
private val pattern = Pattern.compile(matches)
private var _currentMatcher: Matcher = null
private def currentMatcher =
{ println("Getting matcher: " + _currentMatcher); _currentMatcher }
private def currentMatcher_=(matcher: Matcher) =
{ println("Setting matcher: " + matcher); _currentMatcher = matcher }
def isDefinedAt(entity: String) =
{ currentMatcher = pattern.matcher(entity); currentMatcher.matches }
def apply(entity: String) = apply
def apply = {
val range = 0 until currentMatcher.groupCount()
val groups = range
map (currentMatcher.group(_))
filterNot (_ == null)
map (_.replace('.', '/'))
format.format(groups: _*)
}
}
val config =
<external-links>
<mapping>
<vendor>OpenJDK</vendor>
<match>{ """^(javax?|sunw?|com.sun|org\.(ietf\.jgss|omg|w3c\.dom|xml\.sax))(\.[^.]+)+$""" }</match>
<format>{ "http://download.oracle.com/javase/7/docs/api/%s.html" }</format>
</mapping>
</external-links>
def getLinkNew(entity: String) =
(config \ 'mapping)
collectFirst({ case m => Mapping.fromXml(m)})
map(_.apply)
def getLinkOld(entity: String) =
(config \ 'mapping).view
map(m => Mapping.fromXml(m))
find(_.isDefinedAt(entity))
map(_.apply)
}我试图通过使用collectFirst来改进getLinkOld方法,如getLinkNew中所示,但我总是得到一个NullPointerException,因为_currentMatcher仍然设置为null
scala> ExternalReferences2.getLinkNew("java.util.Date")
Getting matcher: null
java.lang.NullPointerException
at ExternalReferences2$Mapping.apply(<console>:32)
at ExternalReferences2$$anonfun$getLinkNew$2.apply(<console>:58)
at ExternalReferences2$$anonfun$getLinkNew$2.apply(<console>:58)
at scala.Option.map(Option.scala:131)
at ExternalReferences2$.getLinkNew(<console>:58)
at .<init>(<console>:13)
at .<clinit>(<console>)
at .<init>(<console>:11)
at .<clinit>(<console>)虽然它可以完美地与getLinkOld配合使用。
这里的问题是什么?
发布于 2011-08-18 23:17:21
在isDefined中,您的匹配器是作为副作用创建的。将副作用函数传递给诸如map之类的例程通常会导致灾难,但这并不是这里发生的事情。您的代码要求在调用apply之前调用isDefined,并使用相同的参数。这使得你的代码非常脆弱,这就是你应该改变的。
一般情况下,PartialFunction的客户端不必遵循该协议。例如,想象一下
if (f.isDefinedAt(x) && f.isDefinedAt(y)) {fx = f(x); fy = f(y)}. 这里调用apply的代码甚至不是你的,而是集合类的,所以你不能控制发生的事情。
你在getLinkNew中遇到的具体问题是,isDefined是called.The PartialFunction,collectFirst的参数是{case m => ...}。调用的isDefined是此函数的isDefined。因为m是一个无可辩驳的模式,所以它总是正确的,并且collectFirst将总是返回第一个元素(如果有)。partial函数返回另一个碰巧没有在m中定义的部分函数( Mapping),这是不相关的。
编辑-可能的解决方法
一个非常小的更改是检查matcher是否可用,如果不可用,则创建它。最好也保留用于创建它的entity字符串,这样就可以检查它是否是正确的字符串。只要不存在多线程,这就应该使副作用变得良性。但是顺便说一句,不要使用null,使用Option,所以编译器不会让你忽略它可能是None的可能性。
var _currentMatcher : Option[(String, Matcher)] = None
def currentMatcher(entity: String) : Matcher = _currentMatcher match{
case Some(e,m) if e == entity => m
case _ => {
_currentMatcher = (entity, pattern.matcher(entity))
_currentmatcher._2
}
}再次编辑。愚蠢的我
抱歉,所谓的变通方法确实使类更安全,但它并不能使collectFirst解决方案工作。同样,case m =>分部函数总是被定义的(注意:entity甚至没有出现在您的getLinkNew代码中,这应该是令人担忧的)。问题是需要一个实体的PartialFunction (而不是NodeSeq的实体,它将为函数所知,但不会作为参数传递)。isDefined将被调用,然后应用。模式和匹配器依赖于NodeSeq,所以它们不能预先创建,只能在isDefined和/或apply中创建。同样,您可以缓存在isDefined中计算的内容,以便在Apply中重用。这绝对不是很好
def linkFor(entity: String) = new PartialFunction[NodeSeq, String] {
var _matcher : Option[String, Matcher] = None
def matcher(regexp: String) = _matcher match {
case Some(r, m) where r == regexp => m
case None => {
val pattern = Pattern.compile(regexp)
_matcher = (regexp, pattern.matcher(entity))
_matcher._2
}
}
def isDefined(mapping: NodeSeq) = {
matcher(mapping \ "match" text).matches
}
def apply(mapping: NodeSeq) = {
// call matcher(...), it is likely to reuse previous matcher, build result
}
}您可以将其与(config \ mapping).collectFirst(linkFor(entity))一起使用
https://stackoverflow.com/questions/7100003
复制相似问题