Search code examples
scalapattern-matchingextractor

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 :)


Solution

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