2012-04-01 114 views
2

編輯
OK,@Drexin帶來了一個好點的重。Scala的隱式轉換的陷阱

如何處理不常見的轉換,與PreDef implicits衝突不會發生?例如,我正在與斯卡拉的JodaTime(偉大的項目!)合作。在我的implicits定義相同的控制器封裝的對象,我有一個類型別名:

type JodaTime = org.joda.time.DateTime 

和隱含的意思轉換JodaTime龍(用於建立在ScalaQuery之上的DAL後者的日期存儲爲長)

implicit def joda2Long(d: JodaTime) = d.getMillis 

此處PreDef和我的控制器包含義之間不存在歧義,並且控制器含義不會過濾到DAL中,因爲它位於不同的包範圍中。所以,當我做

dao.getHeadlines(articleType, Some(jodaDate)) 

隱式轉換爲長爲我做的,IMO,安全,並考慮到基於日期的查詢大量使用,我節省一些樣板。

同樣,對於str2Int轉換,控制器層接收Servlet URI參數爲String - > String。有很多情況下URI包含數字字符串,所以當我篩選一個路由以確定該字符串是否是Int時,我不希望每次都使用stringVal.toInt;相反,如果正則表達式通過,則讓隱式將字符串值轉換爲Int。大家聚在一起會是什麼樣子:

implicit def str2Int(s: String) = s.toInt 
get("""/([0-9]+)""".r) { 
    show(captures(0)) // captures(0) is String 
} 
def show(id: Int) = {...} 

在上述背景下,這些都是有效的用例隱式轉換,或者更,始終是明確的?如果是後者,那麼有什麼有效的隱式轉換用例?

ORIGINAL
包對象我有一些隱式轉換中定義了一個簡單的字符串INT:

implicit def str2Int(s: String) = s.toInt 

一般能正常工作,這需要一個Int PARAM方法,但接收一個字符串,將轉換爲Int,就像返回類型設置爲Int的方法一樣,但實際返回的值是一個String。

大,現在在某些情況下與可怕的曖昧隱式的編譯器錯誤:

兩個方法中類型的對象PREDEF augmentString(X:字串) scala.collection.immutable.StringOps和方法str2Int( S:字符串)詮釋 從java.lang.String中可能的轉換函數{VAL toInt:?}?

的情況下,我知道這是試圖做的時候發生的是手動聯字符串,TO- Int轉換。例如,val i = "10".toInt

我的解決方法/黑客一直在包對象的implicits一起創建asInt中幫手:def asInt(i: Int) = i和作爲,asInt("10")

那麼,是隱含的最佳實踐隱(即通過獲取學習燒傷),還是有一些指引要遵循,以免陷入自己製造的陷阱?換句話說,是否應該避免簡單的常見隱式轉換,並且只能在要轉換的類型是唯一的情況下使用? (即永遠不會打歧義陷阱)

感謝您的反饋,implicits是真棒......當他們工作打算;-)

+0

當您在範圍內進行隱式轉換時,爲什麼要調用''10「.toInt'?你應該使用''10':Int'來讓隱式轉換處理它。 (但我同意drexin關於這一般不是一個好的隱含的範圍。) – 2012-04-01 13:22:11

+0

我不叫「10」.toInt,就是一個例子。我被咬的地方是新的JodaTime()。toString(「yyyy」)。toInt。我不知道重新:「10」:Int語法,很好知道。我編輯了我的答案以顯示使用隱含的上下文 – virtualeyes 2012-04-01 13:28:07

+0

您的問題現在不包含問題。你可以嘗試再次編輯,說明你有什麼問題嗎? – 2012-04-01 13:39:09

回答

6

我想你在這裏混合使用兩種不同的用例。

在第一種情況下,在功能相同的情況下,您使用隱式轉換來隱藏不同類之間的任意區別(或任意對您)。隱式轉換適用於該類別;這可能是安全的,並且很可能是一個好主意。我可能會用充實,我的圖書館模式,而是和寫

