Подтвердить что ты не робот

Как можно предоставить специализированные реализации вручную с помощью Scala специализации?

Специализация promises для обеспечения высокоэффективных внедрений для примитивных типов с минимальным дополнительным шаблоном. Но специализация, похоже, слишком жаждет собственного блага. Если я хочу специализировать класс или метод,

def foo[@specialized(Byte) A](a: A): String = ???

class Bar[@specialized(Int) B] {
  var b: B = ???
  def baz: B = ???
}

тогда мне нужно написать единую реализацию, которая охватывает как специализированные, так и общие случаи. Что делать, если эти случаи действительно отличаются друг от друга, так что реализации не перекрываются? Например, если бы я хотел выполнить математику по байтам, мне нужно было бы вставить кучу & 0xFF в логика.

Я мог бы написать специализированный тип-класс для правильной математики, но это не просто толкает то же самое проблема на одном уровне? Как написать мой специализированный метод + для этого типа класса таким образом, чтобы он не конфликт с более общей реализацией?

class Adder[@specialized(Byte) A] {
  def +(a1: A, a2: A): A = ???
}

Кроме того, как только я создаю класс типа таким образом, как я могу убедиться, что правильный тип класса используется для моих специализированных методов вместо общей версии (которая, если она действительно является общей, должна, вероятно, компилироваться и, конечно же, работать, за исключением того, что это не то, что я хочу)?

Есть ли способ сделать это без макросов? С макросами проще?

4b9b3361

Ответ 1

Это моя лучшая попытка. Он работает, но реализация не очень хороша (даже если результаты есть). Усовершенствования приветствуются!

Существует макро-бесплатный способ сделать это как на уровне класса, так и на уровне метода, и он включает классы типов - довольно много их! И ответ не совсем то же самое для классов и методов. Так что медведь со мной.

Специализированные классы вручную

Вы вручную классифицируете классы так же, как вручную, для разных классов для классов: ваш суперкласс является абстрактным (или является признаком), а подклассы предоставляют детали реализации.

abstract class Bippy[@specialized(Int) B] {
  def b: B
  def next: Bippy[B]
}

class BippyInt(initial: Int) extends Bippy[Int] {
  private var myB: Int = initial
  def b: Int = myB
  def next = { myB += 1; this }
}

class BippyObject(initial: Object) extends Bippy[Object] {
  private var myB: Object = initial
  def b: B = myB
  def next = { myB = myB.toString; this }
}

Теперь, если бы у нас был специализированный метод для выбора правильных реализаций, мы бы сделали:

object Bippy{
  def apply[@specialized(Int) B](initial: B) = ???  // Now what?
}

Итак, мы обратились к нашей проблеме о предоставлении специализированных специализированных классов и методов в необходимо предоставить специализированные специализированные методы.

Специализированные методы вручную

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

def foo[@specialized(Int) A: SpecializedFooImpl](a: A): String =
  implicitly[SpecializedFooImpl[A]](a)

... или мы могли бы, если бы implicitly гарантировали сохранение специализации, и если бы мы только когда-либо хотел один параметр типа. В общем, это не так, поэтому мы напишем наш тип class выводится как неявный параметр, а не полагается на синтаксический сахар A: TC.

def foo[@specialized(Int) A](a: A)(implicit impl: SpecializedFooImpl[A]): String =
  impl(a)

(Вообще-то, это все равно меньше).

Итак, мы обратились к нашей проблеме предоставления специализированных специализированных методов просто нуждающимся для написания специализированных типов и получения компилятора для заполнения правильных.

Специализированные классы класса

Классы классов - это просто классы, и теперь мы должны снова писать специализированные классы, но есть критическое различие. Пользователь не тот, кто запрашивает произвольные экземпляры. Это дает нам достаточно дополнительной гибкости для работы.

Для foo нам нужна версия Int и полностью общая версия.

trait SpecFooImpl[@specialized (Int), A] {
  def apply(param: A): String
}

final class SpecFooImplAny[A] extends SpecFooImpl[A] {
  def apply(param: A) = param.toString
}

final class SpecFooImplInt extends SpecFooImpl[Int] {
  def apply(param: Int) = "!" * math.max(0, param)
}

Теперь мы могли бы создать implicits для предоставления таких классов типов, например:

implicit def specFooAsAny[A] = new SpecFooImplAny[A]

implicit val specFooAsInt = new SpecFooImplInt

за исключением того, что у нас есть проблема: если мы на самом деле пытаемся вызвать foo: Int, применяются оба implicits. Поэтому, если бы у нас был только способ определить приоритет того класса, который мы выбрали, мы все установили.

Выбор классов классов (и вообще имплицитов)

