Scala隐式转换难题 [英] Scala Implicit Conversion Gotchas

查看:108
本文介绍了Scala隐式转换难题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑

好​​吧,@ Drexin提出了一个很好的建议:使用隐式转换器时类型安全性/令人惊讶的结果丢失。



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

  type JodaTime = org.joda.time .DateTime 

和一个将JodaTime转换为Long的隐式(对于构建在ScalaQuery顶部的DAL,其中日期存储为Long)

 隐式def joda2Long(d:JodaTime)= d.getMillis 

在PreDef和我的控制器包隐式函数之间可能没有歧义,并且控制器隐式函数不会过滤到DAL中,因为这与另一个不同包装范围。所以当我这样做

  dao.getHeadlines(articleType,Some(jodaDate))

对IMO来说,隐式转换为Long已为我安全完成,并且考虑到基于日期的查询已被大量使用,我节省了一些样板。 p>

类似地,对于str2Int转换,控制器层将servlet URI参数接收为String-> String。在很多情况下URI都包含数字字符串,因此当我过滤路由以确定String是否为Int时,我不想每次都使用stringVal.toInt。相反,如果正则表达式通过,则让隐式为我将字符串值转换为Int。总共看起来像这样:

 隐式def str2Int(s:String)= s.toInt 
get( /([[0-9]+)\"\".r){
show(captures(0))// captures(0)是字符串
}
def show(id :Int)= {...}

在上述情况下,这些有效的隐式用例是转化,还是更多,总是明确的?如果是后者,那么什么是有效的隐式转换用例?



原始

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

 隐式def str2Int(s:字符串)= s.toInt 

通常这可以正常工作,需要采取的方法一个Int参数,但接收一个String,将其转换为Int,将返回类型设置为Int,但实际返回值是一个String的方法也是如此。



太好了,现在在某些情况下,编译器会出现可怕的含糊不清的隐式错误:


这两种方法在对象Predef类型(x:字符串)
scala.collection.immutable.StringOps和方法str2Int(s:String)Int
可能是从java.lang.String到?{val
toInt:?}

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



我的解决方法/黑客是创建asInt帮助器以及包对象中的隐式变量: def asInt(i:Int)= i 并用作 asInt( 10)



因此,是隐式的最佳实践是隐式的(即通过燃烧来学习),还是有一些可遵循的准则以免陷入陷阱自己创造的?换句话说,应该避免简单,通用的隐式转换,而仅在要转换的类型是唯一的情况下使用吗? (即永远不会碰到歧义陷阱)



感谢您的反馈,隐式棒极了……当它们按预期工作时;-)

解决方案

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



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

 类JodaGivesMS(jt:JodaTime){def ms = jt.getMillis} 
隐式def joda_can_give_ms(jt:JodaTime)= new JodaGivesMS(jt)

并在每次调用时都使用 .ms ,只是为了明确起见。原因是这里的单位很重要(毫秒不是微秒,不是秒不是毫米,但是所有单位都可以表示为整数),我宁愿留下 some 记录单位在介面,在大多数情况下。每次键入 getMillis 都比较麻烦,但是 ms 也不是太糟糕。不过,这种转换还是合理的(如果对那些可能在以后的几年中修改代码的人(包括您)进行了充分的文档证明)。



但是,在第二种情况下,您正在一种非常常见的类型与另一种之间执行不可靠的转换。没错,您只是在有限的范围内进行操作,但是这种转换仍然容易逃脱并引起问题(异常或类型并非您所要的)。相反,您应该编写正确处理转换所需的方便例程,并在各处使用它们。例如,假设您有一个期望为是,否或整数的字段。您可能会有类似的

  val Rint =(\d +)。r 
s匹配{
case yes => println( Joy!)
case no => println( Woe!)
case Rint(i)=> println(魔术数字为 + i.toInt)
case _ => println(我无法开始描述这是多么的灾难)
}

但是代码是错误的,因为 12414321431243 .toInt 抛出异常,而您真正想要的是说情况是灾难性的。相反,您应该编写正确匹配的代码:

  case对象Rint {
val Reg =([ -] \d +)。r
def不适用(s:字符串):Option [Int] = s match {
case Reg(si)=>
try {Some(si.toInt)}
catch {case nfe:NumberFormatException =>无}
情况_ =>
}
}

