本文介绍了如何使用 Scala 专业化提供手动专业化的实现?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

专业化承诺为原始类型提供高效的实现用最少的额外样板.但是专业化似乎过于渴望自己的好处.如果我想专门化一个类或方法,

Specialization promises to provide high-efficiency implmentations for primitive typeswith minimal extra boilerplate. But specialization seems to be too eager for its own good.If I want to specialize a class or method,

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

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

然后我需要编写一个涵盖特殊和通用情况的实现.如果这些情况真的彼此不同,以至于实现不会重叠怎么办?例如,如果我想对字节进行数学运算,我需要插入一堆 &0xFFs 进入逻辑.

then I am required to write a single implementation that covers both the specialized and the generic cases.What if those cases are really different from each other, such that the implementations do not overlap?For example, if I wanted to perform math on bytes, I would need to insert a bunch of & 0xFFs into thelogic.

我可能会写一个专门的类型类来做正确的数学运算,但这不只是推动相同的问题倒退一级?我如何为该类型类编写专门的 + 方法,而不是与更一般的实现冲突?

I could possibly write a specialized type-class to do the math right, but doesn't that just push the sameproblem back one level? How do I write my specialized + method for that type class in a way that doesn'tconflict with a more general implementation?

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

此外,一旦我以这种方式创建了类型类,我如何确保将正确的类型类用于我的专用方法而不是通用版本(如果它真的通用,应该可以编译并且肯定会运行,除非它不是我想要的)?

Also, once I do create a type-class this way, how do I make sure the correct type class is used for my specialized methodsinstead of the general version (which, if it is truly general, should probably compile and certainly would run, except that it isn't what I want)?

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

Is there a way to do this without macros? Is it easier with macros?

推荐答案

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

This is my best attempt so far. It works but the implementation isn't pretty (even if the results are). Improvements are welcome!

在类和方法级别有一种无宏的方法可以做到这一点,而且它确实涉及类型类——相当多的他们!对于类和方法,答案并不完全相同.所以请耐心等待.

There is a macro-free way to do this, both at the class and method level, and it does involve type classes--quite a lot ofthem! And the answer is not exactly the same for classes and methods. So bear with me.

您手动特化类的方式与手动为类提供任何类型的不同实现的方式相同:你的超类是抽象的(或者是一个特质),而子类提供了实现细节.

You manually specialize classes the same way that you manually provide any kind of different implementation for classes:your superclass is abstract (or is a trait), and the subclasses provide the implementation details.

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

现在,如果我们有一种专门的方法来挑选正确的实现,我们就大功告成了:

Now, if only we had a specialized method to pick out the right implementations, we'd be done:

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

因此,我们将提供自定义专用类方法的问题转化为需要提供自定义的专门方法.

So we've converted our problem of providing custom specialized classes and methods into justneeding to provide custom specialized methods.

手动特化一个方法需要一种方法来编写一个实现,但仍然可以选择您想要的实现(在编译时).类型类在这方面做得很好.认为我们已经有了实现我们所有功能的类型类,并且编译器会选择正确的.然后我们就可以写

Manually specializing a method requires a way to write one implementation that can nonethelessselect which implementation you want (at compile time). Type classes are great at this. Supposewe already had type classes that implemented all of our functionality, and that the compiler wouldselect the right one. Then we could just write

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

...或者我们可以,如果 implicitly 保证保留专业化,如果我们只曾经想要一个单一的类型参数.一般来说,这些事情是不正确的,所以我们会写我们的类型类作为隐式参数输出,而不是依赖于 A: TC 语法糖.

...or we could if implicitly was guaranteed to preserve specialization and if we onlyever wanted a single type parameter. In general these things are not true, so we'll writeour type class out as an implicit parameter rather than relying on the A: TC syntactic sugar.

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

(实际上,这无论如何都少了样板.)

(Actually, that's less boilerplate anyway.)

所以我们已经将提供自定义专用方法的问题转化为只需要编写专门的类型类并让编译器填写正确的类型.

So we've converted our problem of providing custom specialized methods into just needingto write specialized typeclasses and getting the compiler to fill in the correct ones.

类型类只是类,现在我们又要写专门的类了,但是有一个关键的区别.用户不是要求任意实例的人.这为我们的工作提供了足够的额外灵活性.

Type classes are just classes, and now we have to write specialized classes again, butthere's a critical difference. The user isn't the one asking for arbitrary instances.This gives us just enough extra flexibility for it to work.

对于foo,我们需要一个Int 版本和一个完全通用的版本.

For foo, we need an Int version and a fully generic version.

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)
}

