Есть ли способ "обогатить" класс Scala без переноса кода на другой объект?

В Scala 2.9 добавить пользовательские методы в класс библиотеки (обогатить или "сутенер" ). Мне пришлось написать что-то вроде этого:

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._. Конечно, я мог бы поместить все неявные классы в один объект-оболочку, но это означало бы разместить их в одном файле, что было бы довольно длинным - довольно уродливым решением.

Ответ 1

Теперь стандартный термин обогащается. Я не уверен, почему это было не раньше, учитывая, что используемые им методы библиотеки назывались такими, как RichString not 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)
}

который не спасет вас от работы (но работает быстрее).

Ответ 2

Работа, позволяющая классу обогащения находиться в другом файле, заключается в том, чтобы обернуть его в свойство вместо объекта:

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

Как указывает Рекс, вы не можете использовать классы значений внутри других классов. Поэтому, если вы хотите использовать классы значений Scala 2.10, вы не можете использовать вложенные неявные классы. D'о!

Ответ 3

Ну, ошибка компилятора - это все. Вы просто не можете определить неявный класс на верхнем уровне. Это означает, что вы либо используете объект-оболочку (который также может быть объектом пакета), либо определяете класс непосредственно в области, если он предназначен для использования (в редком случае, когда он не может быть повторно использован).

Причиной этого является то, что вы не сможете импортировать неявное преобразование без оболочки. Вы не можете импортировать неявный класс (именно: преобразование) по собственному имени.

Кроме того, я бы предложил использовать несколько другую подпись (см. классы значений):

implicit class PimpedString(val self: String) extends AnyVal

что приводит к тому, что вызовы ваших методов расширения могут быть (и будут) вложенными.

Ответ 4

Еще одно очень хорошее место для размещения неявного объявления - это сопутствующий объект.

Для неявного преобразования от Type1 до Type2 Scala выполняется поиск в сопутствующих объектах всех предков обоих типов. Поэтому часто бывает легко помещать неявное преобразование где-то, чтобы он не нуждался в явном импорте.

case class Curie(curie:String)

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

Когда вы вызываете метод, который требует Curie с String, метод преобразования вызывается без какого-либо дополнительного импорта.