gpt4 book ai didi

scala - 如何使用 Scala 特化提供手动特化的实现?

转载 作者:行者123 更新时间:2023-12-03 10:59:29 25 4
gpt4 key购买 nike

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

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

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

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

我可能会写一个专门的类型类来做正确的数学运算,但这不只是插入相同的
问题倒退一级?我的专业怎么写 +该类型类的方法,而不是
与更一般的实现冲突?
class Adder[@specialized(Byte) A] {
def +(a1: A, a2: A): A = ???
}

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

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

最佳答案

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

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

手动专业类(class)

您可以像手动为类提供任何类型的不同实现一样手动专门化类:
你的父类(super class)是抽象的(或者是一个特征),子类提供了实现细节。

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

现在我们可以创建隐式来提供像这样的类型类
implicit def specFooAsAny[A] = new SpecFooImplAny[A]

implicit val specFooAsInt = new SpecFooImplInt

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

类型类的选择(以及一般隐式)

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

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

所以我们的最后一块拼图是将我们的类型类隐式弹出到一个链中
相互扩展的特征,更通用的特征出现得更早。
trait LowPriorityFooSpecializers {
implicit def specializeFooAsAny[A] = new SpecializedFooImplAny[A]
}

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

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

请注意,类型类将与您制作的一样专业,即使
不使用专门的注释。所以你完全可以不用 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 注释编写自定义专用代码,
  • 使专用类抽象并手动提供具体实现
  • 使专门的方法(包括专门类的生成器)采用真正工作的类型类
  • 使基类型类 trait @specialized 并提供具体实现
  • 在特征的继承层次结构中提供隐式 vals 或 defs,以便选择正确的

  • 这是很多样板文件,但最终您将获得无缝的定制专业体验。

    关于scala - 如何使用 Scala 特化提供手动特化的实现?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29468341/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com