2013-07-27 71 views
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提供的全部功能嗎?謝謝。

+0

爲什麼不把地址分辨率移到'yield'表達式? –

+0

@TheTerribleSwiftTomato:不知道我明白,也許你可以發表一個例子? – sasuke

+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並被接受。 – sasuke

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") 
    } 
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("/")) 
} 

我不得不重新格式化代碼片段以避免滾動。

+0

+1爲一種新的思維方式。 – sasuke