class JodaGivesMS(jt: JodaTime) { def ms = jt.getMillis } 
implicit def joda_can_give_ms(jt: JodaTime) = new JodaGivesMS(jt) 

,並在每次調用使用.ms,只是要明確。原因在於單位在這裏(毫秒不是毫秒不是毫秒,不是毫米,但都可以表示爲整數),我寧願留下一些記錄單位在界面上,在大多數情況下。 getMillis每次都比較滿口,但ms也不錯。儘管如此,這種轉換是合理的(如果在未來幾年(包括您)可能修改代碼的人有充分的文檔記錄)。

然而,在第二種情況下,您正在執行一種非常常見的類型與另一種非常常見的類型之間的不可靠轉換。誠然,你只是在有限的情況下做這件事,但這種轉變仍然容易逃脫並導致問題(不是你意思的異常或類型)。相反,您應該編寫那些您需要的方便正確處理轉換的例程,並隨處使用這些例程。例如,假設您有一個您希望爲「是」,「否」或整數的字段。你可能有這樣的事情

val Rint = """(\d+)""".r 
s match { 
    case "yes" => println("Joy!") 
    case "no" => println("Woe!") 
    case Rint(i) => println("The magic number is "+i.toInt) 
    case _ => println("I cannot begin to describe how calamitous this is") 
} 

但這代碼是錯誤,因爲"12414321431243".toInt拋出一個異常,當你真正想要的是說,情況是災難性的。相反,您應該編寫適當匹配的代碼:

case object Rint { 
    val Reg = """([-]\d+)""".r 
    def unapply(s: String): Option[Int] = s match { 
    case Reg(si) => 
     try { Some(si.toInt) } 
     catch { case nfe: NumberFormatException => None } 
    case _ => None 
    } 
} 

並用此代替。現在,不是執行冒險的和隱含的String轉換爲Int,當您執行匹配時,它將全部得到正確處理,包括正則表達式匹配(避免在錯誤解析中引發和捕獲成堆異常)以及異常處理如果正則表達式通過。

如果你有一些既有字符串又有int表示的東西,創建一個新類,然後隱式轉換爲每個類,如果你不想使用該對象(你知道它可以安全地)不斷重複一次沒有提供任何照明的方法調用。

+0

優秀的答案,雷克斯,謝謝,這有助於澄清隱含的轉換領土對我來說更多一點。我給出的例子是一個初學者開始探索的例子,很高興知道我正在跟蹤的地方(JodaTime)和路徑(容易出錯的str2Int)。我認爲一般的經驗法則是:明確的,如果你要採取隱式路線,那麼這樣做是爲了避免讓自己陷入困境(當你後來回到代碼中)和其他人可能會破譯你的魔法王國;-) – virtualeyes 2012-04-01 16:04:33

5

我儘量不給任何隱式轉換隻是把它從一種類型轉換爲另一個,但只爲皮條客我的圖書館模式。當你將一個String傳遞給一個採用Int的函數時,可能會有點混亂。類型安全也有巨大的損失。如果你將一個字符串傳遞給一個錯誤地接受一個I​​nt的函數,編譯器就無法檢測到它,因爲它假設你想這樣做。所以總是顯式地進行類型轉換,並且只使用隱式轉換來擴展類。

編輯:

要回答你的問題更新:對於可讀性的原因,請使用顯式getMillis。在我看來,implicits的有效用例是「pimp my library」,視圖/上下文邊界,類型類,清單,構建器......但不要懶得寫一個明確的方法調用。

+0

我會+1,「玩它安全」的方法。 – virtualeyes 2012-04-01 12:59:54

+0

更新了我的文章 – drexin 2012-04-01 15:22:42

+0

這是真的,我給出的隱含的實現示例是懶惰;我知道,getMillis和toInt不會對手指過度徵稅。 – virtualeyes 2012-04-01 16:00:27