Many typelevel constructs like heterogenous lists (and in this library, also type sets, type maps, literal singleton types and other constructs derived from them) rely on an encoding of natural numbers at the type level. While Peano numbers are simple, their construction is too cumbersome and performance too poor for them to be suitable for most of the more complex constructs provided by the library.
Functionally, dense numbers encode a numeric type in binary. A number is either 0 or a heterogenous list of digits. For non zero numbers, the head
of the list is the least significant bit and the last element is always 1
.
sealed trait Dense
trait DCons[d <: Dense.Digit, T <: Dense] extends Dense
trait DNil extends Dense
object Dense {
sealed trait Digit
trait D0 extends Digit
trait D1 extends Digit
type ::[H <: Digit, T <: Dense] = DCons[H, T]
type _0 = DNil
type _1 = D1 :: DNil
type _2 = D0 :: D1 :: DNil
type _3 = D1 :: D1 :: DNil
type _4 = D0 :: D0 :: D1 :: DNil
type _5 = D1 :: D0 :: D1 :: DNil
type _6 = D0 :: D1 :: D1 :: DNil
type _7 = D1 :: D1 :: D1 :: DNil
type _8 = D0 :: D0 :: D0 :: D1 :: DNil
type _9 = D1 :: D0 :: D0 :: D1 :: DNil
}
By construction, left shift and right shift are constant time operations while all other times are log-time or worse. Therefore, in theory, indexers that use dense numbers are less efficient than those that use Peano numbers. In practice, the difference can be ignored and is compensated several times over by the performance gained in other operations. This is why typequux exclusively uses dense numbers to encode natural numbers into types.
The type signatures for dense numbers can be quite long and are not terribly informative. /**/
represents an omitted (for clarity) type signature.
Some of the type constructors that are a part of the Dense
trait are useful in practice.
scala> import typequux._; import Dense._
import typequux._
import Dense._
scala> implicitly[_0#Inc =:= _1]
res0: /**/ = <function1>
scala> implicitly[_7#Inc =:= _8]
res2: /**/ = <function1>
scala> implicitly[_1#Dec =:= _0]
res3: /**/ = <function1>
scala> implicitly[_16#Dec =:= _15]
res4: /**/ = <function1>
scala> implicitly[_0#ShiftL =:= _0]
res5: /**/ = <function1>
scala> implicitly[_1#ShiftL =:= _2]
res6: /**/ = <function1>
scala> implicitly[_3#ShiftL =:= _6]
res7: /**/ = <function1>
scala> implicitly[_0#ShiftR =:= _0]
res8: /**/ = <function1>
scala> implicitly[_1#ShiftR =:= _0]
res9: /**/ = <function1>
scala> implicitly[_2#ShiftR =:= _1]
res10: /**/ = <function1>
scala> implicitly[_7#ShiftR =:= _3]
res11: /**/ = <function1>
The companion object provides type constructors that implement common operations. Supported operations are:
scala> implicitly[_2 + _3 =:= _5]
res12: /**/ = <function1>
scala> implicitly[_6 + _0 =:= _6]
res13: /**/ = <function1>
scala> implicitly[_0 + _2 =:= _2]
res14: /**/ = <function1>
scala> implicitly[_4 * _0 =:= _0]
res15: =:=[typequux.Dense.*[typequux.Dense._4,typequux.Dense._0],typequux.Dense._0] = <function1>
scala> implicitly[_7 * _1 =:= _7]
res16: =:=[typequux.Dense.*[typequux.Dense._7,typequux.Dense._1],typequux.Dense._7] = <function1>
scala> implicitly[_3 * _4 =:= _12]
res17: =:=[typequux.Dense.*[typequux.Dense._3,typequux.Dense._4],typequux.Dense._12] = <function1>
scala> implicitly[_4 * _4 =:= _16]
res18: =:=[typequux.Dense.*[typequux.Dense._4,typequux.Dense._4],typequux.Dense._16] = <function1>
scala> implicitly[_5 ^ _0 =:= _1]
res19: /**/ = <function1>
scala> implicitly[_7 ^ _1 =:= _7]
res20: /**/ = <function1>
scala> implicitly[_1 ^ _4 =:= _1]
res21: /**/ = <function1>
scala> implicitly[_3 ^ _2 =:= _9]
res22: /**/ = <function1>
scala> implicitly[_2 ^ _4 =:= _16]
res23: /**/ = <function1>
scala> import Typequux._ // useful imports for True and False
import Typequux._
scala> implicitly[_0 < _3 =:= True]
res26: /**/ = <function1>
scala> implicitly[_5 <= _5 =:= True]
res27: /**/ = <function1>
scala> implicitly[_5 === _5 =:= True]
res28: /**/ = <function1>
scala> implicitly[_5 === _7 =:= False]
res29: /**/ = <function1>
scala> implicitly[_7 > _4 =:= True]
res30: /**/ = <function1>
scala> implicitly[_7 >= _4 =:= True]
res31: /**/ = <function1>
scala> implicitly[Max[_4, _5] =:= _5]
res32: /**/ = <function1>
scala> implicitly[Min[_5, _9] =:= _5]
res33: /**/ = <function1>
It is implemented by a marker typeclass.
scala> implicitly[DenseDiff[_0, _0, _0]]
res34: /**/ = /**/
scala> implicitly[DenseDiff[_4, _0, _4]]
res35: /**/ = /**/
scala> implicitly[DenseDiff[_13, _5, _8]]
res36: /**/ = /**/
scala> Dense.toLong[_0]
res37: Long = 0
scala> Dense.toLong[_16]
res38: Long = 16
scala> Dense.toLong[_16 * _4]
res39: Long = 64
scala> Dense.toInt[_0]
res40: Int = 0
scala> Dense.toInt[_16]
res41: Int = 16
scala> Dense.toInt[_16 * _4]
res42: Int = 64
Dense numbers can be shown to satisfy:
+[A, B] =:= +[B, A]
*[A, B] =:= *[B, A]
+[A, +[B, C]] =:= +[+[A, B], C]
*[A, *[B, C]] =:= *[*[A, B], C]
*[+[A, B], C] =:= +[*[A, C], *[B, C]]
+[A, _0] =:= A
, +[_0, A] =:= A
*[A, _1] =:= A
, *[_1, A] =:= A
A#Inc#Dec =:= A
A#ShiftL#ShiftR =:= A
A#ShiftL =:= *[A, _2]
Xor[===[A, _2 * A#ShiftR], ===[A, *[_2, A#ShiftR] + _1]] =:= True
*[A ^ B, A ^ C] =:= ^[A, B + C]
^[A ^ B, C] =:= ^[A, B * C]
*[A ^ C, B ^ C] =:= ^[A * B, C]
^[A, _1] =:= A
^[A, _0] =:= _1
^[_1, A] =:= _1