4

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

一条值得追求的路?除了代码膨胀之外,还有三件事反对它:

  • 可能有几十个GEs 需要自定义约束
  • 编写GEs 变得越来越困难:代码可能需要传递几十个类型参数
  • 转换可能会变得困难,例如想象一个List[GE[_<:Rate]].map( ??? ). 我的意思是如何Duty.RateCons翻译成TDuty.RateConsTDuty不同的地方GE)......

我已经在这个项目上投入了相当多的时间,这就是为什么我不愿意这么轻易放弃的原因。所以...说服我我正在做一些有用的事情,或者告诉我我应该回到动态检查的版本。

4

1 回答 1

0

正如 Jesper Nordenberg 所提到的,要做的事情是定义一组封闭的类型和对这些类型的相等操作。如果你重新审视这个问题,你如何解决它的例子是可取的。此外,还需要提问者所需的类型级编程示例。

在此处此处阅读更多信息。

于 2011-04-29T20:10:27.690 回答