失败的伴随对象应用方法

时间:2014-06-02 12:21:49

标签: scala

Scala类在其随播对象中使用applyunapply方法很常见。

unapply的行为是明确的:如果其参数是或者可以转换为类的有效实例,则返回Some。否则,请返回None

举一个具体的例子,让我们设想一个Url案例类:

object Url {
  def apply(str: String): Url = ??? 
  def unapply(str: String): Option[Url] = ???
}

case class Url(protocol: String, host: String, path: String)

如果str是有效网址,则unapply将返回Some[Url],否则为None

apply对我来说有点不太清楚:它如何应对str不是有效的网址?

来自Java世界,我的第一直觉是抛出IllegalArgumentException,这将允许我们将伴侣对象实现为:

object Url {
  def apply(str: String): Url = ... // some function that parses a URI and throws if it fails.

  def unapply(str: String): Option[Url] = Try(apply(str)).toOption
}

我理解这在功能世界中并不算是非常好的做法(例如,在this answer中解释过)。

另一种方法是让apply返回Option[Url],在这种情况下,它会成为unapply的简单克隆,最好是未实现的。

这是正确的结论吗?这类潜在失败的apply方法是否应该实现?在这种情况下,投掷是否正常?我还没有看到第三种选择吗?

2 个答案:

答案 0 :(得分:7)

这有点主观,但我认为你不应该这样做。

假设您允许apply失败,即抛出异常或返回空选项。然后执行val url = Url(someString)可能会失败,尽管看起来非常像构造函数。这就是整个问题:伴随对象的apply方法应该可靠地为您构造新实例,并且您无法从任意字符串中可靠地构造Url实例。所以不要这样做。

unapply通常应该用于获取有效的Url对象,并返回另一个可以再次创建Url的表示形式。作为一个例子,查看为case类生成的unapply方法,它只返回一个包含构造它的参数的元组。因此签名实际上应该是def unapply(url: Url): String

所以我的结论是都不应该用于构建Url。我认为让方法def parse(str: String): Option[Url]明确你正在做什么(解析字符串)并且它可能会失败是最惯用的。然后,您可以Url.parse(someString).map(url => ...)使用Url实例。

答案 1 :(得分:3)

None的情况下,提取器的用户通常负责决定如何处理失败,并且模式匹配语法使得这非常方便。这是一个稍微简单的例子:

import scala.util.Try

object IntString {
  def unapply(s: String) = Try(s.toInt).toOption
}

现在我们可以写下列任何内容:

def intStringEither(s: String): Either[String, Int] = s match {
  case IntString(i) => Right(i)
  case invalid => Left(invalid)
}

或者:

def intStringOption(s: String): Option[Int] = s match {
  case IntString(i) => Some(i)
  case _ => None
} // ...or equivalently just `= IntString.unapply(s)`.

或者:

def intStringCustomException(s: String): Int = s match {
  case IntString(i) => i
  case invalid => throw MyCustomParseFailure(invalid)
}

这种灵活性是关于提取器的好处之一,如果你在unapply中抛出(非致命)异常,那么就会使这种灵活性短路。