鉴于 Scala 的强大类型系统,我有一个雄心勃勃的项目,现在我将要放弃它,因为努力与有用的比率似乎太高了。
基本上我有一些图形元素 ( ),它们对应于以给定计算速率GE
执行的声音过程。图形元素由形成其输入的其他图形元素组成。现在对输入的速率有相当随意的限制。在源语言(SuperCollider)中,速率是在运行时检查的,这自然是因为它是一种动态类型的语言。我想看看我是否可以在编译时强制执行检查。
一些约束相当简单,可以用“arg1 的速率必须至少与 arg2 的速率一样高”的形式表示。但其他人变得复杂,例如
“如果 arg0 的汇率是‘需求’,则 args1 的汇率必须是‘需求’或‘标量’或等于封闭 GE 的汇率”。
问题是:我应该放弃吗?以下是运行时检查的外观:
sealed trait Rate
case object demand extends Rate
case object audio extends Rate
case object control extends Rate
case object scalar extends Rate
trait GE { def rate: Rate }
// an example GE:
case class Duty(rate: Rate, in0: GE, in1: GE) extends GE {
def checkRates(): Unit =
require(in0.rate != demand || (in1.rate != demand &&
in1.rate != scalar && in1.rate != rate))
}
相比之下,它与费率的类型参数相比如何:
sealed trait Rate
trait audio extends Rate
trait demand extends Rate
trait control extends Rate
trait scalar extends Rate
trait GE[R <: Rate]
object Duty {
trait LowPri {
implicit def con1[R, T]: RateCons[R, audio , T] = new ConImpl[R, audio , T]
implicit def con2[R, T]: RateCons[R, control, T] = new ConImpl[R, control, T]
implicit def con3[R, T]: RateCons[R, scalar , T] = new ConImpl[R, scalar , T]
implicit def con4[R, T]: RateCons[R, demand , demand] =
new ConImpl[R, demand, demand]
implicit def con5[R, T]: RateCons[R, demand , scalar] =
new ConImpl[R, demand, scalar]
}
object RateCons extends LowPri {
implicit def con6[R]: RateCons[R, demand, R] = new ConImpl[R, demand, R]
}
private class ConImpl[ R, S, T ] extends RateCons R, S, T ]
sealed trait RateCons[ R, S, T ]
def ar[S <: Rate, T <: Rate](in0: GE[S], in1: GE[T])(
implicit cons: RateCons[audio, S, T]) = apply[audio, S, T](in0, in1)
def kr[S <: Rate, T <: Rate](in0: GE[S], in1: GE[T])(
implicit cons: RateCons[control, S, T]) = apply[control, S, T](in0, in1)
}
case class Duty[R <: Rate, S <: Rate, T <: Rate](in0: GE[S], in1: GE[T])(
implicit con: Duty.RateCons[R, S, T]) extends GE[R]
测试:
def allowed(a: GE[demand], b: GE[audio], c: GE[control], d: GE[scalar]): Unit = {
Duty.ar(b, c)
Duty.kr(b, c)
Duty.ar(b, a)
Duty.ar(b, d)
Duty.ar(a, b)
Duty.kr(a, c)
}
def forbidden(a: GE[demand], b: GE[audio], c: GE[control], d: GE[scalar]): Unit = {
Duty.kr(a, b)
Duty.ar(a, c)
}
一条值得追求的路?除了代码膨胀之外,还有三件事反对它:
- 可能有几十个
GE
s 需要自定义约束 - 编写
GE
s 变得越来越困难:代码可能需要传递几十个类型参数 - 转换可能会变得困难,例如想象一个
List[GE[_<:Rate]].map( ??? )
. 我的意思是如何Duty.RateCons
翻译成TDuty.RateCons
(TDuty
不同的地方GE
)......
我已经在这个项目上投入了相当多的时间,这就是为什么我不愿意这么轻易放弃的原因。所以...说服我我正在做一些有用的事情,或者告诉我我应该回到动态检查的版本。