Как реализовать промежуточные типы для неявных методов?

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

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

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

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

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

Есть ли общее руководство? Нет ли разницы, потому что компилятор/VM избавляются от накладных расходов 2)?


person Raphael    schedule 07.03.2011    source источник


Ответы (2)


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

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

ОБНОВЛЕНИЕ: я должен пересмотреть свой ответ. Я бы использовал вариант 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";
  }
}

анонимный генерирует класс test$$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();
    }
  }

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

person Paolo Falabella    schedule 07.03.2011
comment
Можете ли вы предложить некоторые доказательства/доказательства вашей веры? Я думал о добавлении к типам, которые я не контролирую, в противном случае, конечно, лучшее решение. Я уточняю вопрос в этой связи. - person Raphael; 07.03.2011
comment
@Raphael Рафаэль, у меня здесь не установлена ​​scala, и я не могу предоставить вам доказательства. Через несколько часов, когда буду дома, соберу оба варианта, проверю сгенерированный .class и выложу результаты - person Paolo Falabella; 07.03.2011
comment
@Рафаэль, оказывается, ты был прав, не доверяя непроверенным утверждениям :) Я пересмотрел свой ответ. - person Paolo Falabella; 07.03.2011
comment
Любой случай, когда используется структурный тип (например, во втором примере), будет компилироваться для использования отражения и, следовательно, приведет к снижению производительности. Первый способ, добавление класса для предоставления расширения, является каноническим (и широко используется в стандартных библиотеках Scala). - person Kris Nuttycombe; 07.03.2011

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

Считают, что

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

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

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

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

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

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

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

person Daniel C. Sobral    schedule 07.03.2011
comment
Спасибо за эти идеи. Однако мне любопытно, как это объяснение согласуется с выводами Паоло, которые указывают на то, что есть тип в JVM, который можно использовать без отражения. Или test$$anon$1 это только имя класса, а не тип? - person Raphael; 07.03.2011
comment
@Raphael Это похоже на разницу между интерфейсом и реализацией. Класс test$$anon$1 можно рассматривать как реализацию интерфейса, тогда как AnyRef { def foo(...): ... } будет самим интерфейсом. Хотя верно то, что в ограниченных сценариях можно заменить вызовы отражения этими анонимными классами, это неверно для более общих сценариев. - person Daniel C. Sobral; 07.03.2011
comment
Спасибо за объяснение. Я предполагал, что new {foo = "bar"} эквивалентен new Compiler_Please_Create_This_Class_For_Me() для анонимных классов в С# и Java, а не new AnyRef {foo = "bar"}, поэтому мне было трудно связать анонимные классы со структурными типами. - person Paolo Falabella; 08.03.2011