2

所以伙计们,我一直在尝试将一些 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 提供的全部功能的惯用解决方案吗?谢谢。

4

3 回答 3

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 ";")
}
于 2013-07-27T16:16:25.427 回答
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")
  }
于 2013-07-27T15:32:05.390 回答
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稍后在映射期间或在 for 理解中。


因此,在这种通用方法之上,您可以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("/"))
}

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

于 2013-07-27T19:58:32.393 回答