Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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(ChangeSet(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?

like image 354
npiv Avatar asked May 30 '11 05:05

npiv


People also ask

What is Scala match error?

class MatchError extends RuntimeExceptionThis class implements errors which are thrown whenever an object doesn't match any pattern of a pattern matching expression.

Does Scala have pattern matching?

Scala's pattern matching statement is most useful for matching on algebraic types expressed via case classes. Scala also allows the definition of patterns independently of case classes, using unapply methods in extractor objects.

What is the use of Scala pattern matching?

Pattern matching is a way of checking the given sequence of tokens for the presence of the specific pattern. It is the most widely used feature in Scala. It is a technique for checking a value against a pattern. It is similar to the switch statement of Java and C.

How pattern matching works in a function's parameter list?

Pattern matching tests whether a given value (or sequence of values) has the shape defined by a pattern, and, if it does, binds the variables in the pattern to the corresponding components of the value (or sequence of values). The same variable name may not be bound more than once in a pattern.


2 Answers

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
  }
}
like image 98
kassens Avatar answered Sep 29 '22 02:09

kassens


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
  }
}
like image 20
huynhjl Avatar answered Sep 29 '22 02:09

huynhjl