Scala 片段到 TypeScript如何转换抽象类型成员

问题描述

我在 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)
  }
}

现在,我正在尝试将这个东西转换为 TypeScript。由于我们没有 Abstract Type Members 功能,我似乎无法找到不需要在某些时候进行类型转换的解决方案。

我目前在 TypeScript 中有什么(也可以在 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,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>> 

我感兴趣的是我们是否可以避免在这里投射:

return new Cons(this.head,That>)

TypeScript 似乎理解该值实际上是 Append<B,That>,因为它不允许转换为任何不同的内容,例如Append<B,B> 或类似的东西。但是因为我们使用了 with 中的 abtract class RowSet,所以我们最终得到了 Cons<A,RowSet>

我们能否以不同的方式定义 RowSet,以便 TypeScript 在没有我们帮助的情况下正确推断所有内容?也许抽象类型成员的转换方式不同(从 Scala 转换时)?

解决方法

感谢 Oleg Pyzhcov 的评论,我能够在没有任何手动类型转换的情况下使其工作。 F-bounded polymorphism 被建议作为解决这个问题的一种方法,事实证明它在这里确实有帮助

解决方案看起来像这样,不需要类型转换,一切都按预期进行

abstract class RowSet<T extends RowSet<T>> {
    abstract with<That extends RowSet<That>>(that: That): Append<T,That>
}

type Append<This extends RowSet<This>,That extends RowSet<That>> =
    This extends Cons<infer A,infer B> ? Cons<A,Append<B,That>> : That;

class Empty extends RowSet<Empty> {
    public with<That extends RowSet<That>>(that: That): That {
        return that;
    }
}

class Cons<A,B extends RowSet<B>> extends RowSet<Cons<A,B>> {
    constructor(public readonly head: A,public readonly tail: B) {
        super();
    }

    public with<That extends RowSet<That>>(that: That): Cons<A,That>> {
        return new Cons(this.head,this.tail.with(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>> 

您可以在Playground

上查看