2012-04-01 79 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