views:

611

answers:

5

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

+4  A: 

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
That looks pretty simple, I think I tried that in Scala 2.7 and it didn't compile, I will try again.
Alex Black
I just checked: it works perfectly with Scala 2.7.5
paradigmatic
+4  A: 

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
Regular Expressions as cases are used in the same way.
retronym
Thanks Mitch, I did in fact try that, and I think it failed because I didn't have my val capitalized.. interesting.
Alex Black
+11  A: 

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
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".
Alex Black
Jim Barrows
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.
Alex Black
+2  A: 

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

Daniel
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.
Alex Black
@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
filter the list first. i think Daniel's solution is the most strait forward one.
IttayD
@Thomas: good call.
Alex Black
@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
@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
+5  A: 

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.

Thomas Jung
That looks like it would work well Thomas!
Alex Black
Man, I love Scala. It couldn't be more readable, and it's a *function*. And an one-liner, at that.
Daniel