【问题标题】:Scala Implicit Conversion GotchasScala 隐式转换陷阱
【发布时间】:2012-04-15 09:12:50
【问题描述】:

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

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

type JodaTime = org.joda.time.DateTime

以及将 JodaTime 转换为 Long 的隐式(对于构建在 ScalaQuery 之上的 DAL,其中日期存储为 Long)

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

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

dao.getHeadlines(articleType, Some(jodaDate))

IMO 安全地为我完成了到 Long 的隐式转换,并且鉴于大量使用基于日期的查询,我节省了一些样板文件。

同样,对于 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) = {...}

在上述上下文中,这些隐式转换的有效用例,还是更多,总是显式的?如果是后者,那么什么是有效的隐式转换用例?

原创
package 对象 中,我定义了一些隐式转换,其中一个是简单的 String 到 Int:

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

通常这很好用,接受 Int 参数但接收 String 的方法会转换为 Int,返回类型设置为 Int 但实际返回值是 String 的方法也是如此。

太好了,现在在某些情况下,编译器会出现可怕的隐含歧义:

对象 Predef 类型 (x: String) 中的两个方法 augmentString scala.collection.immutable.StringOps 和方法 str2Int(s: String) Int 是从 java.lang.String 到 ?{val 的可能转换函数 toInt: ?}

我知道发生这种情况的情况是尝试进行手动内联 String 到 Int 的转换。例如val i = "10".toInt

我的解决方法/技巧是创建一个 asInt 帮助器以及包对象中的隐式:def asInt(i: Int) = i 并用作 asInt("10")

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

感谢您的反馈,隐式非常棒......当它们按预期工作时 ;-)

【问题讨论】:

  • 当您在范围内进行隐式转换时,为什么要调用"10".toInt?您应该改为"10": Int 并让隐式转换处理它。 (但我同意 drexin 的观点,一般来说这不是一个很好的隐含范围。)
  • 我没有调用“10”.toInt,就是一个例子。我被咬的地方是 new JodaTime().toString("yyyy").toInt。我不知道 re:"10": Int 语法,很高兴知道。我编辑了我的答案以显示使用隐式的上下文
  • 您的问题现在不包含问题。您能否再次尝试编辑以说明您遇到的问题?
  • 隐含还是不隐含,这是个问题。我的编辑更多地显示了我使用隐式转换的上下文。到目前为止的回复是,不要太明确,所以我想知道隐含的“有效”用例是什么?编辑我的问题以反映这一点
  • @virtualeyes 添加方法的隐式转换是可以的(而且是不可避免的)。隐式转换到转换类型充其量是危险的,应该避免。简单来说,JavaConversions 不好,JavaConverters 好。它们也可以与类似构建器的模式一起使用,例如在 ScalaQuery 中,但实际上,这是另一个问题。不要改变问题,提出一个新问题。它是免费的。

标签: scala conflict implicit-conversion


【解决方案1】:

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

在第一种情况下,在功能相同的情况下,您使用隐式转换来隐藏不同类之间的任意区别(或任意区别)。 JodaTimeLong 的隐式转换适合该类别;这可能是安全的,而且很可能是个好主意。我可能会改用丰富我的图书馆模式,然后写

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

并在每次通话时使用.ms,只是为了明确。原因是这里的单位很重要(毫秒不是微秒不是秒不是毫米,但都可以表示为整数),我宁愿留下 some 记录单位是什么接口,在大多数情况下。 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
  }
}

并改用它。现在不再执行从StringInt 的冒险和隐式转换,当您执行匹配时,它将被正确处理,正则表达式匹配(以避免抛出和捕获成堆的异常解析错误)和异常处理,即使正则表达式通过。

如果您有一个同时具有字符串和 int 表示形式的东西,请创建一个新类,然后在您不希望保留使用该对象(您知道可以安全地使用任何一种)时对每个类进行隐式转换重复一个实际上不提供任何照明的方法调用。

【讨论】:

  • 很好的答案,雷克斯,谢谢,这对我来说有助于澄清隐式转换领域。我给出的例子是初学者开始探索的例子,很高兴知道我在哪里(JodaTime)和远离(容易出错的str2Int)。我想一般的经验法则是:要明确,并且,如果您要采用隐式路线,请以这样的方式进行,以免使您自己(当您稍后返回代码时)和其他可能有的人感到困惑破译你的魔法王国;-)
【解决方案2】:

我尽量不隐式转换任何东西,只是为了将它从一种类型转换为另一种类型,而只是为了皮条客我的库模式。当您将 String 传递给采用 Int 的函数时,可能会有些混乱。此外,类型安全性也有很大损失。如果您将字符串传递给错误地采用 Int 的函数,则编译器无法检测到它,因为它假定您想要这样做。所以总是显式地进行类型转换,并且只使用隐式转换来扩展类。

编辑:

要回答您更新的问题:为了便于阅读,请使用显式 getMillis。在我看来,隐式的有效用例是“pimp my library”、视图/上下文边界、类型类、清单、构建器……但不要懒得写对方法的显式调用。

【讨论】:

  • 我会 +1 一个,“安全行事”的方法。
  • 是的,我给出的隐式实现示例是懒惰的;我知道,getMillis 和 toInt 对手指的负担并不过分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-14
  • 2011-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-16
  • 2011-09-12
相关资源
最近更新 更多