Как реализовать промежуточные типы для неявных методов? - PullRequest
3 голосов
/ 07 марта 2011

Предположим, я хочу предложить метод foo для существующего типа A вне моего контроля.Насколько я знаю, канонический способ сделать это в Scala - реализовать неявное преобразование из A в некоторый тип, который реализует foo.Теперь я в основном вижу два варианта.

  1. Определить отдельный, возможно, даже скрытый класс для этой цели:

    protected class Fooable(a : A) {
      def foo(...) = { ... }
    }
    implicit def a2fooable(a : A) = new Fooable(a)
    
  2. Определить анонимный классinline:

    implicit def a2fooable(a : A) = new { def foo(...) = { ... } }
    

Вариант 2), конечно, меньше, чем шаблон, особенно когда происходит много параметров типа.С другой стороны, я думаю, что это должно создать больше накладных расходов, поскольку (концептуально) создается один класс на преобразование, а не один класс в глобальном масштабе в 1).

Существует ли общее руководство?Разницы нет, потому что компилятор / ВМ избавляются от издержек 2)?

Ответы [ 2 ]

7 голосов
/ 07 марта 2011

Использование отдельного класса лучше для производительности, так как альтернатива использует отражение.

Учтите, что

new { def foo(...) = { ... } }

действительно

new AnyRef { def foo(...) = { ... } }

Теперь, AnyRef не имеет метода foo.В Scala этот тип на самом деле AnyRef { def foo(...): ... }, который, если вы удалите AnyRef, вы должны распознать как структурный тип .

Во время компиляции это время можно передать обратнои далее, и везде будет известно, что метод foo может быть вызван.Однако в JVM нет структурного типа, и для добавления интерфейса потребуется прокси-объект, что может вызвать некоторые проблемы, такие как нарушение ссылочного равенства (т. Е. Объект не будет равен версии самого структурного типа).

Был найден способ использовать кэшированные вызовы отражения для структурных типов.

Итак, если вы хотите использовать шаблон Pimp My Library для любого чувствительного к производительности приложения, объявите класс.

6 голосов
/ 07 марта 2011

Я считаю, что 1 и 2 компилируются в один и тот же байт-код (кроме имени класса, которое генерируется в случае 2). Если Fooable существует только для того, чтобы вы могли неявно преобразовать A в Fooable (и вы никогда не собираетесь напрямую создавать и использовать Fooable), то я бы выбрал вариант 2.

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

UPDATE : Я должен пересмотреть мой ответ. Я бы использовал вариант 1 вашего кода, потому что вариант 2, как оказалось, использует отражение (scala 2.8.1 в Linux).

Я скомпилировал эти две версии одного и того же кода, декомпилировал их в java с помощью jd-gui, и вот результаты:

исходный код с именованным классом

class NamedClass { def Foo : String = "foo" }

object test {
  implicit def StrToFooable(a: String) = new NamedClass 
  def main(args: Array[String]) { println("bar".Foo) }
}

исходный код с анонимным классом

object test {
  implicit def StrToFooable(a: String) = new { def Foo : String = "foo" } 

  def main(args: Array[String]) { println("bar".Foo) }
}    

скомпилировано и декомпилировано в java с помощью java-gui. «Именованная» версия генерирует NamedClass.class, который декомпилируется в эту Java:

public class NamedClass
  implements ScalaObject
{
  public String Foo()
  {
    return "foo";
  }
}

аноним генерирует тестовый класс $$ anon $ 1, который декомпилируется в следующую Java

public final class test$$anon$1
{
  public String Foo()
  {
    return "foo";
  }
}

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

однако на сайте вызова я получаю эту Java для "именованной" версии

public void main(String[] args) 
{ 
  Predef..MODULE$.println(StrToFooable("bar").Foo());
}

и это для анонима

  public void main(String[] args) { 
    Object qual1 = StrToFooable("bar"); Object exceptionResult1 = null;
    try { 
      exceptionResult1 = reflMethod$Method1(qual1.getClass()).invoke(qual1, new Object[0]); 
      Predef..MODULE$.println((String)exceptionResult1); 
      return; 
    } catch (InvocationTargetException localInvocationTargetException) { 
      throw localInvocationTargetException.getCause();
    }
  }

Я немного погуглил и обнаружил, что другие сообщили об одном и том же, но я не нашел больше информации о том, почему это так.

...