使用Scala在类型级编程中推广DivisibleBy

时间:2017-02-18 21:32:10

标签: scala functional-programming type-level-computation

我正在尝试使用类型级编程来概括Scala中的可分割关系。这是我的Nat号码定义:

trait Nat {
  type plus[N<:Nat] <: Nat
}
trait Zero extends Nat {
  type plus[N<:Nat] = N
}
trait Succ[N <: Nat] extends Nat {
  type plus[M<:Nat] = Succ[M#plus[N]]
}

对于说3号,这有效:

trait DivBy3[N<:Nat]

object DivByX {
  implicit val DivBy3_0: DivBy3[_0] = new DivBy3[_0]{}
  implicit def DivBy3_n[N <: Nat](implicit witness: DivBy3[N]): DivBy3[Succ[Succ[Succ[N]]]] =
    new DivBy3[Succ[Succ[Succ[N]]]]{}

  implicitly[DivBy3[_0]] // <- compiles, good!
  //implicitly[DivBy3[_1]] <- doesn't compile, good!
  //implicitly[DivBy3[_2]] <- doesn't compile, good!
  implicitly[DivBy3[_3]] // <- compiles, good!
  //implicitly[DivBy3[_4]] <- doesn't compile, good!
  //implicitly[DivBy3[_5]] <- doesn't compile, good!
  implicitly[DivBy3[_6]] // <- compiles, good!
}

当我尝试使用具有2个类型参数的特征来推广它时,它不起作用(类似trait DivByX[X <: Nat, N <: Nat])。所以我尝试了以下,但也没有用:

trait Divisors[X <: Nat] {

  trait DivByX[N <: Nat]

  implicit val DivByX_0: DivByX[_0] = new DivByX[_0]{}

