Scala: сопоставление с образцом, когда один из двух предметов удовлетворяет некоторому условию - PullRequest
10 голосов
/ 11 января 2010

Я часто пишу код, который сравнивает два объекта и выдает значение, основанное на том, являются ли они одинаковыми или разными, в зависимости от того, насколько они различны.

Так что я мог бы написать:

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

Эти 2-й и 3-й случаи действительно одинаковы, поэтому я попытался написать:

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

Но не повезло.

Я сталкиваюсь с этой проблемой в нескольких местах, и это только конкретный пример, более общий паттерн - у меня есть две вещи, и я хочу знать, соответствует ли один и только один из них некоторому предикату, поэтому хотел бы написать что-то вроде этого:

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

Таким образом, идея заключается в том, что OneAndOnlyOne можно настроить с помощью предиката (в данном случае isDefined), и вы можете использовать его в нескольких местах.

Выше не работает вообще, так как в обратном направлении, предикат должен быть передан в экстрактор, а не возвращен.

Как насчет чего-то подобного?

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

с:

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
  }
}

Но это не компилируется.

Может кто-нибудь увидеть способ заставить это решение работать? Или предложить другое решение? Я, вероятно, делаю это более сложным, чем это:)

Ответы [ 6 ]

19 голосов
/ 11 января 2010

Я думаю, вы задаете два немного разных вопроса.

Один вопрос - как использовать «или» в выражениях switch. || не работает; | делает. И вы не можете использовать переменные в этом случае (потому что в общем случае они могут совпадать с разными типами, что делает тип запутанным). Итак:

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

Другой вопрос, как избежать необходимости делать это снова и снова, особенно если вы хотите получить значение в кортеже. Я реализовал здесь версию для Option, но вы можете использовать развернутый кортеж и логическое значение.

Одна хитрость для достижения этой цели состоит в том, чтобы предварительно обернуть значения перед тем, как вы начнете сопоставлять их, а затем использовать свои собственные сопоставляющие конструкции, которые делают то, что вы хотите. Например,

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"
}
7 голосов
/ 11 января 2010

Если вам нужно поддерживать произвольные предикаты, вы можете извлечь из этого (что основано на идее Дэниела ):

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

определение функции:

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

Теперь вы можете использовать его так:

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

Я не уверен, хорошая ли это идея (то есть читаемая). Но, тем не менее, аккуратное упражнение.

Было бы неплохо, если бы вы могли сопоставлять кортежи: case (value1, value2) => ... вместо списков.

6 голосов
/ 11 января 2010

Как насчет этого:

    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>
4 голосов
/ 11 января 2010

Вы должны быть в состоянии сделать это, если сначала определите это как val:

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"
}

Как следует из названия, имя val, содержащего объект экстрактора, должно быть написано заглавными буквами.

3 голосов
/ 11 января 2010

На Scala 2.8:

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

В Scala 2.7, однако, вам нужна подсказка типа, чтобы она работала. Итак, если предположить, например, value равно Int, то:

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

Забавно, что я неправильно прочитал «первый» как «список» в ответе Митч Блевинс , и это дало мне эту идею. : -)

0 голосов
/ 16 ноября 2011

Поскольку вы уже сопоставили (Some (x), Some (y)), вы можете явно сопоставить (None, None), а остальные случаи (Some (x), None) и (None, Some ( у)):

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