10

我经常编写代码来比较两个对象并根据它们是否相同或不同产生一个值,具体取决于它们的不同之处。

所以我可能会写:

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

但是,这不会编译。

任何人都可以看到使此解决方案起作用的方法吗?或者提出另一种解决方案?我可能让这比现在更复杂:)

4

6 回答 6

19

我想你问的是两个略有不同的问题。

一个问题是如何在 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"
}
于 2010-01-11T05:04:10.417 回答
7

如果您必须支持任意谓词,您可以从中派生(基于Daniel 的想法):

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) => ...而不是列表,那就太好了。

于 2010-01-11T15:39:39.313 回答
6

这个怎么样:

    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>
于 2010-01-11T03:28:16.487 回答
4

如果您首先将其定义为 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 的名称必须大写。

于 2010-01-11T04:10:51.220 回答
3

在 Scala 2.8 上:

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

然而,在 Scala 2.7 上,您需要一个类型提示才能使其工作。因此,假设valueInt,例如,那么:

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

有趣的是,我在Mitch Blevins的回答中将“第一”误读为“列表”,这给了我这个想法。:-)

于 2010-01-11T11:15:12.320 回答
0

由于你已经匹配了 (Some(x), Some(y)),你可以显式匹配 (None, None),剩下的情况是 (Some(x), None) 和 (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
于 2011-11-15T23:09:35.683 回答