表达式默认处理的异常处理

时间:2013-07-27 15:19:49

标签: scala exception

所以大家,我一直试图将一些Java代码示例从一本书转换为惯用的Scala,以加强我的Scala学习。我对小东西很满意,但在使用for表达式时无缝地处理异常让我感到难过。

前提是:给定主机名列表,检索主机名/ IP地址元组列表。听起来很简单,适用于良好的情况,即

  def printHostInfo(args: Array[String]) {
    val tuples = for {
      arg <- args
      inet <- InetAddress.getAllByName(arg.trim)
    } yield (inet.getHostName, inet.getHostAddress)
    println(tuples mkString "; ")
  }

但现在出现了困难的部分:我想轻松处理输入错误主机名时发生的异常。我可以使用新的Try构造,但它只是躲过了这个问题。

def printHostInfo(args: Array[String]) {
    val tuples = for {
      arg <- args
      inet <- Try(InetAddress.getAllByName(arg.trim)) getOrElse Array()
    } yield (inet.getHostName, inet.getHostAddress)
    println(tuples mkString "; ")
  }

在上面的代码片段中,如果主机名不好,则跳过该条目,我们都很高兴。 但是,我想做的是在主机不好的情况下,检索像(www.hostname.com, Bad host name)这样的元组。我试着弄乱Option和其他东西,但我得到编译时错误,我还没有资格破译。有人可以建议一个简洁的惯用解决方案并使用Scala提供的全部功能吗?感谢。

3 个答案:

答案 0 :(得分:3)

我最初的意思是这样结束:

def printHostInfo(args: Array[String]) = {
    val tuples = for {
      arg <- args
    } yield Try[Seq[(String,String)]](InetAddress.getAllByName(arg.trim)
        .map(inet => (inet.getHostName, inet.getHostAddress))) getOrElse List((arg.trim,"Bad host name"))
    println(tuples.flatten mkString ";")
}

这不是优雅的代码。

这是一个“功能性”重新设计,保留Try的使用:

def printHostInfo1(args: Seq[String]) = {
  def hostToTuple(inet: InetAddress) = (inet.getHostName, inet.getHostAddress)

  val hosts = args.flatMap(arg =>
                Try(InetAddress.getAllByName(arg.trim).map(hostToTuple(_)))
                                getOrElse Array((arg.trim,"Bad host name")))

  println(hosts mkString ";")
}

一般来说,我不知道我现在是否清楚地解释了这个问题,但我的观点是你应该尽可能地将异常处理推迟到“尽可能远”。在我看来,你最终遇到的问题是你过早地处理了异常,现在你仍然坚持使用类型系统阻碍而不是帮助你(注意:这种事情不会成为一个问题。动态类型的语言,例如Python)。

从这个角度来看,这是一个简单的迭代替代方案:

def printHostInfo3(args: Array[String]) {
    val tuples = for(arg <- args)
        yield try {
            for(inet <- InetAddress.getAllByName(arg.trim))
                yield (inet.getHostName, inet.getHostAddress)
        } catch  {
            case e: Exception => Array((arg.trim, "Bad host name"))
        }

    println(tuples.flatten mkString ";")
}

答案 1 :(得分:1)

经过一番思考之后,我终于设法将其归结为一个有效的解决方案,尽管我们非常感谢改进:

  def printHostInfo(args: Array[String]) {
    val tuples = for {
      arg <- args
      inet <- Try(InetAddress.getAllByName(arg.trim) map
                      (Some(_))) getOrElse Array(None)
    } yield (inet.map(_.getHostName) getOrElse arg,
      inet.map(_.getHostAddress) getOrElse "Host Not Found")
    println(tuples mkString "\n")
  }

答案 2 :(得分:1)

您可以定义一个返回Either的方法:

def getAllInetAddressesByName(h: String): Either[Exception, List[InetAddress]] ={
  try {
    Right(InetAddress.getAllByName(h).toList)
  } catch {
    case e: UnknownHostException => Left(e)
  }
}

返回异常或地址。此方法也可以从可变Array转换为不可变List。它是从Java API(使用异常/数组)到功能数据类型的桥梁。

使用此模式(使用Either),您在映射或理解期间不需要Try


因此,除了这种通用方法之外,您还可以map使用各种结果类型,例如:

val hosts = List("stackoverflow.com", "sdfsdf.sdf", "google.com")

val result = hosts.
  map(host => (host, getAllInetAddressesByName(host))).
  map {
    case (host, Right(addresses)) =>
      (host, addresses.map(a => a.getHostAddress).mkString("/"))
    case (host, Left(ex)) =>
      (host, s"Host $host not found")
  }

或使用for

val result = for {
  host <- hosts
} yield {
  (host,
    getAllInetAddressesByName(host).fold(
      ex => s"Host $host not found",
      addresses => addresses.map(a => a.getHostAddress).mkString("/")))
}

另一个例子:如果你想collect只有好的:

val result = hosts.map(h => (h, getAllInetAddressesByName(h))).collect {
  case (h, Right(addresses)) => 
    (h, addresses.map(a => a.getHostAddress).mkString("/"))
}

我不得不重新格式化代码片段以避免滚动。