Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Pattern matching when one of two items meets some condition

I'm often writing code that compares two objects and produces a value based on whether they are the same, or different, based on how they are different.

So I might write:

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case (Some(value), None)) => "b"
  case (None, Some(value)) => "b"
  case _ = > "c"
}

Those 2nd and 3rd cases are the same really, so I tried writing:

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case (Some(value), None)) || (None, Some(value)) => "b"
  case _ = > "c"
}

But no luck.

I encounter this problem in a few places, and this is just a specific example, the more general pattern is I have two things, and I want to know if one and only one of them meet some predicate, so I'd like to write something like this:

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case OneAndOnlyOne(value, v: Option[Foo] => v.isDefined ) => "b"
  case _ = > "c"
}

So the idea here is that OneAndOnlyOne can be configured with a predicated (isDefined in this case) and you can use it in multiple places.

The above doesn't work at all, since its backwards, the predicate needs to be passed into the extractor not returned.

How about something like this?

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case new OneAndOnlyOne(v: Option[Foo] => v.isDefined )(value) => "b"
  case _ = > "c"
}

with:

class OneAndOnlyOne[T](predicate: T => Boolean) {
  def unapply( pair: Pair[T,T] ): Option[T] = {
    val (item1,item2) = pair
    val v1 = predicate(item1)
    val v2 = predicate(item2)

    if ( v1 != v2 )
      Some( if ( v1 ) item1 else item2 )
    else
      None
  }
}

But, this doesn't compile.

Can anyone see a way to make this solution work? Or propose another solution? I'm probably making this more complicated than it is :)

like image 673
Alex Black Avatar asked Jan 11 '10 03:01

Alex Black


People also ask

How do you match a pattern in Scala?

A pattern match includes a sequence of alternatives, each starting with the keyword case. Each alternative includes a pattern and one or more expressions, which will be evaluated if the pattern matches. An arrow symbol => separates the pattern from the expressions.

Does Scala have pattern matching?

Notes. 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.

Which method of case class allows using objects in pattern matching?

Case classes are Scala's way to allow pattern matching on objects without requiring a large amount of boilerplate. In the common case, all you need to do is add a single case keyword to each class that you want to be pattern matchable.


5 Answers

I think you're asking two slightly different questions.

One question is how to use "or" in switch statements. || doesn't work; | does. And you can't use variables in that case (because in general they might match different types, which renders the type confusing). So:

def matcher[T](a: (T,T)) = {
  a match {
    case (Some(x),Some(y)) => "both"
    case (Some(_),None) | (None,Some(_)) => "either"
    case _ => "none"
  }
}

Another question is how to avoid having to do this over and over, especially if you want to be able to get at the value in the tuple. I've implemented a version here for Option, but you could use an unwrapped tuple and a boolean.

One trick to achieve this is that to prewrap the values before you start matching on it, and then use your own matching constructs that do what you want. For instance,

class DiOption[+T] {
  def trinary = this
}
case class Both[T](first: T, second:T) extends DiOption[T] { }
case class OneOf[T](it: T) extends DiOption[T] { }
case class Neither() extends DiOption[Nothing] { }
implicit def sometuple2dioption[T](t2: (Option[T],Option[T])): DiOption[T] = {
  t2 match {
    case (Some(x),Some(y)) => Both(x,y)
    case (Some(x),None) => OneOf(x)
    case (None,Some(y)) => OneOf(y)
    case _ => Neither()
  }
}

// Example usage
val a = (Some("This"),None)
a trinary match {
  case Both(s,t) => "Both"
  case OneOf(s) => "Just one"
  case _ => "Nothing"
}
like image 112
Rex Kerr Avatar answered Sep 22 '22 14:09

Rex Kerr


If you have to support arbitrary predicates you can derive from this (which is based on Daniel's idea):

List(v1, v2) filter (_ %2 == 0) match {
    case List(value1, value2) => "a"
    case List(value) => "b"
    case _ => "c"
}

the definition of the function:

def filteredMatch[T,R](values : T*)(f : T => Boolean)(p: PartialFunction[List[T], R]) : R = 
    p(List((values filter f) :_* ))

Now you can use it like this:

filteredMatch(v1,v2)(_ %2 == 0){
    case List(value1, value2) => "a"
    case List(value) => "b"
    case _ => "c"
}

I'm not so sure if it's a good idea (i.e. readable). But a neat exercise nonetheless.

It would be nice if you could match on tuples: case (value1, value2) => ... instead of lists.

like image 24
Thomas Jung Avatar answered Sep 24 '22 14:09

Thomas Jung


How about this:

    Welcome to Scala version 2.8.0.r20327-b20091230020149 (Java HotSpot(TM) Client VM, Java 1.6.0_17).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def m(v1: Any,v2: Any) = (v1,v2) match {
     |     case (Some(x),Some(y)) => "a"
     |     case (Some(_),None) | (None,Some(_)) => "b"
     |     case _ => "c"
     | }
m: (v1: Any,v2: Any)java.lang.String

scala> m(Some(1),Some(2))
res0: java.lang.String = a

scala> m(Some(1),None)
res1: java.lang.String = b

scala> m(None,None)
res2: java.lang.String = c

scala>
like image 39
Eastsun Avatar answered Sep 26 '22 14:09

Eastsun


You should be able to do it if you define it as a val first:

val MyValThatIsCapitalized = new OneAndOnlyOne(v: Option[Foo] => v.isDefined )
val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case MyValThatIsCapitalized(value) => "b"
  case _ = > "c"
}

As implied by the name, the name of the val containing the extractor object must be capitalized.

like image 43
Mitch Blevins Avatar answered Sep 22 '22 14:09

Mitch Blevins


On Scala 2.8:

val result = List(v1,v2).flatten match {
  case List(value1, value2) => "a"
  case List(value) => "b"
  case _ = > "c"
}

On Scala 2.7, however, you need a type hint to make it work. So, assuming value is Int, for instance, then:

val result = (List(v1,v2).flatten : List[Int]) match {
  case List(value1, value2) => "a"
  case List(value) => "b"
  case _ = > "c"
}

The funny thing about it is that I misread "first" as "list" on Mitch Blevins answer, and that gave me this idea. :-)

like image 43
Daniel C. Sobral Avatar answered Sep 26 '22 14:09

Daniel C. Sobral