开发者

Problem with Scala matching + scope

Given the following code:

case class ChangeSet(field:String, from:Object, to:Object)

private var changed:List[ChangeSet] = Nil

def change(field:String, from:Object, to:Object) {
  changed.find{ case ChangeSet(field,_,_) => true } match {
    case Some(C开发者_开发百科hangeSet(field,to,_)) => // do stuff
    case Some(_) => // do stuff
    case _ => // do stuff
  }
}

The line giving me trouble is Some(ChangeSet(field,to,_)).

It compiles but what seems to be happening is that Scala is filling it in as a placeholder for a wildcard. I base that assumption on the fact that when I do the following Some(ChangeSet(field,to,to)) I get the error to is already defined as value.

What I wanted is to make a ChangeSet object with to from the method parameters.

Is that possible?


When pattern matching Scala interprets all identifiers starting with lower case as placeholders and fills in values. To tell Scala to use to as a constant value from the outer scope you need to surround it with backticks: `to`. Alternatively, you could change the name of to to To as Rex Kerr suggested, but I prefer to keep my variables lowercase.

This should work:

def change(field:String, from:Object, to:Object) {
  changed.find{ case ChangeSet(`field`,_,_) => true } match {
    case Some(ChangeSet(`field`, `to`, _)) => // do stuff
    case Some(_) => // do stuff
    case _ => // do stuff
  }
}


It seems there are two confusions here. The first one is the one identified by Rex and Kim. You can read this section from Programming in Scala for more information. It boils down to:

x match { case Some(foo) => } // variable pattern, defines and binds variable foo
x match { case Some(Foo) => } // constant pattern, based on val Foo
x match { case Some(`foo`) => } // constant pattern for lowercase val

You can also use a guard to constraint the match

x match { case Some(foo) if condition => }

The second confusion is that you want to "make a ChangeSet object with to from the method parameters". If I understand you correctly you are trying to construct an object using the case class syntax:

ChangeSet(field, from, to)

This does not work on that side of pattern matching. What happens on the case side of the pattern match can actually be seen as the reverse of the construction of ChangeSet. match { case ChangeSet(field, from, to) => } sort of deconstructs your ChangeSet object and assigns its parts to the field, from and to vals. This is also true when it's composed like this: Some(ChangeSet(field, from, to)), it first deconstructs Some and then ChangeSet. You can see that working on value definitions as it is leveraging the same deconstruction mechanism:

scala> val cset = ChangeSet("a", "from", "to")
cset: ChangeSet = ChangeSet(a,from,to)

scala> val Some(ChangeSet(s, o1, o2)) = Some(cset)
s: String = a
o1: java.lang.Object = from
o2: java.lang.Object = to

It seems what you want to do is to construct a new object that copies the value of ChangeSet but replacing a single field. Case classes supports this with copy, continuing with my REPL example:

scala> val cset2 = cset.copy(from = o2)
cset2: ChangeSet = ChangeSet(a,to,to)

With that in mind, here is another suggestion for change:

def change(field:String, from:Object, to:Object) {
  changed.find(_.field == field) match {
    case Some(cset) =>
      val csetnew = cset.copy(from = to)
      // do stuff with csetnew
    case None =>
      // do stuff
  }
}


You can't use variable names in patterns like that. You have to first match it to a new variable and then do an explicit comparison.

def change(field:String, from:Object, to:Object) {
  changed.find{ case ChangeSet(f,_,_) => field == f } match {
    case Some(ChangeSet(f,too,_)) if f == field && to == too => // do stuff
    case Some(_) => // do stuff
    case _ => // do stuff
  }
}


If you use a lower-case name in a pattern match, Scala will fill in the value. If you want to only match if you have that value, you need to use an upper-case name. Leaving aside the logic of what you're trying to do, and the questionable change in ordering of names, you want:

def change(Field: String, from:Object, To: Object) {
  changed.find{
    case ChangeSet(Field,_,_) => true
    case _ => false   // You need this line!  No match is an exception, not false!
  } match {
    case Some(ChangeSet(Field,To,_)) => // do stuff
    case Some(_) => // do stuff
    case _ => // do stuff
  }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