Один из секретных ингредиентов, используемый компилятором для определения права, подразумеваемого для использования является наследованием. Если имплициты происходят от A через B extends A, но B объявляет свою собственную, которая также может применяться, в B победить, если все остальное равно. Поэтому мы помещаем те, которые хотим завоевать глубже в иерархии наследования.

Кроме того, поскольку вы можете определить implicits в чертах, вы можете смешать их в любом месте.

Итак, последний фрагмент нашей головоломки состоит в том, чтобы выскочить в наш тип класса в цепочку черт, которые расширяют друг друга, с более типичными, которые появляются раньше.

trait LowPriorityFooSpecializers {
  implicit def specializeFooAsAny[A] = new SpecializedFooImplAny[A]
}

trait FooSpecializers extends LowPriorityFooSpecializers {
  implicit val specializeFooAsInt = new SpecializedFooImplInt
}

Смешайте в приоритете с наивысшим приоритетом везде, где нужны импликации, и типы классов будут выбраны по желанию.

Обратите внимание, что классы типов будут такими же специализированными, как вы их делаете, даже если специализированная аннотация не используется. Таким образом, вы можете обойтись без specialized вообще, если вы знаете тип достаточно точно, если вы не хотите использовать специализированные функции или взаимодействовать с другими специализированными классами. (И вы, вероятно, делаете.)

Полный пример

Предположим, что мы хотим сделать двухпараметрическую специализированную функцию bippy, которая будут применены следующие преобразования:

bippy(a, b) -> b
bippy(a, b: Int) -> b+1
bippy(a: Int, b) -> b
bippy(a: Int, b: Int) -> a+b

Мы должны быть в состоянии достичь этого с помощью трех типов классов и одного специализированного метод. Попробуем сначала метод:

def bippy[@specialized(Int) A, @specialized(Int) B](a: A, b: B)(implicit impl: SpecBippy[A, B]) =
  impl(a, b)

Тогда классы типов:

trait SpecBippy[@specialized(Int) A, @specialized(Int) B] {
  def apply(a: A, b: B): B
}

final class SpecBippyAny[A, B] extends SpecBippy[A, B] {
  def apply(a: A, b: B) = b
}

final class SpecBippyAnyInt[A] extends SpecBippy[A, Int] {
  def apply(a: A, b: Int) = b + 1
}

final class SpecBippyIntInt extends SpecBippy[Int, Int] {
  def apply(a: Int, b: Int) = a + b
}

Затем подразумевается в цепных чертах:

trait LowerPriorityBippySpeccer {
  // Trick to avoid allocation since generic case is erased anyway!
  private val mySpecBippyAny = new SpecBippyAny[AnyRef, AnyRef]
  implicit def specBippyAny[A, B] = mySpecBippyAny.asInstanceOf[SpecBippyAny[A, B]]
}

trait LowPriorityBippySpeccer extends LowerPriorityBippySpeccer {
  private val mySpecBippyAnyInt = new SpecBippyAnyInt[AnyRef]
  implicit def specBippyAnyInt[A] = mySpecBippyAnyInt.asInstanceOf[SpecBippyAnyInt[A]]
}

// Make this last one an object so we can import the contents
object BippySpeccer extends LowPriorityBippySpeccer {
  implicit val specBippyIntInt = new SpecBippyIntInt
}

и, наконец, мы попробуем его (после вставки всего в :paste в REPL):

scala> import Speccer._
import Speccer._

scala> bippy(Some(true), "cod")
res0: String = cod

scala> bippy(1, "salmon")
res1: String = salmon

scala> bippy(None, 3)
res2: Int = 4

scala> bippy(4, 5)
res3: Int = 9

Он работает - наши пользовательские реализации включены. Просто чтобы проверить, что мы можем использовать любой тип, но мы не впадаем в неправильную реализацию:

scala> bippy(4, 5: Short)
res4: Short = 5

scala> bippy(4, 5: Double)
res5: Double = 5.0

scala> bippy(3: Byte, 2)
res6: Int = 3

И, наконец, чтобы убедиться, что мы действительно избегали бокса, мы будем время bippy на суммирование цепочки целых чисел:

scala> val th = new ichi.bench.Thyme
th: ichi.bench.Thyme = [email protected]

scala> val adder = (i: Int, j: Int) => i + j
adder: (Int, Int) => Int = <function2>