并使用它代替。现在,当您执行时,不必执行从 String Int 的冒险的和隐式转换一个匹配项将被正确处理,包括正则表达式匹配(以避免在错误的分析中抛出并捕获大量异常)和即使正则表达式通过也进行异常处理。



如果您同时具有字符串和int表示形式,请创建一个新类,然后如果不想使用该对象(您可以肯定是这样的话)对每个对象进行隐式转换,以保持重复方法调用并不能真正提供任何照明。


EDIT
OK, @Drexin brings up a good point re: loss of type safety/surprising results when using implicit converters.

How about a less common conversion, where conflicts with PreDef implicits would not occur? For example, I'm working with JodaTime (great project!) in Scala. In the same controller package object where my implicits are defined, I have a type alias:

type JodaTime = org.joda.time.DateTime

and an implicit that converts JodaTime to Long (for a DAL built on top of ScalaQuery where dates are stored as Long)

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

Here no ambiguity could exist between PreDef and my controller package implicits, and, the controller implicits will not filter into the DAL as that is in a different package scope. So when I do

dao.getHeadlines(articleType, Some(jodaDate))

the implicit conversion to Long is done for me, IMO, safely, and given that date-based queries are used heavily, I save some boilerplate.

Similarly, for str2Int conversions, the controller layer receives servlet URI params as String -> String. There are many cases where the URI then contains numeric strings, so when I filter a route to determine if the String is an Int, I do not want to stringVal.toInt everytime; instead, if the regex passes, let the implicit convert the string value to Int for me. All together it would look like:

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

In the above contexts, are these valid use cases for implicit conversions, or is it more, always be explicit? If the latter, then what are valid implicit conversion use cases?

ORIGINAL
In a package object I have some implicit conversions defined, one of them a simple String to Int:

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

Generally this works fine, methods that take an Int param, but receive a String, make the conversion to Int, as do methods where the return type is set to Int, but the actual returned value is a String.

Great, now in some cases the compiler errors with the dreaded ambiguous implicit:

both method augmentString in object Predef of type (x: String) scala.collection.immutable.StringOps and method str2Int(s: String) Int are possible conversion functions from java.lang.String to ?{val toInt: ?}

The case where I know this is happening is when attempting to do manual inline String-to-Int conversions. For example, val i = "10".toInt

My workaround/hack has been to create an asInt helper along with the implicits in the package object: def asInt(i: Int) = i and used as, asInt("10")

So, is implicit best practice implicit (i.e. learn by getting burned), or are there some guidelines to follow so as to not get caught in a trap of one's own making? In other words, should one avoid simple, common implicit conversions and only utilize where the type to convert is unique? (i.e. will never hit ambiguity trap)

Thanks for the feedback, implicits are awesome...when they work as intended ;-)

解决方案

I think you're mixing two different use cases here.

In the first case, you're using implicit conversions used to hide the arbitrary distinction (or arbitrary-to-you, anyway) between different classes in cases where the functionality is identical. The JodaTime to Long implicit conversion fits in that category; it's probably safe, and very likely a good idea. I would probably use the enrich-my-library pattern instead, and write

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

and use .ms on every call, just to be explicit. The reason is that units matter here (milliseconds are not microseconds are not seconds are not millimeters, but all can be represented as ints), and I'd rather leave some record of what the units are at the interface, in most cases. getMillis is rather a mouthful to type every time, but ms is not too bad. Still, the conversion is reasonable (if well-documented for people who may modify the code in years to come (including you)).

In the second case, however, you're performing an unreliable transformation between one very common type and another. True, you're doing it in only a limited context, but that transformation is still liable to escape and cause problems (either exceptions or types that aren't what you meant). Instead, you should write those handy routines that you need that correctly handle the conversion, and use those everywhere. For example, suppose you have a field that you expect to be "yes", "no", or an integer. You might have something like

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

But this code is wrong, because "12414321431243".toInt throws an exception, when what you really want is to say that the situation is calamitous. Instead, you should write code that matches properly:

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
  }
}

and use this instead. Now instead of performing a risky and implicit conversion from String to Int, when you perform a match it will all be handled properly, both the regex match (to avoid throwing and catching piles of exceptions on bad parses) and the exception handling even if the regex passes.

If you have something that has both a string and an int representation, create a new class and then have implicit conversions to each if you don't want your use of the object (which you know can safely be either) to keep repeating a method call that doesn't really provide any illumination.

这篇关于Scala隐式转换难题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