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

4 ответа

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

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

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

// 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'о!

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

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

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

implicit class PimpedString(val self: String) extends AnyVal

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

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

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

case class Curie(curie:String)

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

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

Другие вопросы по тегам