【问题标题】:Case Classes w/ Option Parameters & Case Matching带选项参数和案例匹配的案例类
【发布时间】:2016-08-17 04:17:40
【问题描述】:

我有一个案例类,它有多个参数,其中一些是选项。这是一个简化的例子:

case class Foobar(a: String, b: Option[String], c: Option[CustomClass])

我希望能够匹配 Foobar 的情况,其中 b 和/或 c 不是无。例如,一种情况可能是:

testResult match {
    case Foobar("str1", Some(_), None) => "good"
    case Foobar("str2", None, Some(_)) => "ok"
    case _ => "bad"
}

此外,我想通过变量引用案例模式,这就是我遇到的问题。我想做如下的事情:

val goodPat = Foobar("str1", Some(_), None) // compile fail
val okPat = Foobar("str2", None, Some(_)) // compile fail
testResult match {
    case `goodPat` => "good"
    case `okPat` => "ok"
    case _ => "bad"
}

这样的事情可能吗?还有另一种方法来指定“非无”吗?有没有其他方法可以解决这个问题?

编辑:我正在为问题添加更多细节和上下文。我有一个大的 2 元组列表,代表特定函数的单元测试。 2 元组表示输入和预期输出。例如。

// imagine this list is much bigger and Foobar contains more Option parameters
val tests = List(
    ("test1", Foobar("idkfa", None, None)),
    // I know these fail to compile but I need to do something like this
    ("test2", Foobar("idclip", Some("baz"), Some(_)),
    ("test3", Foobar("iddqd", Some(_), None)
)

tests.foreach(test => {
    val (input, expected) = test
    myFunction(input) match {
        case `expected` => println("ok")
        case _ => println("bad")
    }
})

【问题讨论】:

  • 为什么要匹配case类中的变量名?我认为没有必要。您的第一种方法有什么问题?
  • 我没有匹配变量名。我正在尝试将模式定义移到匹配块之外,因为实际代码要复杂得多,我需要以编程方式分配模式。
  • val a = Foo(x) 是用于创建类的语法。你不能在这里使用像_ 这样的通配符模式匹配器。
  • 好的,所以你有一长串测试名称和预期结果的元组。你是说对于某些测试你不关心 b 参数的字符串值是什么?而对于许多其他人来说,您关心 b 参数的字符串值到底是什么?
  • @Samar 是的,这是正确的:有时确切的 b 参数并不重要,我只需要知道它不是 None

标签: scala


【解决方案1】:

我认为您正在寻找这样的东西:

case class DoomOpt(s: String)
case class Foobar(a: String, b: Option[String], c: Option[DoomOpt])

def myFunction(s: String): Foobar = { // your code here }

val tests = Map[String, PartialFunction[Foobar, Unit]](
    "idkfa" → { case Foobar("idkfa", None, None) ⇒ },
    "test2" → { case Foobar("idclip", Some("baz"), Some(_)) ⇒ },
    "test3" → { case Foobar("iddqd", Some(_), None) ⇒ },
    "idspispopd" → { case Foobar("idspispopd", Some(_), None) ⇒ }
  )

tests.foreach { case (input, checker) =>
  if (checker.isDefinedAt(myFunction(input)))
    println("ok")
  else
    println("bad")
}

【讨论】:

  • 我认为这是我需要的,但我试图了解它是如何工作的。具体来说,myFunction 被定义为Foobar(s, None, None) 的一个实例,所以这是否意味着checker.isDefinedAt(myFunction(input)) 只有在最后两个选项为无时才为真?编辑:哦,那只是一个例子。我想我现在明白了。
  • 在这种情况下是的。但是您的函数返回取决于“输入”的 Foobar 类的动态结果。它可以是 Foobar 类的任何实例。
  • 是的,我现在明白了。谢谢。我仍在学习 Scala,但函数中没有“return”语句仍然时常让我感到厌烦。
【解决方案2】:

模式匹配使用提供 unapply 函数来解构对象的提取器。所以...在这种情况下,您可以只提供您的自定义提取器。创建这些提取器测试用例的列表并一一应用。

case class Foobar(s: String, o: Option[Int])

trait TestExtractor {
  def unapply(fbar: Foobar): Boolean
}

object somePatExtractor extends TestExtractor {
  def unapply(fbar: Foobar): Boolean = fbar match {
    case Foobar("yes", Some(_)) => true
    case _ => false
  }
}

object nonePatExtractor extends TestExtractor {
  def unapply(fbar: Foobar): Boolean = fbar match {
    case Foobar("yes", None) => true
    case _ => false
  }
}

object bazPatExtractor extends TestExtractor {
  def unapply(fbar: Foobar): Boolean = fbar match {
    case Foobar("yes", Some("baz")) => true
    case _ => false
  }
}


val testList: List[(String, TestExtractor)] = List(("test1", nonePatExtractor), ("test2", bazPatExtractor), ("test3", somePatExtractor))

val fooToTest = Foobar("yes", Some(5))

testList.foreach({
  case (testName, extractor) => {
    fooToTest match {
      case pat @ extractor() => println("testName :: " + testName + ", Result :: ok")
      case _ => println("testName :: " + testName + ", Result :: bad")
    }
  }
})

如果您正在寻找一种更可扩展的方法,那么您可以考虑类似以下的方法,

case class Foobar(s: String, o1: Option[Int], o2: Option[String])

case class TestCondition(df: Foobar => Boolean) {
  def test(foobar: Foobar): Boolean = df(foobar)
}

val o1IsNone = TestCondition(f => f.o1.isEmpty)
val o1IsSome = TestCondition(f => f.o1.isDefined)

val o2IsNone = TestCondition(f => f.o2.isEmpty)
val o2IsSome = TestCondition(f => f.o2.isDefined)

case class TestCase(tcs: List[TestCondition]) {
  def test(foobar: Foobar) = tcs.foldLeft(true)({ case (acc, tc) => acc && tc.test(foobar) })
}

val testList = List[(String, TestCase)](
  ("test1", TestCase(List(o1IsSome, o2IsSome))),
  ("test2", TestCase(List(o1IsSome, o2IsNone))),
  ("test3", TestCase(List(o1IsNone, o2IsSome))),
  ("test4", TestCase(List(o1IsNone, o2IsNone)))
)

val foobarToTest = Foobar("yes", Some(5), None)

testList.foreach({
  case (testName, testCase) => {
    foobarToTest match {
      case foobar: Foobar if testCase.test(foobar) => println("testName :: " + testName + ", Result :: ok")
      case _ => println("testName :: " + testName + ", Result :: bad")
    }
  }
})

【讨论】:

  • 谢谢,这对于具有许多参数和多个选项的案例类来说会变得复杂(即,我需要创建大量的 TestExtractor 对象)。然而,这确实给了我另一个想法。我认为我应该将模式包装在验证函数中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-15
  • 1970-01-01
  • 1970-01-01
  • 2017-02-25
  • 2017-04-25
  • 1970-01-01
相关资源
最近更新 更多