  implicit def DivByX_NplusX[N <: Nat](implicit witness: DivByX[N]): DivByX[N#plus[X]] =
    new DivByX[N#plus[X]]{}

}

object Divisors extends Divisors[_3] {

  type DivBy3[N <: Nat] = DivByX[N]

  implicitly[DivBy3[_0]] // <- it compiles, meh!
  //implicitly[DivBy3[_1]] <- doesn't compile, good!
  //implicitly[DivBy3[_2]] <- doesn't compile, good!
  //implicitly[DivBy3[_3]] <- doesn't compile, baaad!
}

什么是推广这个的正确方法?

编辑:关注@TravisBrown的建议,但不编译为3 | 6

trait Nat
trait Zero extends Nat
trait Succ[N <: Nat] extends Nat

trait Adder[N <: Nat, M <: Nat] {
  type Out <: Nat
}

object Adder {
  type Aux[N<:Nat,M<:Nat, Out0 <: Nat] = Adder[N,M] { type Out = Out0 }

  implicit def add0[N<:Nat]: Aux[N,_0,N] = new Adder[N,_0]{
    type Out = N
  }

  implicit def addN[N<:Nat,M<:Nat](implicit adder: Adder[Succ[N],M]): Aux[N,Succ[M], adder.Out] =
    new Adder[N,Succ[M]] {
      type Out = adder.Out
    }

  def apply[N<:Nat, M<: Nat](implicit adder: Adder[N,M]): Aux[N,M, adder.Out] = adder

}

trait DivByX[X <: Nat, N <: Nat]

object DivByX {
  implicit def DivByX_0[X <: Nat]: DivByX[X, _0] = new DivByX[X, _0]{}
  implicit def DivByX_NplusX[X <: Nat, N <: Nat, S <: Nat](
    implicit div: DivByX[X,N], sum: Adder.Aux[N,X,S]): DivByX[X,S] =
    new DivByX[X,S]{}

  import Adder._
  type DivBy3[N <: Nat] = DivByX[_3, N]
  implicitly[DivBy3[_0]]
  implicitly[DivBy3[_3]]
  implicitly[Adder.Aux[_3,_3,_6]]
  val x: DivBy3[_6] = DivByX_NplusX(implicitly[DivBy3[_3]], implicitly[Adder.Aux[_3,_3,_6]])
  //implicitly[DivBy3[_6]] // <- :( doesn't compile, whyyy?
}

1 个答案:

答案 0 :(得分:4)

这将是一个小手淫,因为说实话,我真的不明白这个东西。我不确定那些不太熟悉scalac实现隐式解析的人是否有可能理解这些东西,但至少有可能对编译器喜欢和不喜欢的内容建立一些直觉,那就是我将在这里解释一下。

将未知的东西移到右边

采取最新的实施:

trait DivByX[X <: Nat, N <: Nat]

object DivByX {
  implicit def DivByX_0[X <: Nat]: DivByX[X, _0] = new DivByX[X, _0]{}
  implicit def DivByX_NplusX[X <: Nat, N <: Nat, S <: Nat](
    implicit div: DivByX[X,N], sum: Adder.Aux[N,X,S]): DivByX[X,S] =
    new DivByX[X,S]{}
}

当您要求DivByX[_3, _6]实例时,编译器将尝试查找提供它的隐式实例。由于DivByX_0不是_0,因此_6会立即停用。接下来,它将尝试DivByX_Nplus,这意味着它需要按顺序求解XNSXS部分很容易 - N这就是问题 - 因为我们知道这个版本不起作用,所以我首先重新安排类型参数来放置未知数最后一个:

trait DivByX[X <: Nat, N <: Nat]

object DivByX {
  implicit def DivByX_0[X <: Nat]: DivByX[X, _0] = new DivByX[X, _0]{}
  implicit def DivByX_NplusX[X <: Nat, S <: Nat, N <: Nat](
    implicit div: DivByX[X,N], sum: Adder.Aux[N,X,S]): DivByX[X,S] =
    new DivByX[X,S]{}
}

不幸的是,这仍然不起作用。如果我们启用-Xlog-implicits,我们会看到以下内容(以及其他一些内容):

scala> implicitly[DivByX[_3, _6]]
...
<console>:16: this.DivByX.DivByX_NplusX is not a valid implicit value for DivByX[_3,_6] because:
hasMatchingSymbol reported error: could not find implicit value for parameter sum: Adder.Aux[N,_3,_6]
       implicitly[DivByX[_3, _6]]
                 ^
...

所以看起来问题是它无法解决N中的N + 3 = 6。是否有这样的原因?我不知道。我们可以坐下来使用规范和scalac源代码,然后花一点时间试图找出编译器是否有这样做的原因,或者我们可以将我们不知道的移动内容的一般规则应用到最后

减去类型

我们不能使用我们的Adder类型类来重写N + 3 = 6等式,使N最后出现,但我们可以为新的类型写一个类似的类使这成为可能的操作:

trait Minus[M <: Nat, S <: Nat] { type Out <: Nat }

object Minus {
  type Aux[M <: Nat, S <: Nat, Out0 <: Nat] = Minus[M, S] { type Out = Out0 }

  def apply[M <: Nat, S <: Nat](implicit m: Minus[M, S]): Aux[M, S, m.Out] = m

  implicit def minus0[M <: Nat]: Aux[M, _0, M] = new Minus[M, _0] {
    type Out = M
  }

  implicit def minusN[M <: Nat, S <: Nat](implicit
    m: Minus[M, S]
  ): Aux[Succ[M], Succ[S], m.Out] = new Minus[Succ[M], Succ[S]] {
    type Out = m.Out
  }
}

我们可以确认它看起来像我们认为的那样:

scala> implicitly[Minus.Aux[_6, _1, _5]]
res6: Minus.Aux[_6,_1,_5] = Minus$$anon$2@2d4203b4

scala> implicitly[Minus.Aux[_6, _3, _3]]
res7: Minus.Aux[_6,_3,_3] = Minus$$anon$2@2a071dd

scala> implicitly[Minus.Aux[_6, _3, _2]]
<console>:16: this.Minus.minusN is not a valid implicit value for Minus.Aux[_6,_3,_2] because:
hasMatchingSymbol reported error: type mismatch;
...

现在我们可以尝试再次撰写DivByX,将Adder替换为Minus

trait DivByX[X <: Nat, N <: Nat]

object DivByX {
  implicit def divByX0[X <: Nat]: DivByX[X, _0] = new DivByX[X, _0] {}
  implicit def DivByXS[X <: Nat, S <: Nat, N <: Nat](implicit
    div: DivByX[X, N],
    minus: Minus.Aux[S, X, N]
  ): DivByX[X, S] = new DivByX[X, S] {}
}

......但它仍然不起作用:

scala> implicitly[DivByX[_6, _3]]
<console>:16: error: could not find implicit value for parameter e: DivByX[_6,_3]
       implicitly[DivByX[_6, _3]]
                 ^

再次将未知的东西移到右边

所以我们只是应用我们将“更多未知”的东西向下移动到右边的规则,这次查看divByXS的隐含参数。在div类型中,我们同时看到X(我们知道)和N(我们没有),而对于minus,我们看到了两件我们知道的事情( SX)和我们没有的N。所以我们尝试切换他们的订单,以便我们更了解的是第一个:

trait DivByX[X <: Nat, N <: Nat]

object DivByX {
  implicit def divByX0[X <: Nat]: DivByX[X, _0] = new DivByX[X, _0] {}
  implicit def DivByXS[X <: Nat, S <: Nat, N <: Nat](implicit
    minus: Minus.Aux[S, X, N],
    div: DivByX[X, N]
  ): DivByX[X, S] = new DivByX[X, S] {}
}

然后我们再试一次:

scala> implicitly[DivByX[_3, _6]]
res0: DivByX[_3,_6] = DivByX$$anon$4@8d6e389

这似乎太好了,所以让我们尝试其他一些东西:

scala> implicitly[DivByX[_3, Succ[Succ[Succ[_6]]]]]
res1: DivByX[_3,Succ[Succ[Succ[_6]]]] = DivByX$$anon$4@4c025f43

scala> implicitly[DivByX[_2, _6]]
res2: DivByX[_2,_6] = DivByX$$anon$4@4d4069f9

scala> implicitly[DivByX[_2, _4]]
res3: DivByX[_2,_4] = DivByX$$anon$4@40dc52e

scala> implicitly[DivByX[_1, _5]]
res4: DivByX[_1,_5] = DivByX$$anon$4@5bd63e47

scala> implicitly[DivByX[_3, _5]]
<console>:16: error: could not find implicit value for parameter e: DivByX[_3,_5]
       implicitly[DivByX[_3, _5]]
                 ^

是的,看起来它有效,即使我们不知道它为什么有效,至少我们是以模糊的原则来到这里。