I'm trying to generalize a divisible-by relation in Scala using type level programming. This is my Nat
number definitions:
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]]
}
For say number 3 this works:
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!
}
When I tried to generalize it with a trait with 2 type parameters it didn't work (something like trait DivByX[X <: Nat, N <: Nat]
). So I tried the following, which didn't work either:
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!
}
What's a correct way to generalize this?
Edit: Followed @TravisBrown 's suggestion but it doesn't compile for 3 | 6
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?
}
This is going to be a little hand-wavy, because to be honest I don't really understand this stuff. I'm not sure it's possible for anyone who isn't closely familiar with scalac's implementation of implicit resolution to understand this stuff, but it is at least possible to build up some intuitions about what the compiler likes and doesn't like, and that's what I'll try to explain here.
Take your latest implementation:
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]{}
}
When you ask for a DivByX[_3, _6]
instance, the compiler is going to try to find an implicit that provides it. DivByX_0
is immediately out, since _0
isn't _6
. Next it's going to try DivByX_Nplus
, which means it needs to solve for X
, N
, and S
, in that order. The X
and S
parts are easy—it's N
that's the problem—so since we know this version doesn't work, I'd start by rearranging the type parameters to put the unknown one last:
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]{}
}
This still doesn't work, unfortunately. If we turn on -Xlog-implicits
, we'll see the following (along with some other stuff):
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]]
^
...
So it looks like the problem is that it's not able to solve for N
in N + 3 = 6
. Is there a spec'd reason for this? I have no idea. We could sit down with the specification and the scalac source and spend the afternoon trying to figure out whether there's a reason the compiler can't do this, or we could just apply our general rule of moving stuff we don't know to the end.
We can't use our Adder
type class to rewrite the N + 3 = 6
equation in such a way that the N
comes last, but we can write a similar type class for a new operation that would make that possible:
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
}
}
We can confirm that it looks like it does what we think it does:
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;
...
Now we can try writing DivByX
again, replacing Adder
with 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] {}
}
…but it still doesn't work:
scala> implicitly[DivByX[_6, _3]]
<console>:16: error: could not find implicit value for parameter e: DivByX[_6,_3]
implicitly[DivByX[_6, _3]]
^
So we just apply our rule of moving "more unknown" stuff down and to the right again, this time looking at the implicit arguments for divByXS
. In the type of div
we see both X
(which we know) and N
(which we don't), while for minus
we see two things we know ( S
and X
) and one we don't ( N
). So we try switching their order so that the one we know more about comes first:
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] {}
}
And then we try again:
scala> implicitly[DivByX[_3, _6]]
res0: DivByX[_3,_6] = DivByX$$anon$4@8d6e389
This seems too good to be true, so let's try some other stuff:
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]]
^
And yeah, it looks like it works, and even if we don't know why it works, at least we got here in a vaguely principled way.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.