2013-04-30 2 views
5

Я ищу хорошие практики, чтобы избежать повторного переписывания одного и того же кода снова и снова, чтобы добиться бесполезности. Скажем, у меня есть что-то вроде этого:Boilerplate Free Scala ArrayBuilder специализация

def speedyArrayMaker[@specialized(Long) A: ClassTag](...): Array[A] = { 
    val builder = Array.newBuilder[A] 
    // do stuff with builder 
    builder.result 
} 

Это приведет к Unboxed хранения, лежащий в основе моего builder, когда это возможно, но, как я понимаю, не распакованный метод не вызывает к нему, потому что я иду через неспециализированной ArrayBuilder черты.

В мономорфной мире специализируется на Long, я бы написать val builder = new ArrayBuilder.ofLong() и избежать бокс на всех, но мало того, чтобы говорить ArrayBuilder/Builder специализироваться на всех примитивных типов, я не могу думать хороший способ, чтобы избежать дублирования усилие здесь. Один подход, который я думал может быть, в speedyArrayMaker:

val (add, builder): (A => Unit, ArrayBuilder[A]) = implicitly[ClassTag[A]].runtimeClass match { 
    case java.lang.Long.TYPE => 
    val builder = new ArrayBuilder.ofLong() 
    ((x: Long) => builder += x, builder).asInstanceOf 
    case _ => 
    val builder = Array.newBuilder[A] 
    ((x: A) => builder += x, builder) 
} 

Поскольку это только += метод мы действительно заботимся, чтобы получить специализированные, а затем мы получаем Function1 для add, которая специализируется на Long. Проверка с помощью javap, на самом деле я получаю

90: invokestatic #118; //Method scala/runtime/BoxesRunTime.boxToLong:(J)Ljava/lang/Long; 
93: invokeinterface #127, 2; //InterfaceMethod scala/collection/mutable/Builder.$plus$eq:(Ljava/lang/Object;)Lscala/collection/mutable/Builder; 

для версии Array.newBuilder[A] (даже в специализированной продукции), а также:

252: invokeinterface #204, 3; //InterfaceMethod scala/Function1.apply$mcVJ$sp:(J)V 

для свернутой версии. Я могу помещать этот шаблон в функцию «специализированного помощника строителя», но он кажется уродливым, особенно когда он все еще отправляется во время выполнения на основе чего-то известного во время компиляции во время специализации. В конечном итоге я бы сказал, что мое предложение здесь заключается в том, чтобы контактировать с тем, что Function1 уже специализирован, и мне это не особенно нравится.

Есть ли умные трюки, которые я могу использовать, чтобы сделать это более приятным? Я понимаю, что это очень низкоуровневая деталь и редко будет критичной по производительности, но с учетом количества усилий/дублирования кода, которые попадают во все специализированные классы ArrayBuilder.of*, кажется, что очень жаль отбросить некоторые из их преимуществ в обмен на будучи полиморфными.

Редактировать Я думал о чем-то уродливым, но я надеялся, что будет работать:

def builderOf(x: Array[Int]): ArrayBuilder.ofInt = new ArrayBuilder.ofInt() 
def builderOf(x: Array[Long]): ArrayBuilder.ofLong = new ArrayBuilder.ofLong() 
def builderOf[A: ClassTag](x: Array[A]): ArrayBuilder[A] = ArrayBuilder.make[A] 

, а затем внутри моей специализированной функции:

val witness: Array[A] = null 
val builder = builderOf(witness) 

, но это, кажется, называют родовое builderOf даже в специализированной версии (хотя достаточно информации о типе доступно для вызова версии Array[Long]). Кто-нибудь знает, почему это не работает? Подход кажется довольно чистым, по сравнению с другим, который я предлагал. Я предполагаю, что я надеялся на более «макроподобный» подход к специализации, но я думаю, что нет никакой гарантии, что он будет корректным для всех экземпляров, если только он не выбирает один и тот же метод для каждой специализации :(

+0

Я не уверен, чтобы увидеть, как все, что вы будете делать, вы можете получить любую специализацию, учитывая, что 'ArrayBuidler' не специализируется (и, таким образом,' + = 'никогда не будет специализироваться даже при вызове из специализированного метода).Вы получите специализацию только в том случае, если вы просто обходите «ArrayBuidler» (например, определив свою собственную специализированную версию). –

+0

На самом деле, мне показалось, что специализация «всего лишь» внешнего метода (тот, который вызывает «+ =»), может уже нам значительно ускорить, позволяя джиттеру выполнять вставку с мономорфным кешем. Это то, что вы имели в виду? –

+0

Моя точка (это моя другая учетная запись) заключается в том, что существует множество специализированных подклассов ArrayBuilder под названием 'ofInt',' ofDouble' и т. Д. Они используются, когда вы запрашиваете 'Array.newBuilder [someprimitive]', но вы могут также создавать их непосредственно. Если вы используете 'newBuilder', вы получаете' ArrayBuilder', который не является специализированным, но если вы создаете экземпляр 'new ArrayBuilder.ofInt()', вы также получите unboxed-вызовы на '+ =', и это то, что я пытался для захвата выше. Вы можете протестировать это, аннотируя 'new ofInt()' с более конкретными и менее конкретными типами и посмотреть, есть ли у вас бокс-вызов. – copumpkin

ответ

4

может попробовать что-то вдоль линий (извините варварские имена),

import scala.collection.mutable.ArrayBuilder 
import scala.reflect.ClassTag 

trait SpecializedArrayBuilder[@specialized(Long) A] { 
    def +=(a: A) 
    def result: Array[A] 
} 

trait LowPrioritySpecializedArrayBuilder { 
    implicit def defaultBuilder[A: ClassTag] = new SpecializedArrayBuilder[A] { 
    val builder = ArrayBuilder.make[A] 
    def +=(a: A) = builder += a 
    def result = builder.result 
    } 
} 

object SpecializedArrayBuilder extends LowPrioritySpecializedArrayBuilder { 
    implicit val longBuilder = new SpecializedArrayBuilder[Long] { 
    val builder = new ArrayBuilder.ofLong 
    def +=(a: Long) = builder += a 
    def result = builder.result 
    } 
} 

object Specialized { 
    def speedyArrayMaker[@specialized(Long) A](a: A) 
    (implicit builder: SpecializedArrayBuilder[A]): Array[A] = { 
    builder += a 
    builder.result 
    } 

    def main(arg: Array[String]) { 
    val arr = speedyArrayMaker(1L) 
    println(arr) 
    } 
} 
+0

Спасибо за это! У меня еще не было возможности проверить его, но я предполагаю, что имплициты решаются в соответствии со специализированным типом, в отличие от перегруженных методов, как я пытался в исходном вопросе. Я думаю, что определенный код, который вы написали, даст мне один экземпляр строителя для всего типа, но нетрудно понять, как его модифицировать, чтобы сделать строителей по требованию. –

+0

В некотором смысле это просто реплицирует много дубликатов, которые уже находятся в стандартной библиотеке. Похоже, если бы мы захотели начать осторожно добавлять аннотации специализации к большим частям библиотеки коллекций, «ArrayBuilder» и «Builder» были бы хорошими отправными точками. –

+0

@MyseriousDan Да, согласен. –