10

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

tshepang
  • 12,111
  • 21
  • 91
  • 136
waterlooalex
  • 13,642
  • 16
  • 78
  • 99
  • Do you have a use case showing why you want to do this? Perhaps a better solution exists using `Either` – Kevin Wright Jan 11 '10 at 08:46
  • Use case - mostly I just want to avoid writing the two cases e.g. (some, none) and (none, some). My general use case is I'm comparing two products on a particular feature, and maybe both products have the feature, or just one of them, or if they both have it, maybe one has a good value for that feature and one a poor value. – waterlooalex Jan 11 '10 at 13:57
  • possible duplicate of [Match multiple cases classes in scala](http://stackoverflow.com/questions/1837754/match-multiple-cases-classes-in-scala) – nawfal May 18 '13 at 11:35

6 Answers6

19

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"
}
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Hi Rex, that looks promising, but it doesn't (yet) extend to other situations, e.g. where OneOf isn't asking for one of them to be defined, but for one of them to say "be an even number" or "have a value > 10". – waterlooalex Jan 11 '10 at 13:41
  • Doesn't: case (Some(x),Some(y)) if ((x>10) && (y isEven))=> "both" work? – Jim Barrows Jan 11 '10 at 16:03
  • I'm not sure I'm following Jim, but: yes, that does work, but I don't think thats what I'm saying. I'd like to conceptually do this: Case(oneisEven) => case(bothAreEven) =>. E.g. whether they are even or not is just a different test than whether or not they are both not None. – waterlooalex Jan 11 '10 at 16:08
7

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.

Community
  • 1
  • 1
Thomas Jung
  • 32,428
  • 9
  • 84
  • 114
6

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>
Eastsun
  • 18,526
  • 6
  • 57
  • 81
4

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.

Mitch Blevins
  • 13,186
  • 3
  • 44
  • 32
3

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

Community
  • 1
  • 1
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • Thanks for the response Daniel, that looks good but only works for the predicate "x => x.isDefined", I couldn't use it for say "x => x %2 == 0" to check if one and only one of the two items was even. – waterlooalex Jan 11 '10 at 13:34
  • 1
    @Alex - You can express it like this: `List(v1, v2) filter (_ %2 == 0) match { case List(value1, value2) => "a" case List(value) => "b" case _ => "c" }` – Thomas Jung Jan 11 '10 at 14:18
  • filter the list first. i think Daniel's solution is the most strait forward one. – IttayD Jan 11 '10 at 14:24
  • @Alex: I suppose with some work you can extract the pattern: `def xMatch(values : List[T], f : T => Boolean, p : List[PartialFunction[T, X]])`. – Thomas Jung Jan 11 '10 at 14:41
  • @Alex: I've been tinkering with it a bit: http://stackoverflow.com/questions/2039715/scala-pattern-matching-when-one-of-two-items-meets-some-condition/2042824#2042824 – Thomas Jung Jan 11 '10 at 15:44
0

Since you already matched against (Some(x), Some(y)), you may match against (None, None) explicitly, and the remaining cases are (Some(x), None) and (None, Some(y)):

def decide [T](v1: Option[T], v2:Option[T]) = (v1, v2) match {
  case (Some (x), Some (y)) => "a"
  case (None, None)         => "c"
  case _                    => "b"
}

val ni : Option [Int] = None 
decide (ni, ni)            // c
decide (Some (4), Some(3)) // a
decide (ni, Some (3))      // b
decide (Some (4), ni)      // b
user unknown
  • 35,537
  • 11
  • 75
  • 121