加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

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

发布时间:2020-12-16 09:29:30 所属栏目:安全 来源:网络整理
导读:专业化有望为原始类型提供高效率的实现 最少的额外样板。但专业化似乎过于渴望自己的利益。 如果我想专门化一个类或方法, def foo[@specialized(Byte) A](a: A): String = ???class Bar[@specialized(Int) B] { var b: B = ??? def baz: B = ???} 那么我需
专业化有望为原始类型提供高效率的实现
最少的额外样板。但专业化似乎过于渴望自己的利益。
如果我想专门化一个类或方法,

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

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

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

解决方法

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

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

手动专业课程

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

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)

……或者我们可以隐含地保证专业化,如果我们只是
曾经想要一个单一的类型参数。一般来说这些都不是真的,所以我们会写
我们的类型类作为隐式参数而不是依赖于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扩展A,但是B.
声明它自己也可以申请,如果其他条件相同则在B赢。
因此,我们将我们想要在继承层次结构中更深入地获胜。

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

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

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

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

将最高优先级的特征混合到需要的任何地方,以及
将根据需要选择类型类。

请注意,类型类将与您创建它们一样专门化,即使是
不使用专门的注释。所以你可以做到没有专业,
只要你知道这种类型就足够了,除非你想使用专门的
功能或与其他专业类互操作。 (你可能会这样做。)

一个完整的例子

假设我们想要制作一个双参数专用的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中):

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

最后,为了验证我们实际上已经避免了拳击,我们将时间生活
总结一堆整数:

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注释编写自定义专用代码,

>使专门的类抽象并手动提供具体的实现
>使专门的方法(包括专门的类的生成器)采用完成实际工作的类型类
>使基本类型类特征@specialized并提供具体实现
>在特征的继承层次结构中提供隐式val或def,以便选择正确的值

这是很多样板,但最后你可以获得无缝的定制专业体验。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读