如何使用Scala专业化提供手动专用实现?

时间:2015-04-06 09:11:10

标签: scala specialization

专业化有望为原始类型提供高效率的实现 最小的额外样板。但专业化似乎过于渴望自己的利益。 如果我想专门化一个类或方法,

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 = ???
}

另外,一旦我以这种方式创建了一个类类,我如何确保为我的专用方法使用正确的类型类 而不是一般版本(如果它是真正的一般版本,应该编译,当然可以运行,除非它不是我想要的)?

有没有办法在没有宏的情况下做到这一点?使用宏更容易吗?

3 个答案:

答案 0 :(得分:25)

这是我迄今为止最好的尝试。它可以工作,但实现并不漂亮(即使结果是)。欢迎改进!

有一种无宏的方法可以在类和方法级别执行此操作,并且它确实涉及类型类 - 相当多的 他们!对于类和方法,答案并不完全相同。所以忍受我。

手动专业课程

您手动专门化类的方式与手动为类提供任何类型的不同实现的方式相同: 您的超类是抽象的(或者是特征),子类提供实现细节。

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保证专业化,如果我们这样做的话 曾经想要一个单一的类型参数。一般来说,这些都不是真的,所以我们会写 我们的类型类作为隐式参数而不是依赖于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,则两个隐含都将适用。 因此,如果我们只是想方设法确定我们选择的类型类别的优先级,那么我们就可以全部设定。

类型类的选择(以及一般的含义)

编译器用于确定隐式使用的权利的秘密成分之一 是继承。如果隐含来自A B extends AB 声明它自己也可以适用,B中的那些如果其他条件相同则获胜。 因此,我们将我们想要在继承层次结构中更深入地获胜。

此外,由于您可以自由定义特征中的含义,因此您可以将它们混合在任何地方。

因此,我们的最后一个难题是将我们的类型类隐含到链中 相互延伸的特征,前面出现的更通用的特征。

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
}

最后我们会尝试一下(将所有内容粘贴在REPL中的:paste中):

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 = ichi.bench.Thyme@1130520d

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

所以我们可以看到我们专业的bippy-adder实现了同样的性能 正如专业的Function2所做的那样(每ns大约增加3个,这对于现代主义来说是正确的 机)。

摘要

使用@specialized注释编写自定义专用代码

  1. 使专业类抽象并手动提供具体实现
  2. 制作专门的方法(包括专门课程的生成器)使用完成实际工作的类型类
  3. 制作基类类型特征@specialized并提供具体实现
  4. 在特征的继承层次结构中提供隐式val或def,以便选择正确的值
  5. 它有很多样板,但在最后,你可以获得无缝的定制专业体验。

答案 1 :(得分:3)

这是scala internals mailing list

的答案

使用miniboxing specialization,您可以使用反射功能:

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方法的确切行为,而无需定义任何隐式类。

答案 2 :(得分:2)

我知道它已经很老了,但我碰到它寻找别的东西,也许它会证明是有用的。我有类似的动机,并在how to check I'm inside a specialized function or class

中回答

我使用了反向查找表 - 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)
}