Scala: сутенерство без шаблонов

Я широко использую шаблон Pimp my Library и как удалить шаблон. Например, скажем, у меня есть какая-то черта PrettyPrint:

trait PrettyPrint { def prettyPrint: String }

Если я хочу прокачать Int и Double, мне нужно написать такой код:

implicit def int2PrettyPrint(self: Int) = 
  new PrettyPrint { def prettyPrint = "Int: " + self }
implicit def double2PrettyPrint(self: Double) = 
  new PrettyPrint { def prettyPrint = "Double: " + self }

В приведенном выше я бы классифицировал как шаблон: 1) имя неявного преобразования, 2) ключевое слово «новое», 3) возможно, имя аргумента «я», 4) возможно, ключевое слово «неявное». Я бы лучше написал что-то вроде этого:

@pimp[Int, PrettyPrint] { def prettyPrint = "Int: " + self }
@pimp[Double, PrettyPrint] { def prettyPrint = "Double: " + self }

Предполагается, что в правой части приведенного выше кода имя «я» является аргументом преобразования.

Идеи, как это сделать?

Некоторые примечания:

1) Я могу использовать Scala 2.10, если это необходимо.

2) Насколько я могу судить, новых неявных классов в Scala 2.10 недостаточно. Это связано с тем, что для каждого неявного класса существует только одно неявное преобразование. Другими словами, следующий код не будет компилироваться, потому что PrettyPrint объявлен дважды:

implicit class PrettyPrint(self: Int) = ...
implicit class PrettyPrint(self: Double) = ...

person emchristiansen    schedule 04.11.2012    source источник
comment
@pr1001, да. У вас есть подсказки, как это сделать, или хорошие ссылки для чтения?   -  person emchristiansen    schedule 05.11.2012
comment
Пока что макросы не могут вносить глобально видимые изменения (например, добавлять общедоступные члены или классы). Возможно, мы добавим эту функциональность в один из релизов 2.10.x, но не обещаем.   -  person Eugene Burmako    schedule 05.11.2012
comment
@EugeneBurmako, так что же мне остается? Аннотация плюс плагин компилятора?   -  person emchristiansen    schedule 05.11.2012
comment
Как насчет генератора кода? Вам действительно нужно делать это так много во многих местах в коде, что быстрее написать подключаемый модуль компилятора, чем написать небольшой код, который выдает расширенный код, который вам нужен?   -  person Rex Kerr    schedule 05.11.2012
comment
Генератор кода был бы идеален, если бы я мог беспрепятственно интегрировать его в процесс сборки (SBT и Eclipse). Любые указатели?   -  person emchristiansen    schedule 05.11.2012
comment
Не от меня, так как я действительно не занимаюсь SBT/Eclipse. Но учитывая, что я автоматически создавал вещи с помощью генераторов кода на C 20 лет назад, я должен представить, что это все еще возможно.   -  person Rex Kerr    schedule 05.11.2012
comment
@emchristiansen Похоже, это единственный способ удовлетворить все ваши требования.   -  person Eugene Burmako    schedule 05.11.2012


Ответы (4)


Дополнение к нашему обсуждению в списке рассылки NativeLibs4Java, где я пример такого подключаемого модуля компилятора (который заменяет @extend(Int) def foo = blah на implicit class foo(self: Int) extends AnyVal { def foo = blah }).

Я написал более сложный плагин, который расширяет эти определения до... макросов (предоставление макрорасширяемых расширений / «сутенеров», без зависимости от времени выполнения!).

Данный:

@extend(Any) def quoted(quote: String): String = quote + self + quote

Он расширяется до:

