I am implementing an HList using inline
and a type match fails only for the last element of the list. I cannot figure out why. So here are the basic definitions for the HList:
import scala.compiletime.*
import compiletime.ops.int.*
sealed trait Tup
case object EmpT extends Tup
case class TCons[H, T <: Tup](head: H, tail: T) extends Tup
type +:[A,T<:Tup] = TCons[A, T]
type EmpT = EmpT.type
extension [A, T <: Tup] (a: A) def +: (t: T): TCons[A, T] =
TCons(a, t)
Now, I want to implement a get
method that returns the element at a given index. If the index is invalid, it should fail at compile time. The following inline function works correctly:
transparent inline def getOk[L <: Tup, N <: Int](l: L, n: N): Any =
inline l match
case _: EmpT =>
error("Element index exceeds length of list.")
case list : TCons[_,_] =>
inline if n == 0
then
list.head
else
inline val next = n-1
getOk(list.tail,next)
val t1 = getOk(1 +: 2 +: 3 +: EmpT, 0)
assert(t1 == 1)
val t2 = getOk(1 +: 2 +: 3 +: EmpT, 1)
assert(t2 == 2)
val t3 = getOk(1 +: 2 +: 3 +: EmpT, 2)
assert(t3 == 3)
When looking at the Tuple
implementation, I see type matching is used. So I “lifted” the type match and adapted and tested it as follows (just changed the concatenation symbol):
type Elem[X <: Tup, N <: Int] = X match
case x +: xs =>
N match
case 0 => x
case S[n1] => Elem[xs, n1]
summon[Elem[1 +: 2 +: 3 +: EmpT, 0] =:= 1]
summon[Elem[1 +: 2 +: 3 +: EmpT, 1] =:= 2]
summon[Elem[1 +: 2 +: 3 +: EmpT, 2] =:= 3]
So that works correctly. Invalid indexing will also cause a compilation error, as expected. The next step, was to use the same inline function above and adapt that to the use of the return type match. I used this function:
inline def get[L <: Tup, N <: Int](l: L, n: N): Elem[L, n.type] =
inline if n < 0
then
error("Can only select a positively indexed element")
inline l match
case _: EmpT =>
error("Element index exceeds length of list.")
case list : TCons[_,_] =>
inline if n < 0
then
error("Unexpected value.")
else inline if n == 0
then
list.head.asInstanceOf[Elem[L, n.type]]
else
get(list.tail,n-1).asInstanceOf[Elem[L, n.type]]
val t1b = get(1 +: 2 +: 3 +: EmpT, 0)
assert(t1b == 1)
val t2b = get(1 +: 2 +: 3 +: EmpT, 1)
assert(t2b == 2)
val t3b = get(1 +: 2 +: 3 +: EmpT, 2)
assert(t3b == 3)
But the last call to get
fails with:
Match type reduction failed since selector _
matches none of the cases
case Playground.TCons[x, xs] => (-1 : Int) match {
case (0 : Int) => x
case compiletime.ops.int.S[n1] => Playground.Elem[xs, n1]
}
Th error always occurs for the last element of the list. I have tried several variations, but with no success. For example, in the version above I explicitly test for n < 0
, but that does not seem to work.
I have been trying to figure out how the type matching ends up with a n.type < 0
but have failed. Can anyone tell me what is the issue here?
Full code can be found here:
TIA