scala> var a = Array.fill(1024)(util.Random.nextInt)
a: Array[Int] = Array(-698116967, 2090538085, -266092213, ...

scala> th.pbenchOff(){
  var i, s = 0
  while (i < 1024) { s = adder(a(i), s); i += 1 }
  s 
}{ 
  var i, s = 0
  while (i < 1024) { s = bippy(a(i), s); i += 1 }
  s
}

Benchmark comparison (in 1.026 s)
Not significantly different (p ~= 0.2795)
  Time ratio:    0.99424   95% CI 0.98375 - 1.00473   (n=30)
    First     330.7 ns   95% CI 328.2 ns - 333.1 ns
    Second    328.8 ns   95% CI 326.3 ns - 331.2 ns

Итак, мы видим, что наш специализированный биппи-сумматор достигает такой же производительности как специализируется Function2 (около 3 добавок за ns, что примерно соответствует современному машина).

Резюме

Чтобы написать специальный специализированный код с помощью аннотации @specialized,

  • Сделать специализированный класс абстрактным и вручную поставлять конкретные реализации
  • Сделать специализированные методы (в том числе генераторы для специализированного класса) взять typeclasses, которые выполняют настоящую работу
  • Сделать базовый признак типа @specialized и предоставить конкретные реализации
  • Предоставлять неявные vals или defs в иерархии признаков наследования, чтобы выбрать правильный.

Это много шаблонов, но в конце этого вы получаете беспрецедентный индивидуальный опыт.

Ответ 2

Это ответ из scala список рассылки внутренних дел:

С помощью специализации miniboxing вы можете использовать функцию отражения:

import MbReflection._
import MbReflection.SimpleType._
import MbReflection.SimpleConv._

object Test {
  def bippy[@miniboxed A, @miniboxed B](a: A, b: B): B =
    (reifiedType[A], reifiedType[B]) match {
      case (`int`, `int`) => (a.as[Int] + b.as[Int]).as[B]
      case (  _  , `int`) => (b.as[Int] + 1).as[B]
      case (`int`,   _  ) =>  b
      case (  _  ,   _  ) =>  b
    }

  def main(args: Array[String]): Unit = {
    def x = 1.0
    assert(bippy(3,4) == 7)
    assert(bippy(x,4) == 5)
    assert(bippy(3,x) == x)
    assert(bippy(x,x) == x)
  }
}

Таким образом вы можете выбрать точное поведение метода bippy на основе аргументов типа без определения каких-либо неявных классов.

Ответ 3

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

Я использовал таблицу обратного поиска - SpecializedKey - это специализированный класс, который равен всем другим экземплярам с той же специализацией, поэтому я могу выполнить проверку, подобную этой

def onlyBytes[@specialized E](arg :E) :Option[E] =
    if (specializationFor[E]==specializationFor[Byte]) Some(arg)
    else None

Конечно, при работе с индивидуальными примитивными значениями нет преимуществ при работе, но с коллекциями, особенно с итераторами, становится полезным.

final val AllButUnit = new Specializable.Group((Byte, Short, Int, Long, Char, Float, Double, Boolean, AnyRef))

def specializationFor[@specialized(AllButUnit) E] :ResolvedSpecialization[E] =
   Specializations(new SpecializedKey[E]).asInstanceOf[ResolvedSpecialization[E]]


private val Specializations = Seq(
    resolve[Byte],
    resolve[Short],
    resolve[Int],
    resolve[Long],
    resolve[Char],
    resolve[Float],
    resolve[Double],
    resolve[Boolean],
    resolve[Unit],
    resolve[AnyRef]
).map(
    spec => spec.key -> spec :(SpecializedKey[_], ResolvedSpecialization[_])
).toMap.withDefaultValue(resolve[AnyRef])

private def resolve[@specialized(AllButUnit) E :ClassTag] :ResolvedSpecialization[E] =
    new ResolvedSpecialization[E](new SpecializedKey[E], new Array[E](0))


class ResolvedSpecialization[@specialized(AllButUnit) E] private[SpecializedCompanion]
    (val array :Array[E], val elementType :Class[E], val classTag :ClassTag[E], private[SpecializedCompanion] val key :SpecializedKey[E]) {

    private[SpecializedCompanion] def this(key :SpecializedKey[E], array :Array[E]) =
    this(array, array.getClass.getComponentType.asInstanceOf[Class[E]], ClassTag(array.getClass.getComponentType.asInstanceOf[Class[E]]), key)

    override def toString = s"@specialized($elementType)"

    override def equals(that :Any) = that match {
        case r :ResolvedSpecialization[_] => r.elementType==elementType
        case _ => false
    }

    override def hashCode = elementType.hashCode
}

private class SpecializedKey[@specialized(AllButUnit) E] {
    override def equals(that :Any) = that.getClass==getClass
    override def hashCode = getClass.hashCode

    def className = getClass.getName
    override def toString = className.substring(className.indexOf("$")+1)
}