Now we will use Nat#FoldR to define arithmetic on type-level natural numbers.
Our type level functions and folds will follow the form of these value level functions. (In most cases, we are really just applying a function n times and not doing anything with the current iteration value.)
val list(n: Int) = (n to 1 by -1).toList
// like Succ[N]
def succ(a: Int) = a+1
// like Add[A <: Nat, B <: Nat]
def add(a: Int, b: Int) =
( list(a) :\ b) { (n, acc) => succ(acc) }
def mult(a: Int, b: Int) =
( list(a) :\ 0) { (n, acc) => add(acc, b) }
def exp(a: Int, b: Int) =
(list(b) :\ 1 ) { (n, acc) => mult(acc, a) }
def fact(a: Int) =
(list(a) :\ 1) { (n, acc) => mult(n, acc) }
def mod(a: Int, b: Int) =
(list(a) :\ 0 ) { (n, acc) => if(acc+1 == b) 0 else acc+1 }
For reference, the Fold type was defined as:
trait Fold[-Elem, Value] {
type Apply[N <: Elem, Acc <: Value] <: Value
}
Addition looks like:
type Add[A <: Nat, B <: Nat] = A#FoldR[B, Nat, Inc]
type Inc = Fold[Nat, Nat] {
type Apply[N <: Nat, Acc <: Nat] = Succ[Acc]
}
Multiplication is defined in terms of addition:
type Mult[A <: Nat, B <: Nat] = A#FoldR[_0, Nat, Sum[B]]
type Sum[By <: Nat] = Fold[Nat, Nat] {
type Apply[N <: Nat, Acc <: Nat] = Add[By, Acc]
}
Factorial is defined in terms of multiplication. For whatever reason, it only worked with a fold left:
type Fact[A <: Nat] = A#FoldL[_1, Nat, Prod]
type Prod = Fold[Nat, Nat] {
type Apply[N <: Nat, Acc <: Nat] = Mult[N, Acc]
}
Exponentiation is also in terms of multiplication:
type Exp[A <: Nat, B <: Nat] = B#FoldR[_1, Nat, ExpFold[A]]
type ExpFold[By <: Nat] = Fold[Nat, Nat] {
type Apply[N <: Nat, Acc <: Nat] = Mult[By, Acc]
}
Mod uses Compare#eq:
type Mod[A <: Nat, B <: Nat] = A#FoldR[_0, Nat, ModFold[B]]
type ModFold[By <: Nat] = Fold[Nat, Nat] {
type Wrap[Acc <: Nat] = By#Compare[Acc]#eq
type Apply[N <: Nat, Acc <: Nat] = Wrap[Succ[Acc]]#If[_0, Succ[Acc], Nat]
}
We can define:
def toInt[N <: Nat] : Int
similar to toBoolean. However, it is not very usable for large integers, so we will mainly use:
type Eq[A <: Nat, B <: Nat] = A#Compare[B]#eq toBoolean[ Eq[ A, B ] ]
Some examples:
type Sq[N <: Nat] = Exp[N, _2] val true = toBoolean[ Eq[ Sq[_9], Add[_1,Mult[_8,_10]] ] ] val true = toBoolean[ Eq[ Sq[Sq[_9]], Sq[Add[_1,Mult[_8,_10]]] ] ] val true = toBoolean[ Eq[ Mod[ Exp[_9,_4], _6], _3] ] val true = toInt[ Mod[ Sq[_9], _6] ] == 81 % 6
Operations on Nat are simple to implement using our folds, but don’t work well for reasonably large numbers or more complicated expressions. Around 10,000, the compilation time increases a lot or the stack overflows. Next, we’ll look at representing unsigned integers in binary and (sort of) solve Euler problem #4.
Pingback: Type-Level Programming in Scala « Apocalisp