现在我们可以创建隐式来提供这些类型的类

Now we could create implicits to supply those type classes like so

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

implicit val specFooAsInt = new SpecFooImplInt

除非我们有一个问题:如果我们真的尝试调用 foo: Intboth 隐式都将适用.因此,如果我们有一种方法来确定我们选择的类型类别的优先级,那么我们就万事大吉了.

except we have a problem: if we actually try to call foo: Int, both implicits will apply.So if we just had a way to prioritize which type class we chose, we'd be all set.

编译器用来确定隐式使用权的秘密成分之一是继承.如果隐式来自 A 通过 B extends A,但是 B声明自己的也可以应用,如果其他条件相同,B 中的那些会获胜.所以我们把我们想要赢得的更深入的继承层次.

One of the secret ingredients the compiler uses to determine the right implicit to useis inheritance. If implicits come from A via B extends A, but Bdeclares its own that also could apply, those in B win if all else is equal.So we put the ones we want to win deeper in the inheritance hierarchy.

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

Also, since you're free to define implicits in traits, you can mix them in anywhere.

所以我们的最后一块拼图是将我们的类型类隐式弹出到一个链中相互扩展的特征,更通用的特征出现得更早.

So the last piece of our puzzle is to pop our type class implicits into a chainof traits that extend each other, with the more generic ones appearing earlier.

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

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

将最高优先级的 trait 混合到需要隐式的任何地方,以及类型类将根据需要选择.

Mix in the highest-priority trait to wherever the implicits are needed, and thetype classes will be picked as desired.

请注意,类型类将与您制作的一样专业即使不使用专门的注释.所以你完全可以不用specialized,只要你足够准确地知道类型,除非你想使用专门的函数或与其他专用类互操作.(你可能会这样做.)

Note that the type classes will be as specialized as you make them even if thespecialized annotation is not used. So you can do without specialized at all,as long as you know the type precisely enough, unless you want to use specializedfunctions or interoperate with other specialized classes. (And you probably do.)

假设我们想要制作一个两参数的专用 bippy 函数,它将应用以下转换:

Let's suppose we want to make a two-parameter specialized bippy function thatwill do apply the following transformation:

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

我们应该能够通过三个类型类和一个专门的方法.我们先试试方法:

We should be able to achieve this with three type classes and a single specializedmethod. Let's try, first the method:

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
}

然后是链式特征中的隐含:

Then the implicits in chained traits:

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 中将所有内容粘贴在一起之后):

and finally we'll try it out (after pasting everything in together in :paste in the 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

它有效 - 我们的自定义实现已启用.只是为了检查我们可以使用任何类型,但我们不会泄漏到错误的实现中:

It works--our custom implementations are enabled. Just to check that we can useany type, but we don't leak into the wrong implementation:

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 计时在对一堆整数求和:

And finally, to verify that we have actually avoided boxing, we'll time bippy atsumming a bunch of integers:

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 个,这对于现代的机).

So we can see that our specialized bippy-adder achieves the same kind of performanceas specialized Function2 does (about 3 adds per ns, which is about right for a modernmachine).

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

To write custom specialized code using the @specialized annotation,

  1. 使专用类抽象并手动提供具体实现
  2. 使专门的方法(包括专门类的生成器)采用真正起作用的类型类
  3. 制作基本类型类特征 @specialized 并提供具体实现
  4. 在特征的继承层次结构中提供隐式 vals 或 defs,以便选择正确的一个

有很多样板文件,但最终您将获得无缝的定制化专业体验.

It's a lot of boilerplate, but at the end of it all you get a seamless custom-specialized experience.

这篇关于如何使用 Scala 专业化提供手动专业化的实现?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-27 15:51
查看更多