在 Scala 中,如何使用 Option class 重构代码?

In Scala, how to refactor codes with Option class like this?

在文件com.typesafe.play/play_2.11/srcs/play_2.11-2.3.8-sources.jar!/play/api/data/Form.scala中,我看到了这样一个函数的定义:

  protected def addPrefix(prefix: String) = {
    Option(prefix).filterNot(_.isEmpty).map(p => p + Option(key).filterNot(_.isEmpty).map("." + _).getOrElse(""))
  }

我认为这些代码可能需要几秒钟才能理解,并且可以改进以使其更加清晰。有没有人知道什么是更好的重写方法?

一种检查prefix是否为空字符串的方法,

Option(prefix).filterNot(_.isEmpty)

如果不是,附加一个(非空字符串)key,否则return一个空字符串。有很多方法可以在几行中重写它,尽管不一定如此简洁,例如,

protected def addPrefix(prefix: String) = {
  (prefix,key) match {
    case ("",_) => ""
    case (p,"") => p
    case (p,k)  => p + "." + k
  }
}

注意原始代码处理空字符串,

Option(null: String).filterNot(_.isEmpty)
res5: Option[String] = None

而此处建议的代码需要额外检查,例如

def getStr(s: String) = Option(s).filterNot(_.isEmpty).getOrElse("")

因此将上述情况与 (getStr(prefix), getStr(key)) 匹配如下,

protected def addPrefix(prefix: String) = {
  (getStr(prefix), getStr(key)) match {
    case ("",_) => ""
    case (p,"") => p
    case (p,k)  => p + "." + k
  }
}

原始代码简洁但可读,最重要的是不需要额外详细说明处理空值或涵盖所有可能的匹配情况,即语义定义明确。

一种可能提高可读性的方法是定义一个函数,例如 getStr(s: String)(此处建议),以缩短本来很长的单行代码。

Justin 建议使用 for comprehensions 证明是一个可靠的替代方案。

你可以试着把它变成 for-comprehension?

def addPrefix(prefix: String) =
  for{
    safePrefix <- Option(prefix)
    if !safePrefix.isEmpty
    safeKey <- (for{
      innerKey <- Option(key)
      if !innerKey.isEmpty
    } yield s".$innerKey").orElse(Option("")) 
  } yield s"$safePrefix$safeKey" 

但是,由于需要orElse,所以内部for比较乱。所以,这是一个选择,有些人确实考虑更好地理解......可能取决于个人喜好。实际上,将内部 for 包装在一个方法中会起作用,这会增加可读性。

我对 elm 使用匹配的方法很感兴趣,即使你采用相同的方法,由于内部条件,它看起来仍然很乱:

def addPrefix(prefix: String) =  
  Option(prefix) match {
    case Some(prefix) if !prefix.isEmpty => Option(prefix + 
      (Option(key) match{
        case Some(key) if !key.isEmpty => s".$key"
        case _ => ""
      }))
    case _ => None
    }