在Scala 2.9中为库类添加自定义方法(丰富或“pimp”)我必须写这样的东西:
object StringPimper {
implicit def pimpString(s: String) = new {
def greet: String = "Hello " + s
}
}
随着Scala 2.10的发布,我读到它引入了隐式类定义,理论上讲,它是通过在返回匿名类对象的隐式方法中消除需要来简化上述任务的。我以为它会让我只写
implicit class PimpedString(s: String) {
def greet: String = "Hello " + s
}
对我来说看起来更漂亮。但是,这样的定义会导致编译错误:
`implicit' modifier cannot be used for top-level objects
通过将代码再次包装在对象中来解决:
object StringPimper {
implicit class PimpedString(s: String) {
def greet: String = "Hello " + s
}
}
不用说,这几乎抵消了改善的感觉。
那么,有没有办法把它写得更短?要摆脱包装器对象?
我实际上有一个MyApp.pimps
包,其中所有的皮条客都去了(我没有太多,如果有的话,我会使用一些单独的包)而且我厌倦了导入MyApp.pimps.StringPimper._
而不是MyApp.pimps.PimpedString
或MyApp.pimps._
。当然,我可以将所有隐式类放在一个包装器对象中,但这意味着将它们全部放在一个文件中,这将是相当长的 - 非常难看的解决方案。
答案 0 :(得分:10)
标准术语现在是 rich 。鉴于使用它的库方法被命名为RichString
而不是PimpedString
,我不确定为什么之前没有。
无论如何,你不能把隐式类放在什么都没有,但为了使用implicits你需要方法,你不能把方法放在什么都没有。但你可以作弊来获取pimps
中的所有内容。
// This can go in one file
trait ImplicitsStartingWithB {
implicit class MyBoolean(val bool: Boolean) { def foo = !bool }
implicit class MyByte(val byte: Byte) { def bar = byte*2 }
}
// This can go in another file
trait ImplicitsStartingWithS {
implicit class MyShort(val short: Short) { def baz = -short }
implicit class MyString(val st: String) { def bippy = st.reverse }
}
// This is a third file, if you want!
object AllImplicits extends ImplicitsStartingWithB with ImplicitsStartingWithS {}
scala> import AllImplicits._
import AllImplicits._
scala> true.foo
res0: Boolean = false
您也可以使用pimps
包对象执行此操作 - 通常您会将名为package.scala
的文件放在名为pimps
的目录中,然后
package object pimps extends /* blah blah ... */ {
/* more stuff here, if you need it */
}
值得注意的是,作为新功能的价值类具有相当多的限制。有些是不必要的,但是如果你想避免分配一个新的MyBoolean
类(JVM通常可以大大优化,但通常没有达到裸方法调用的程度),你将不得不解决这些限制。在那种情况下,
// In some file, doesn't really matter where
class MyBoolean(val bool: Boolean) extends AnyVal { def foo = !bool }
package object pimps {
def implicit EnrichWithMyBoolean(b: Boolean) = new MyBoolean(b)
}
这不能保存您的工作(但运行得更快)。
答案 1 :(得分:4)
允许浓缩类驻留在不同文件中的解决方法是将其包装在特征而不是对象中:
// file: package.scala
package object mypackage extends StringImplicits {
def test { println("Scala".greet) }
}
// file: StringImplicits.scala
package mypackage
trait StringImplicits {
implicit class RichString(s: String) {
def greet: String = "Hello " + s
}
}
正如Rex指出的那样,你不能在其他类中使用值类。因此,如果您希望从Scala 2.10的值类中受益,则不能使用嵌套的隐式类。 D'哦!
答案 2 :(得分:3)
好吧,编译器错误就是一切。您无法在顶层定义隐式类。这意味着您要么使用包装器对象(也可以是包对象),要么直接在范围内定义类(如果它是要使用的话)(在极少数情况下它不必是可重用的)。 / p>
原因是如果没有包装器,您将无法导入隐式转换。您无法通过自己的名称导入隐式类(精确地:转换)。
除此之外,我建议使用稍微不同的签名(参见Value Classes):
implicit class PimpedString(val self: String) extends AnyVal
,其效果是可以(并且将)内联对您的扩展方法的调用。
答案 3 :(得分:2)
放置隐式声明的另一个非常好的地方是伴随对象。
对于从Type1
到Type2
的隐式转换,Scala会查找两种类型的所有祖先的伴随对象。因此,通常很容易将隐式转换置于不需要显式导入的位置。
case class Curie(curie:String)
object Curie {
implicit def toCurie(s:String) = Curie(curie)
}
只要您调用需要Curie
且String
的方法,无需任何其他导入即可调用转换方法。