【问题标题】:Using Scala match case with lists使用带有列表的 Scala 匹配案例
【发布时间】:2020-02-13 04:18:45
【问题描述】:

我想通过列表使用match case 语句。这是我的用例:

val test: String = "test3"
val option: String = getMyOption(test) match {
  case Some(o: String) => o
  case None => ""
}
test match {
  case `test1` | `test2` => doSomething(test, option, param1)
  case `test3` | `test4` | `test5` => doSomethingElse(test, param1, param2)
  case `test6` => doMyJob()
  case `test7` => doMyJob(param2, param3)
  case other =>
    throw getUnsupportedTestOperation(other)
}

我尝试了以下方法,但似乎在匹配之前对函数进行了评估,因为我从 doSomething(test, option) 函数中获得了 NullPointerException(在 test3 案例中不应该调用它)。

def matchList[T](string: String, cases: => Map[Array[String], T], exception: => Exception): T = {
  val matched: Option[T] = cases collectFirst {
    case (list, matching) if list.contains(string) => matching
  }
  if(matched.isDefined) matched.get else throw exception
}

val mapping1: Array[String] = Array("test1", "test2")
val mapping2: Array[String] = Array("test3", "test4", "test5")
val mapping3: Array[String] = Array("test6")
val mapping4: Array[String] = Array("test7")
val option: String = getMyOption(test).getOrElse("undefined")
val testMapping: Map[Array[String], Unit] = Map(
  mapping1 -> doSomething(test, option, param1),
  mapping2 -> doSomethingElse(test, param1, param2),
  mapping3 -> doMyJob(),
  mapping4 -> doMyJob(param2, param3)
)
matchList(test, testMapping, getUnsupportedTestOperation(other))

在这种情况下,我传递 Unit 之类的回调函数,但我也想使用其他类型(例如:StringArray[String]..)。我想念什么?如何避免在匹配之前评估 Map 内容?是否可以在match case 语句中使用列表?还是有更简单的方法来实现这一目标? (我想避免在match case 中包含if 语句。)

编辑

我的结局

def matchList[T](matchCase: String, matchClause: => ListMap[List[String], T], exception: => Exception): T = {
  val matched: Option[T] = matchClause.find(_._1.contains(matchCase)).map { case (_, output) => output }
  if(matched.isDefined) matched.get else throw exception
}

用法

def testMatchList(input: String, option: String, exception: Exception): Unit = {
  val testMapping: ListMap[List[String], () => Unit] = ListMap(
    mapping1 -> match1(input),
    mapping2 -> match2(input, option),
    mapping3 -> match3(),
    mapping4 -> match4(option)
  ) //  where matching cases signatures looks like => def match1(...): () => Unit = () => ...
  val method = scalaHelper.matchList(input, testMapping, exception)
  method()
}

注意:我还不知道如何从 testMatchList 定义中提取 testMapping...

【问题讨论】:

  • 使用.get,.orNull 充其量不是惯用的,可能是代码异味

标签: scala generics callback pattern-matching


【解决方案1】:

您的所有doXXX 调用都将在创建testMapping 时发生,除非您实际上将它们设为() => () 类型的函数值。

这里有一个简单的方法来实现你想要的:

import scala.collection.immutable.ListMap

class A
class B1 extends A
class B2 extends A

def do1(value: String): A = new B1
def do2(value: String, anotherValue: String): B2 = new B2
def do3(): A = new A

val matchClause = ListMap(
  Set("test1", "test2") -> do1 _,
  Set("test3") -> (do2(_: String, "your other value"))
   Set("test4") -> (_: String) => do3()
)

val test = "test1"
val a: Option[A] = matchClause.find(_._1.contains(test)).map { case (_, method) => method(test) }

注意使用ListMap 而不是Map。必须确保按照声明子句的顺序执行重叠的情况,就像正常的模式匹配一​​样。

当然,这一切都非常难看,除非您的原始模式匹配真的无法阅读,否则我建议您不要这样做。将我的答案与您的第一个代码块进行比较:简单的match更清晰,并且也很可能更高效

【讨论】:

  • 我正在寻找基于列表的解决方案,因为这种模式匹配会迅速增长,我希望能够通过将新案例添加到列表中来更新它。我同意您的效率问题,但我不同意可读性和可维护性。另外,我并不担心ListMap,因为在我的用例中最多会有一场比赛。后者也是我使用collectFirst方法的原因。那么,我怎样才能让Unit 方法变成() => ()
  • @belgacea 我的回答满足您的需要,您只需在声明子句时使用do1 _ 而不是实际调用do1(test)。这有帮助吗?
  • 谢谢,但是函数不带任何参数的情况呢?对于它确实接受参数但不是test 的情况也是如此。我的所有功能都不需要它。有没有办法用参数声明一个函数调用,然后使用已经给定的参数按值调用它?也许我应该将我的函数签名从 def doMyJob(...): Unit = { ... } 更改为类似 def doMyJob(...): Unit = () => { ... } 的东西?
  • 这个问题是你不能在Mapˋ, because the values would have type ˋAny 中存储具有不同arities(参数数量)的函数。你能做的就是无论如何都取一个参数,然后忽略它。我将编辑我的答案以匹配您的新示例。
  • 我的答案已被编辑。当然,这仍然仅限于包含 String => Aˋ, so you have to convert () => Aˋ 的映射,只需丢弃字符串
猜你喜欢
  • 1970-01-01
  • 2015-04-27
  • 2018-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 2017-04-25
相关资源
最近更新 更多