I have a brief example of a value level and type level list in Scala
sealed trait RowSet {
type Append[That <: RowSet] <: RowSet
def with[That <: RowSet](that: That): Append[That]
}
object RowSet {
case object Empty extends RowSet {
type Append[That <: RowSet] = That
override def with[That <: RowSet](that: That): Append[That] = that
}
case class Cons[A, B <: RowSet](head: A, tail: B) extends RowSet { self =>
type Append[That <: RowSet] = Cons[A, tail.Append[That]]
override def with[That <: RowSet](that: That): Append[That] = Cons(head, tail ++ that)
}
}
Currently attempting to convert this to TypeScript. Since TypeScript lacks the Abstract Type Members feature, resolving without type casting seems challenging.
My current TypeScript implementation (also accessible on Playground)
abstract class RowSet {
abstract with<That extends RowSet>(that: That): RowSet
}
type Append<This extends RowSet, That extends RowSet> =
This extends Cons<infer A, infer B> ? Cons<A, Append<B, That>> : That;
class Empty extends RowSet {
public with<That extends RowSet>(that: That): That {
return that;
}
}
class Cons<A, B extends RowSet> extends RowSet {
constructor(public readonly head: A, public readonly tail: B) {
super();
}
public with<That extends RowSet>(that: That): Cons<A, Append<B, That>> {
return new Cons(this.head, this.tail.with(that) as Append<B, That>)
}
}
const x = new Cons(5, new Empty) // Cons<number, Empty>
const y = new Cons("hi", new Empty) // Cons<string, Empty>
const z = x.with(y) // Cons<number, Cons<string, Empty>>
Exploring ways to avoid casting in this scenario:
return new Cons(this.head, this.tail.with(that) as Append<B, That>)
Although TypeScript recognizes the value as Append<B, That>
, the casting is needed due to using the with
method from abstract class RowSet
, resulting in Cons<A, RowSet>
.
Is there an alternative definition for RowSet in TypeScript that would allow TypeScript to infer everything correctly without manual intervention?