所以大家,我一直试图将一些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提供的全部功能吗?感谢。
答案 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("/"))
}
我不得不重新格式化代码片段以避免滚动。