ค้นหาเว็บไซต์

typescript - คำนวณจุดคงที่ของชนิด


สมมติว่าเรามีฟังก์ชันประเภทบางประเภท F<T> และเราต้องการคำนวณจุดคงที่ของมัน type X=F<X> แม้ว่าสิ่งนี้อาจใช้ได้กับประเภทที่เป็นวัตถุ

interface Tree1<T> { left: T; right: T }
interface Tree extends Tree1<Tree> {}
declare const t: Tree;
const r = t.left.right; // OK

สำหรับฟังก์ชันประเภททั่วไปมากขึ้น มันไม่ได้:

type Tree1<T> = null | { left: T; right: T }
interface Tree extends Tree1<Tree> {} // can't extend not-object

หากไม่มีการคำนวณระดับประเภทแบบเลื่อนออกซึ่งกำหนดโดย interface จะเป็นเรื่องยากที่จะหลีกเลี่ยงการสร้างอินสแตนซ์ไม่สิ้นสุด สิ่งที่ดีที่สุดที่ฉันทำได้คือเพียงเลื่อนมันออกไป:

interface Tree1<T> { left: T; right: T }
type Temp1<T> = { a: null | [T[keyof T], T[keyof T]] }
interface Temp2 extends Temp1<Temp2> {}
declare const t: Temp2;
const r = t.a; // Type instantiation is excessively deep

นอกจากนี้สามารถแก้ไขได้โดยการใส่โหนดที่ไม่จำเป็นเข้าไปในต้นไม้ แต่สิ่งนั้นไม่ใช่สิ่งที่ฉันต้องการ

type Tree1<T> = null | { left: T; right: T }
type Tree = Tree1<{ref: Tree}> // OK

ทั้งสองไม่ได้กำหนด Tree โดยตรง

type Tree = null | { left: Tree, right: Tree }

จะคำนวณจุดคงที่ที่หายากนั้นได้อย่างไร?

type Tree1<T> = null | { left: T; right: T }
type Tree = ???
const t: Tree = {left: null, right: {left: null, right: null}};

คำตอบ #1

ฉันไม่รู้วิธีสร้างจุดคงที่เชิงตัวอักษรของฟังก์ชันชนิด แต่คุณสามารถสร้างชนิดที่อ้างอิงตัวเองโดยทางอ้อมผ่านคุณสมบัติ ขึ้นอยู่กับว่าคุณใช้มันทำอะไร นั่นอาจเพียงพอแล้ว

ฉันมาที่คำถามของคุณเพราะฉันกำลังคิดเกี่ยวกับวิธีการพิมพ์ต้นไม้ไวยากรณ์นามธรรม โดยเฉพาะอย่างยิ่ง ฉันต้องการพิมพ์ AST สำหรับกลุ่มภาษาที่เกี่ยวข้องกัน ตัวอย่างเช่น เราอาจมีชนิดสำหรับนิพจน์ที่เกี่ยวข้องกับการบวกตัวแปรเข้าด้วยกัน เช่น 'a + b + c' และเราอาจมีชนิดอีกหนึ่งสำหรับนิพจน์ที่เกี่ยวข้องกับตัวแปร การบวก และการคูณ เช่น 'a*b + c'.

ฉันต้องการให้สิ่งเหล่านี้เป็นชนิดแบบคงที่จริง ๆ — ฉันไม่ต้องการใช้เพียงชนิด AST ขนาดใหญ่เพียงชนิดเดียวเพื่อควบคุมทั้งหมด แต่ฉันก็ต้องการให้ชนิดเหล่านี้สามารถขยายเพิ่มเติมได้ด้วย ฉันไม่ต้องการที่จะต้องกำหนดไวยากรณ์ทั้งหมดใหม่ทุกครั้งที่ฉันเพิ่มชนิดใหม่ของโหนด AST

ดังนั้นฉันจึงคิดว่า ถ้าคุณสามารถมีพารามิเตอร์ทั่วไปที่สอดคล้องกับรูปแบบภาษาต่าง ๆ คุณก็สามารถกำหนดโหนด AST แบบนี้ได้:

type Plus<α extends Grammer> = { 
    kind: "plus"
    left: Expr<α>
    right: Expr<α>
}

จากนั้นคุณสามารถกำหนดเวอร์ชันทั่วไปของไวยากรณ์ได้ดังนี้:

type AbelianGroupBaseGrammer<α extends Grammer> = Variable<α> | Plus<α>

และเปลี่ยนมันให้เป็นมอนอไทป์โดยการสร้างจุดคงที่

AbelianGroupGrammer = AbelianGroupBaseGrammer<AbelianGroupGrammer>

คุณสามารถขยายมันไปยังภาษาที่ใหญ่กว่าได้ดังนี้

type RingBaseGrammar<α extends Grammer> = AbelianGroupBaseGrammer<α> | Times<α>

นี่ใช้ไม่ได้ใน TypeScript คุณจะได้รับข้อผิดพลาด Type alias 'AbelianGroupGrammer' อ้างอิงตัวเองแบบวงกลม

แต่ถ้าคุณซ่อนการอ้างอิงตัวเองไว้ภายในคุณสมบัติของวัตถุ คุณก็สามารถได้ผลลัพธ์เหมือนกัน เวอร์ชันนี้ทำการตรวจสอบประเภทโดยไม่มีข้อผิดพลาด:

type Grammer = {
    "expression": unknown
}

type Expr<α extends Grammer> = α["expression"]

type Plus<α extends Grammer> = { 
    kind: "plus"
    left: Expr<α>
    right: Expr<α>
}

type Times<α extends Grammer> = { 
    kind: "times"
    left: Expr<α>
    right: Expr<α>
}

type Variable<α> = { 
    kind: "variable"
    name: string
}

type AbelianGroupBaseGrammer<α extends Grammer> = { 
    "expression": Variable<α> | Plus<α>
}

type AbelianGroupGrammer = { 
    "expression": Expr<AbelianGroupBaseGrammer<AbelianGroupGrammer>>
}

type RingBaseGrammer<α extends Grammer> = { 
    "expression": Expr<AbelianGroupBaseGrammer<α>> | Times<α>
}

type RingGrammer = {
    "expression": Expr<RingBaseGrammer<RingGrammer>>
}

const x : Expr<AbelianGroupGrammer> = {kind: "variable", name: "x"}
const y : Expr<AbelianGroupGrammer> = {kind: "variable", name: "y"}
const x_plus_y : Expr<AbelianGroupGrammer> = {kind:"plus", left: x, right: y}
const x_times_x_plus_y : Expr<RingGrammer> = {kind:"times", left: x, right: x_plus_y}

function group2ring(a : Expr<AbelianGroupGrammer>) : Expr<RingGrammer> { 
    return a
}