如何在 Scala 映射中优雅地处理 "Key not found"?

How to gracefully handle "Key not found" In a Scala map?

总而言之,如果在 Map 中找到一个值,我需要一种功能性方法做一件事,如果在 Map 中找到值,做另一件事没有找到。 请注意,我对返回的值不感兴趣,但对完成的操作感兴趣。

继续阅读了解详情。

我有一个服务名称(友好)到 URL 路径(不容易记住)的一部分的映射。 这里是初始化。

val serviceMap = Map("read" -> "cryptic-read-path",
  "save" -> "cryptic-save-path", "county" -> "cryptic-zip-code-to-county-service.");

我所知道的所有替代方案都会导致带有已加载 then 部分的 if 语句,但只是一个简单的 404 else 部分。 这是我想到的一些

if (serviceMap.contains(service)) {
    //Do stuff
} else {
    //Issue a 404
}

等价于predicate reversed

if (!serviceMap.contains(key)) //issue a 404
//Do stuff

以上两种方法都需要先检查后获取。

另一个(被Option documentation劝阻)

  serviceMap.get(service) match {
  case _ : Option[String]=> //Do stuff
  case _  => //issue 404
}

还有第三个

  serviceMap.get(service).forEach {//Dostuff and return, since it's just one element }
  //issue 404 if you are here.

Per Option documentation,我被鼓励将 Option 视为一个集合,所以最后一个选择似乎更好,但是使用 forEach 似乎很奇怪改变执行流程之后循环。

这是我的经验不足,但所有这些在我看来都太冗长且不合适。你们能给我一个更好的选择或对这些方法发表评论吗? 感谢期待!

几个更清楚的选项:

  1. matchunapply(避免了 _ : Option[CantTellWhatThisIsAtRuntime] 的类型擦除问题):

    serviceMap.get(service) match {
      case Some(path) => // Do stuff and return
      case _ => // 404
    }
    
  2. 使用 fold 提取值(假设您可以正确键入 404):

    serviceMap.get(service).fold(/* 404 */) { path =>
      // Do stuff and return
    }
    
  3. 使用map + getOrElse将路径转换为响应或return/抛出404():

    serviceMap.get(service).map { path =>
      // Do stuff and return
    } getOrElse {
      // 404
    }
    
  4. 使用 MapgetOrElse 方法(假设您抛出 404):

    val path = serviceMap.getOrElse(service, throw Error404)
    // Do stuff and return
    

我认为惯用的方法是使用 mapgetOrElse:

val result = serviceMap.get(service).map { url =>
  // do something with url
} getOrElse {
  // not found
}

一般而言,不要将 Option 视为单元素集合以 循环 (这不是函数式方法),而是 转换 成别的东西。

您的目标是根据值是否存在执行不同的操作。虽然模式匹配是一种将 Option 转换为值的不那么惯用的方法,但我认为它完全适合作为在多个操作之间进行选择的分支机制。出于这个原因,我建议使用您提出的 match 替代方案。

但是,如果您不同意它是否合适,请考虑将您的问题视为将 Option 转换为操作的问题之一:

 val response = serviceMap.get(service).map { value =>
   // do stuff to convert into a 200 response
 }.getOrElse {
   // create 404 response
 }

map 将任何 Some[String] 转换为 Some[Response],同时保持 None。现在您有了 Option[Response],您可以使用 getOrElse 来替换您的 404 响应,以替代 None.

的可能性

就我个人而言,我会使用模式匹配来完成。它使它非常可读。

serviceMap.get(service) match {
  case Some(s) => println("Here's my string from the map! " + s)
  case _ => //issue 404
}

此外,代码可以很容易地修改。例如,如果在将来的某个时候你需要专门针对映射到 "cryptic-read-path" 的值做一些事情,你可以这样做:

serviceMap.get(service) match {
  case Some("cryptic-read-path") => //do stuff that is specific for cryptic read path
  case Some(s) => println("Here's my string from the map! " + s)
  case _ => //issue 404
}

对于第三个选项,您建议在 foreach 中 returning。这将导致非本地 return,效率不高。 阅读更多相关信息 here

编辑: 如果您不需要 get 调用的结果,您也可以这样做:

serviceMap.get(service) match {
  case Some(_) => //do stuff
  case _ => //issue 404
}

Map 提升为仅为映射键定义的部分函数,​​例如,对于给定的映射

val a = Map( 1->2, 3->4)

我们可以用

提升a
a.lift
Int => Option[Int] = <function1>

等等

a.lift(1)
Option[Int] = Some(2)

a.lift(0)
Option[Int] = None

然后考虑像这样的理解,

val ok = for { v <- a.lift(1); res <- Some(awesome_stuff(v)) }
         yield res

ok getOrElse report404

如果vres不成功(即None),则okNone