import scala.language.experimental.macros
implicit class scalaxy$extensions$quoted$1(self: Any) {
  def quoted(quote: String) = macro scalaxy$extensions$quoted$1.quoted
}
object scalaxy$extensions$quoted$1 {
  def quoted(c: scala.reflect.macros.Context)
            (quote: c.Expr[String]): c.Expr[String] = {
    import c.universe._
    val Apply(_, List(selfTree$1)) = c.prefix.tree
    val self = c.Expr[Any](selfTree$1)
    {
      reify(quote.splice + self.splice + quote.splice)
    }
  }
}
person zOlive    schedule 20.02.2013

Вы можете просто назвать свои неявные классы по-другому:

implicit class PrettyPrintInt(self: Int) = ...
implicit class PrettyPrintDouble(self: Double) = ...
person Alexey Romanov    schedule 04.11.2012
comment
У этого есть две проблемы: 1) Это только немного уменьшает шаблон, 2) (Основная проблема) Это изменяет семантику. Если у меня есть функция def foo(prettyPrint: PrettyPrint)..., я больше не могу передать ей Int или Double. - person emchristiansen; 05.11.2012
comment
@emchristiansen: Почему бы и нет? PrettyPrint — это трейт, и неявные классы могут расширять PrettyPrint. - person kiritsuku; 05.11.2012
comment
@sschaef, если вы это сделаете, вы не сохраните персонажей. Весь смысл неявных классов состоит в том, чтобы свернуть определение трейта и неявное определение в одно определение. Если бы вы использовали неявные классы, но также должны были бы определить супертипаж PrettyPrint, вы бы потеряли эту экономию. Кроме того, вам придется писать расширения PrettyPrint для каждого неявного определения класса. - person emchristiansen; 05.11.2012

Вот еще одно решение, которое требует значительно большего количества шаблонов в обмен на немного меньше беспорядка для каждого конкретного экземпляра PrettyPrint:

implicit class PrettyPrintable[T]( val self: T ) extends AnyVal { 
  def prettyPrint( implicit impl: PrettyPrint[T]): String = impl.prettyPrint( self ) 
}
trait PrettyPrint[T]{ def prettyPrint( self: T ): String }
object PrettyPrint {
  def apply[T]( impl: T => String ): PrettyPrint[T] = new PrettyPrint[T] {
    def prettyPrint( self: T ) = impl( self )
  }
}

implicit val int2PrettyPrint = PrettyPrint[Int]( "Int: " + _ )
implicit val double2PrettyPrint = PrettyPrint[Double]( "Double: " + _ )
// Or more explicitly:
//implicit val int2PrettyPrint = PrettyPrint{self: Int => "Int: " + self }
//implicit val double2PrettyPrint = PrettyPrint{self: Double => "Double: " + self }

Сравнивать:

implicit def int2PrettyPrint(self: Int) = new PrettyPrint { def prettyPrint = "Int: " + self } 

to:

implicit val int2PrettyPrint = PrettyPrint[Int]( "Int: " + _ )

Вам по-прежнему нужно ключевое слово implicit, а также уникальное имя для неявного значения.

person Régis Jean-Gilles    schedule 05.11.2012
comment
+1 Это довольно хорошо, но мне кажется, что плагин компилятора - это то, что нужно. - person emchristiansen; 06.11.2012
comment
Кстати, разве расширения AnyVal в вашем ответе не нужны? Я думаю, что все ссылочные типы расширяют AnyVal автоматически. - person emchristiansen; 06.11.2012
comment
Нет, они расширяют AnyRef по умолчанию. Расширение AnyVal делает его классом значений (работает только в scala 2.10 (вы можете удалить его, это всего лишь оптимизация). - person Régis Jean-Gilles; 06.11.2012
comment
Обратите внимание, что я использовал еще одну особенность scala 2.10: PrettyPrintable — это неявный класс. Для scala ‹ 2.10 сделайте его обычным классом и добавьте неявное преобразование вручную. - person Régis Jean-Gilles; 06.11.2012

Резюме через 1 неделю: похоже, мне нужно написать плагин компилятора, чтобы получить именно то поведение, которое я указал.

person emchristiansen    schedule 12.11.2012