Scala-koodi TypeScriptiksi (kuinka muuntaa abstrakteja tyyppijäseniä)


Minulla on pieni ote arvotason ja tyyppitason listasta Scalassa

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

Nyt yritän muuntaa tämän TypeScriptiin. Koska meillä ei ole Abstract Type Members -ominaisuutta, en tunnu löytävän ratkaisua, joka ei vaatisi tyypin muuntamista jossain vaiheessa.

Mitä minulla on tällä hetkellä TypeScriptissä (myös saatavilla Playgroundissa)

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>> 

Mitä minua kiinnostaa, on se, voidaanko tässä välttää tyyppimuunnosta:

return new Cons(this.head, this.tail.with(that) as Append<B, That>)

Näyttää siltä, että TypeScript ymmärtää, että arvo on itse asiassa Append<B, That>, koska se ei salli muuntaa sitä mihinkään muuhun, esimerkiksi Append<B, B> tai johonkin vastaavaan. Mutta koska käytämme with abtract class RowSet -luokasta, päädymme Cons<A, RowSet>.

Voimmeko määritellä RowSetin eri tavalla, niin että TypeScript päättelee kaiken oikein ilman apuamme? Ehkä abstraktien tyyppijäsenten muuntamiseen (kun muunnetaan Scalasta) on olemassa jokin toinen tapa?

Vastaus #1

Kiitos Oleg Pyzhcovin kommentille pystyin saamaan sen toimimaan ilman manuaalista tyyppimuunnosta. F-rajoitettu polymorfismi ehdotettiin keinona ratkaista tämä ongelma, ja käy ilmi, että se todella auttaa täällä

Ratkaisu näyttää tältä, tyyppimuunnosta ei tarvita, kaikki toimii odotetusti

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, Append<B, 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>> 

Voit tarkistaa sen leikkikentältä