Scala隐式转换Gotchas

Scala隐式转换Gotchas,scala,conflict,implicit-conversion,Scala,Conflict,Implicit Conversion,编辑 好的,@Drexin提出了一个很好的观点:当使用隐式转换器时,类型安全性的损失/令人惊讶的结果 一个不太常见的转换如何,它不会与PreDef隐式发生冲突?例如,我正在Scala中与JodaTime(伟大的项目!)合作。在定义隐式的同一控制器包对象中,我有一个类型别名: type JodaTime = org.joda.time.DateTime 以及一个将JodaTime转换为Long的隐式(对于在ScalaQuery之上构建的DAL,其中日期存储为Long) 这里PreDef和我的控制

编辑
好的,@Drexin提出了一个很好的观点:当使用隐式转换器时,类型安全性的损失/令人惊讶的结果

一个不太常见的转换如何,它不会与PreDef隐式发生冲突?例如,我正在Scala中与JodaTime(伟大的项目!)合作。在定义隐式的同一控制器包对象中,我有一个类型别名:

type JodaTime = org.joda.time.DateTime
以及一个将JodaTime转换为Long的隐式(对于在ScalaQuery之上构建的DAL,其中日期存储为Long)

这里PreDef和我的控制器包implicits之间不存在歧义,并且控制器implicits不会过滤到DAL中,因为DAL在不同的包范围中。所以当我这么做的时候

dao.getHeadlines(articleType, Some(jodaDate))
对Long的隐式转换对我来说是安全的,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) = {...}
在上面的上下文中,这些隐式转换的有效用例是否总是显式的?如果是后者,那么什么是有效的隐式转换用例

原创
在包对象中,我定义了一些隐式转换,其中一个是从简单字符串到Int的转换:

implicit def str2Int(s: String) = s.toInt
一般来说,这种方法很好用,接受Int参数但接收字符串的方法将转换为Int,返回类型设置为Int但实际返回值是字符串的方法也是如此

很好,现在在某些情况下,编译器错误带有可怕的模糊隐式:

类型为(x:String)的对象Predef中的两个方法均为augmentString scala.collection.immutable.StringOps和方法str2Int(s:String)Int 是否可以将java.lang.String转换为{val toInt:?}

我知道这种情况发生在尝试进行手动内联字符串到Int的转换时。例如,
val i=“10”.toInt

我的解决方法/技巧是在包对象中创建一个asInt助手和隐式元素:
def asInt(i:Int)=i
,并用作,
asInt(“10”)

那么,隐式最佳实践是隐式的(即通过烧焦来学习),还是有一些指导方针可以遵循,以免陷入自己制造的陷阱?换句话说,是否应该避免简单、常见的隐式转换,而只在要转换的类型唯一的情况下使用?(即永远不会陷入歧义陷阱)


感谢您的反馈,当它们按预期工作时,它们是很棒的;-)

我尝试不隐式转换任何内容,只是为了将其从一种类型转换为另一种类型,而只是为了pimp my library模式。当您将字符串传递给接受Int的函数时,可能会有点混乱。此外,类型安全性也会有巨大损失。如果您将字符串传递给一个误用Int的函数,编译器将无法检测到它,因为它假定您想要这样做。所以总是显式地进行类型转换,并且只使用隐式转换来扩展类

编辑:


为了回答更新的问题:为了可读性,请使用显式getMillis。在我看来,隐式的有效用例是“pimpmylibrary”、视图/上下文边界、类型类、清单、构建器。。。但是不要懒得写一个方法的显式调用。

我认为您在这里混合了两个不同的用例

在第一种情况下,在功能相同的情况下,使用隐式转换来隐藏不同类之间的任意区别(或者对您来说是任意的)。
JodaTime
Long
的隐式转换适用于该类别;这可能是安全的,而且很可能是个好主意。我可能会改用enrich my library模式,然后编写

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")
}
但是这个代码是错误的,因为当你真正想说情况是灾难性的时候,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表示的东西,创建一个新的类,然后对每个类进行隐式转换,如果你不想使用这个对象(你知道可以)
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
  }
}