16

在 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.PimpedStringor MyApp.pimps._。当然,我可以将所有隐式类放在一个包装器对象中,但这意味着将它们全部放在一个文件中,这会很长 - 非常丑陋的解决方案。

4

4 回答 4

10

现在的标准术语是丰富。我不确定为什么以前没有,因为使用它的库方法被命名为RichStringnot PimpedString

无论如何,你不能把隐式类放在任何地方,但是为了使用隐式你需要方法,你不能把方法放在任何地方。但是您可以作弊以获取所有内容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)
}

这不会节省您的工作(但运行速度更快)。

于 2013-02-10T21:27:47.477 回答
5

允许扩充类驻留在不同文件中的解决方法是将其包装在特征而不是对象中:

// 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 的值类中受益,则不能使用嵌套的隐式类。哦!

于 2013-02-10T21:30:31.423 回答
3

好吧,编译器错误说明了一切。您只是不能在顶层定义隐式类。这意味着您要么使用包装对象(也可以是包对象),要么直接在范围内定义要使用的类(在极少数情况下,它不必是可重用的)。

原因是如果没有包装器,您将无法导入隐式转换。您不能按自己的名称导入隐式类(准确地说:转换)。

除此之外,我建议使用稍微不同的签名(请参阅Value Classes):

implicit class PimpedString(val self: String) extends AnyVal

这具有对您的扩展方法的调用可以(并且将)内联的效果。

于 2013-02-10T21:04:00.937 回答
2

另一个放置隐式声明的好地方是伴生对象。

对于从Type1Type2Scala 的隐式转换,会查看这两种类型的所有祖先的伴随对象。因此,在不需要显式导入的地方放置隐式转换通常很容易。

case class Curie(curie:String)

object Curie {
  implicit def toCurie(s:String) = Curie(curie)
}

无论您在何处调用需要Curie使用String转换方法的方法,都无需任何额外的导入即可调用。

于 2013-09-07T17:51:19.640 回答