TypeScript ก็เหมาะกับการพัฒนาคอมไพเลอร์อย่างน่าประหลาดใจ
(matklad.github.io)- ภาษาที่ใช้พัฒนาเครื่องมือสายคอมไพเลอร์นั้นตามธรรมเนียมมักแบ่งเป็น OCaml และ C++ แต่สำหรับการทดลองภาษาเล็ก ๆ TypeScript ก็อาจเป็นตัวเลือกที่ใช้งานได้คล่องแบบภาษาในตระกูล ML
- Rust ผสานข้อดีของ ML และ C++ พร้อมให้ความสามารถด้านมัลติเธรดที่ปลอดภัย แต่ต้องคอยจำลองการจัดวางข้อมูลในหน่วยความจำ จึงอาจเป็นภาระเกินไปสำหรับ ต้นแบบขนาดเล็ก
- Deno ช่วยให้เริ่ม การทดลองภาษาด้วย TypeScript ได้รวดเร็ว ด้วยไบนารีเดียว มี linting และ formatting ในตัว ไม่มีขั้นตอนคอมไพล์ และมี task runner กับ watch mode
- ตัวอย่าง type checker ใช้การผสมกันของ AST แบบ generic, tagged union, visitor และ bottom-up transform เพื่อแปลง
Expr<void>เป็นExpr<Type>และใส่TypeErrorเป็นค่าชนิดข้อมูลเพื่อลด ข้อผิดพลาดลูกโซ่ - TypeScript อาจเป็นเครื่องมือที่มีประสิทธิภาพสำหรับการแฮ็กภาษาเล็ก ๆ เพราะมี autocomplete, ระบบชนิดข้อมูลที่ยืดหยุ่น และคุณสมบัติรันไทม์ที่ถอยไปใช้แนวทาง dynamic ได้เมื่อจำเป็น
ภาพเดิมของการเลือกภาษาเพื่อใช้พัฒนา
- เมื่อสร้างเครื่องมือแนวคอมไพเลอร์ การเลือกภาษาที่ใช้พัฒนามักมีอยู่สองแนวทางใหญ่
- งานที่เน้นตัวภาษา เช่นสเปกเชิงรูปแบบหรือภาษาของเล่นสำหรับงานอดิเรก มักเหมาะกับ OCaml
- ตัวอย่างเช่น plzoo และ WebAssembly reference interpreter
- งานที่เน้นการติดตั้งใช้งานจริงและต้องพร้อมสำหรับโปรดักชัน มักเลือก C++
- LLVM, clang, v8, HotSpot ล้วนพัฒนาด้วย C++
- Rust ได้รับอิทธิพลโดยตรงจาก ML และ C++ รวมข้อดีของทั้งสองภาษาไว้ด้วยกัน และยังมีจุดแข็งเฉพาะตัวอย่างมัลติเธรดที่ปลอดภัย
- อย่างไรก็ตาม บนสเปกตรัมนี้มันเอนมาทาง ความพร้อมระดับโปรดักชัน มากกว่า
- ระบบ build ที่ “แค่ใช้งานได้เลย” ช่วยการทำต้นแบบได้ก็จริง แต่ก็มาพร้อมความซับซ้อนเพิ่มจากการต้องจำลองการจัดวางข้อมูลจริงในหน่วยความจำ
ข้อดีของแนวทาง index ใน Rust และภาระสำหรับโค้ดขนาดเล็ก
- คำแนะนำที่พบบ่อยเมื่อสร้างคอมไพเลอร์ด้วย Rust คือหลีกเลี่ยง pointer แล้วใช้ index แทน
- index มีข้อดีหลายอย่างใน codebase ขนาดใหญ่
- สามารถเก็บ side table ไว้ในโมดูลที่เกี่ยวข้องได้ ทำให้ coupling ต่ำลง
- index ที่เป็น
u32และการจัดวางแบบ struct-of-arrays เอื้อต่อประสิทธิภาพ - ทำ serialization หรือเชื่อมเข้ากับเฟรมเวิร์ก incremental compilation ได้ง่าย ทำให้กลยุทธ์การคำนวณยืดหยุ่นขึ้น
- แต่ในการเขียนโปรแกรมขนาดเล็ก แนวทางแบบ index เองก็กลายเป็นเรื่องจุกจิก และสำหรับการทดลองเชิงงานอดิเรก ภาระนี้อาจร้ายแรงถึงขั้นทำให้ไม่อยากทำต่อ
- OCaml ยังให้ความรู้สึกเก่าอยู่บ้าง และในบริบทนี้จึงมีการพิจารณาว่า TypeScript จะใช้เป็นทางเลือกในบทบาทที่คล้ายกับ ML ได้หรือไม่
Deno และ TypeScript กับสภาพแวดล้อมทดลองที่เริ่มได้เร็ว
- ใช้ deno เป็นสภาพแวดล้อมเริ่มต้น
- ให้ประสบการณ์แบบ พร้อมใช้ทันที สำหรับ TypeScript
- จุดนี้เป็นความลำบากใน OCaml ส่วน Rust ดีกว่า OCaml หรือ C++ แต่ Deno ก็ยังให้ประสบการณ์ที่ง่ายกว่า Rust
- ประสบการณ์พัฒนาของ Deno เหมาะกับการแฮ็ก PLT ขนาดเล็ก
- เป็นไบนารีเดียว
- มี linting และ formatting ในตัว
- ไม่มีขั้นตอนคอมไพล์แยกต่างหาก
- มี task runner และ watch mode ในตัว
- ตัว TypeScript เองก็มี ระบบชนิดข้อมูล ที่ยืดหยุ่นพอ และมีภาระด้านไวยากรณ์ไม่มาก
แพตเทิร์นของ TypeScript จากตัวอย่าง type checker ขนาดเล็ก
- AST เริ่มต้นจากนิพจน์ที่มีข้อมูลตำแหน่งในไฟล์
Locationมีfile,line,column- ใน TypeScript สตริงก็เป็นแค่
stringตัวเลขก็เป็นแค่numberจึงไม่ต้องกังวลกับการแยกแบบusizeกับu32
- นิพจน์แยกตำแหน่งกับ kind ออกจากกัน และต่อมาทำให้ข้อมูลที่เกี่ยวข้องเป็น generic ในรูป
Expr<T>- นิพจน์ทันทีหลัง parse จะมีข้อมูลเป็น
void - นิพจน์ที่ผ่าน type checker แล้วจะมีข้อมูลเป็น
Type - ฟังก์ชัน type inference รับ
ast.Expr<void>และคืนค่าast.Expr<Type>
- นิพจน์ทันทีหลัง parse จะมีข้อมูลเป็น
- TypeScript ไม่ได้เพิ่มพฤติกรรมรันไทม์ให้อัตโนมัติ ดังนั้นหากต้องการ match กับ union type ก็ต้องใส่ข้อมูลชนิดที่รันไทม์เอง
- ฟิลด์อย่าง
tag: "binary",tag: "if"ทำหน้าที่นี้ tag: "binary"หมายความว่า ณ รันไทม์ ค่านั้นเป็นสตริง"binary"ได้เท่านั้น
- ฟิลด์อย่าง
- boolean literal และ int literal มีรูปแบบแทบเหมือนกัน จึงถูกทำให้เป็นนามธรรมด้วย
ExprLiteral<T, V, Tag>ExprBool<T>คือExprLiteral<T, boolean, "bool">ExprInt<T>คือExprLiteral<T, number, "int">
- ค่าชนิดข้อมูลแบ่งเป็น
TypeBool,TypeIntและมี singleton value ชื่อเดียวกันให้ใช้ด้วย- TypeScript ลบชนิดข้อมูลทิ้งทั้งหมด จึงทำให้ชื่อฝั่ง type และชื่อฝั่ง value อยู่คนละ namespace
- ใช้คุณสมบัตินี้เพื่อกำหนดให้ type และ value ใช้ชื่อเดียวกันได้
Visitor, transform, ชนิดข้อมูลข้อผิดพลาด, desugaring
switchของ TypeScript เป็น statement ไม่ใช่ expression ดังนั้นเพื่อจัดการ kind ของนิพจน์ให้สะดวกจึงนิยาม visitor ขึ้นมา- เมธอด
bool,int,binary,ifจัดการแต่ละ kind ตามลำดับ - autocomplete ของ editor ช่วยทั้งการเขียน
switchcase และการ implement visitor
- เมธอด
transform<U, V>เป็นฟังก์ชัน traversal แบบทั่วไปที่แปลงExpr<U>เป็นExpr<V>- การแปลงทำแบบ bottom-up
- ตอนเยี่ยมชมโหนดภายใน นิพจน์ย่อยจะถูกแปลงไว้แล้ว จึงทำให้ชนิดของ visitor เป็น
Visitor<V, V>ไม่ใช่Visitor<U, V>
- หากอาศัยความเป็นภาษาชนิดข้อมูลแบบ dynamic ของ TypeScript ก็สามารถใช้
Object.keysเพื่อสร้าง traversal ที่ทั่วไปกว่านี้ได้- ถึงอย่างนั้นก็ยังคงลายเซ็นฟังก์ชันแบบ static ไว้ได้
- ในตัวอย่างนี้ไม่ได้จำเป็นนัก แต่หากต้องการก็มีช่องให้ถอยไปใช้วิธี dynamic ได้
- ข้อผิดพลาดด้านชนิดข้อมูลไม่ได้สะสมไว้ในอาร์เรย์เป็น side effect แต่แสดงเป็นชนิดข้อมูล
TypeErrorTypeจึงกลายเป็นTypeBool | TypeInt | TypeErrorTypeErrorมีtag: "Error",location,messagetype_equalจะคืนค่า true ถ้าฝั่งใดฝั่งหนึ่งเป็นErrorเพื่อป้องกัน ความล้มเหลวต่อเนื่องเป็นลูกโซ่
- type checker สุดท้ายตรวจทั้งนิพจน์ binary และ if
- ถ้า operand ซ้ายและขวาของนิพจน์ binary มีชนิดต่างกัน จะคืนข้อผิดพลาด
"binary expression operands have different types" - ถ้าเงื่อนไขของนิพจน์ if ไม่ใช่ boolean จะคืนข้อผิดพลาด
"if condition is not a boolean" - ถ้า then branch กับ else branch มีชนิดต่างกัน จะคืนข้อผิดพลาด
"if branches have different types"
- ถ้า operand ซ้ายและขวาของนิพจน์ binary มีชนิดต่างกัน จะคืนข้อผิดพลาด
- ผลลัพธ์สุดท้ายยังต้องเขียน type อยู่พอสมควร แต่ autocomplete ช่วยได้มาก ความรู้สึกเหมือนต้องฝืนสู้กับภาษามีน้อย และรูปแบบโดยรวมก็เข้ากับปัญหาอย่างเป็นธรรมชาติ
- เหตุผลที่สรุปว่า TypeScript มีประสิทธิภาพสำหรับเครื่องมือแฮ็กภาษาเล็ก ๆ มีอยู่สามข้อ
- Deno เป็น สคริปต์รันไทม์ ที่เล็ก ปิดจบในตัว ทรงพลัง และเหมาะกับเวิร์กโฟลว์การพัฒนาที่มีประสิทธิภาพ
- เครื่องมือของ TypeScript ใช้งานกับ IDE ได้ดี มีประโยชน์และช่วยเพิ่มประสิทธิภาพ อีกทั้งด้วย Deno จึงแทบไม่ต้องตั้งค่า
- ตัวภาษาแข็งแรงทั้งฝั่งรันไทม์และคอมไพล์ไทม์ สามารถแสดงโครงสร้างที่ซับซ้อนพอสมควรผ่าน type ได้ และยังถอยไปใช้แนวทาง dynamic ได้เมื่อจำเป็น
- อีกแนวคิดหนึ่งที่เสนอไว้คือการ desugar syntax sugar จำนวนมากแบบ type-safe
- ทำให้
ExprและExprKindไม่ได้พารามิเตอร์แบบข้อมูลที่เกี่ยวข้อง แต่พารามิเตอร์วนกลับไปที่ExprKindทั้งก้อน ExprKindCoreแทนชุดนิพจน์พื้นฐานExprKindSugarรวมทั้งนิพจน์พื้นฐานและนิพจน์ที่ desugar กลับเป็นนิพจน์พื้นฐานได้desugar(expr: ExprSugar): ExprCoreจะย่อนิพจน์ syntax sugar ให้เป็นนิพจน์แกนหลักdesugar_one(expr: ExprKindSugar<ExprCore>): ExprKindCore<ExprCore>จะทำการแปลงหนึ่งขั้น เมื่อ subexpression ถูก desugar มาแล้วทั้งหมด
- ทำให้
1 ความคิดเห็น
ความเห็นจาก Hacker News
TypeScript โดยรวมเป็นภาษาที่ยอดเยี่ยม และจุดที่ฟังก์ชันเป็นอ็อบเจ็กต์ที่มีพร็อพเพอร์ตี/เมธอดได้นั้นถูกประเมินค่าต่ำไป
สามารถรันอาร์เรย์ของฟังก์ชันเหมือนเป็นคำสั่ง แล้วค่อยเพิ่มคำอธิบายอย่าง
helpในภายหลัง หรือเพิ่มสถานะด้วย closure/partial application ได้ จึงไม่จำเป็นต้องรีบกำหนดคลาสอย่างCommandตั้งแต่ต้นใน OOP การตั้งชื่อให้เร็วเกินไปมักสร้างความขัดแย้งมากมาย และมองว่าการส่งค่าที่จำเป็นเข้าไปในฟังก์ชันนั้นเป็นธรรมชาติกว่าโครงสร้างแบบ
VideoCompressor#compress()ภาษาอื่น ๆ ที่รองรับอ็อบเจ็กต์ที่ทำงานเหมือนฟังก์ชันถูกรวบรวมไว้ที่ https://en.wikipedia.org/wiki/Function_object
เช่น handler ใน
net/httpโครงสร้างอาจ implement เมธอดserveก็ได้ หรือ handler function อาจเรียกตัวเองเพื่อทำให้ตรงตาม interface ก็ได้ใน Clojure สามารถแนบ metadata กับ
varของฟังก์ชันเพื่อทำคล้าย ๆ กันได้ และเพราะเป็น Lisp จึงทำแทบอะไรก็ได้ด้วย macroอีกทั้ง CSP channel ของ
core/asyncยังแยกการประมวลผลกับการสื่อสารออกจากกัน ช่วยเลี่ยงปัญหาเรื่องสีของฟังก์ชันอย่าง callback/promise/async/awaitและทำให้คำสั่งต่าง ๆ ทำงานเหมือน producer ที่ส่งผลลัพธ์ไปยัง channel ได้ไม่ว่าเมธอดจะชื่ออะไร ก็สามารถใช้ในบริบทของฟังก์ชันได้ และไม่จำเป็นต้องอ้างถึงชื่อเมธอดเฉพาะ
อย่างไรก็ตาม ไม่ค่อยชอบกรณีที่ฟังก์ชันมีพร็อพเพอร์ตีเพื่อเก็บสถานะไว้ และอาจเปลี่ยนพฤติกรรมแม้เรียกด้วยอาร์กิวเมนต์ชุดเดิม มองว่าข้อดีสำคัญของ functional programming คือการหลุดพ้นจาก state แบบ OOP
Funcสำหรับฟังก์ชันที่มีค่าคืนกลับ และActionสำหรับฟังก์ชันที่ไม่มีค่าคืนกลับlambda expression ของ JavaScript ค่อนข้างคล้ายกับ C# และ signature ของฟังก์ชันใน TypeScript กับ C# ก็คล้ายกันพอสมควร
มี repository เล็ก ๆ ที่แสดงความคล้ายกันของ JavaScript, TypeScript และ C#: https://github.com/CharlieDigital/js-ts-csharp
สกรีนช็อตที่แสดง logic เดียวกันเป็น JS/TS/C# เคียงกัน: https://github.com/CharlieDigital/js-ts-csharp/blob/main/js-...
ก็ไม่ได้แปลกใจมากนัก มองว่า TypeScript สุดท้ายแล้วก็เป็นอีกภาษาหนึ่งที่ค่อย ๆ รับความสามารถจำนวนมากจากตระกูล ML เข้ามาอย่างยากลำบาก
เพราะไม่มี pattern matching แท้ ๆ จึงไม่สะดวกเท่า OCaml แต่ถ้าเทียบกับกลุ่มอย่าง C#, Swift, Dart, Kotlin ก็ถือว่าอยู่ในระดับใช้ได้
TypeScript มีระบบ type ที่ทรงพลัง แต่ standard library พื้นฐานและตัวภาษายังน่าเสียดาย และไม่มี pattern matching/switch expression
Dart มี object model แบบปิด ทำให้เสรีภาพเชิง dynamic ต่ำกว่า ระบบ type และ expression ก็อ่อนกว่า อีกทั้งแทบไม่มี facility ด้าน metaprogramming จึงต้องพึ่ง boilerplate แบบ Java และ code generator
C# เป็นภาษาที่ใกล้กับความสามารถแบบ ML มากที่สุดในบรรดาภาษาที่กล่าวถึง แต่ต่างจาก TypeScript ตรงที่ไม่มี sum type ทำให้หลายอย่างยุ่งยากกว่า
เช่น นำ parser ที่รู้จัก
{ ... }ไปประกอบกับ parser อื่น ๆ และนิยาม statement ให้เป็นอย่างใดอย่างหนึ่งระหว่าง control flow/declaration/assignment ได้การประมวลผล list แบบ ML และ pattern matching แสดงพลังได้ดีมากเมื่อจัดการ intermediate representation
แม้ในบทความก็ต้องอ้อมไปใช้ visitor pattern เพราะ
switchไม่ใช่ expression และการรองรับ iterator ของ JavaScript ก็ขาด ๆ เกิน ๆ อย่างประหลาดมี
.map()แต่ใช้ได้เฉพาะกับ array และไม่สามารถนำไปใช้กับ iterator ทั่วไปได้โดยตรงแม้มีความต่างมาก แต่การสลับไปมาไม่ยาก และโดยส่วนตัวแม้จะชอบ Kotlin มากกว่า ก็ยังใช้ได้ทั้งคู่
ชวนคิดว่า ถ้า TypeScript หลุดจากความเข้ากันได้กับ JavaScript แล้วคอมไพล์ไปเป็น WASM จะเป็นอย่างไร Kotlin กำลังเพิ่ม WASM compiler และมี JS transpiler อยู่แล้ว โดยโค้ดเดียวกันโหลดบน WASM ได้เล็กกว่าและเร็วกว่า
JavaScript บนเบราว์เซอร์ไม่ใช่ target สำหรับการคอมไพล์ที่ดี และเมื่อโปรเจกต์ใหม่ ๆ เริ่มด้วย TypeScript ตั้งแต่ต้นมากขึ้น เหตุผลที่ว่าต้องย้ายจาก JavaScript เดิมได้ง่าย ๆ ก็อ่อนลงเรื่อย ๆ
ถ้าสลับใช้ Rust กับ TypeScript จะเห็นชัดมากว่าขาดความสามารถอย่าง tagged enum เพียงใด
ข้อเสนอ ADT enum ดูเหมือนจะหยุดไปแล้ว เลยสงสัยว่ามีความพยายามอื่นอยู่หรือไม่: https://github.com/Jack-Works/proposal-enum/discussions/19
ระบบ type ของ TypeScript นั้นน่าสนุก แต่ก็อดสงสัยไม่ได้ว่าถ้าคอมไพเลอร์เขียนด้วยภาษาที่คอมไพล์แล้วจะเร็วขึ้นอีกแค่ไหน
แน่นอนว่าต้องมีสมมติฐานใหญ่คือ “การ implement ที่ดี”
swcและesbuildไม่ใช่ตัวเปรียบเทียบที่ดีนัก เพราะความเร็วที่เพิ่มขึ้นส่วนใหญ่เกิดจากการตัดไวยากรณ์เฉพาะของ TypeScript ออกเพื่อสร้าง JavaScripttscมักจะช้าเฉพาะตอนรันครั้งแรก และถ้าใช้แฟล็กincrementalหรือโหมด watch แบบ--transpile-onlyเวลาคอมไพล์โดยทั่วไปจะลดลงต่ำกว่า 100ms ทำให้แทบไม่รู้สึกต่างจาก SWC หรือ ESBuildประเด็นคือ ถ้าการตรวจ type ของโปรแกรมขนาดกลางใช้เวลา 20 วินาที โดยมากไม่ได้เป็นเพราะมันเป็น JS แต่เป็นเพราะ type เกิด combinatorial explosion
runtime อื่นอาจให้ประโยชน์ด้าน parallelism หรือเวลาเริ่มต้นได้ แต่เขาบอกว่ายังไม่เคยเห็น benchmark ที่เน้น CPU ซึ่งรองรับการเพิ่มความเร็วโดยรวม 20 เท่า
แล้วพอใช้เวลาทั้งวัน debug logic ของ type แค่ 5 บรรทัด ก็จะเริ่มถามตัวเองว่ามาถึงจุดนี้ได้ยังไง
เป็นโปรเจกต์ที่เขียนด้วย Rust โดยนักพัฒนาหลักของ SWC โดย SWC คอมไพล์ TS เป็น JS ส่วน STC ตรวจ type ของ TS
ว่ากันว่า isolated declaration mode ที่กำลังจะมาในอนาคตสามารถลดเวลาคอมไพล์ได้สูงสุดถึง 75%: https://github.com/microsoft/TypeScript/pull/53463#issuecomm...
สำหรับคนที่เพิ่งเริ่มเรียนคอมไพเลอร์ ขอแนะนำหนังสือเล่มนี้: https://keleshev.com/compiling-to-assembly-from-scratch/
ผู้เขียนอธิบายว่าเขาสร้างคอมไพเลอร์เป็น ARM assembly 32 บิตด้วย subset ของ TypeScript และมันดูแทบเหมือน pseudocode จึงเข้าถึงง่าย
หนังสือแบ่งเป็นสองส่วน ส่วนแรกสร้าง interpreter ของภาษาโดยใช้ Java และส่วนที่สองคอมไพล์ภาษาเดียวกันเป็น bytecode แล้ว implement virtual machine สำหรับ bytecode ด้วย C
โค้ดทุกบรรทัดของ implementation ถูกอ้างอิงไว้ในหนังสือ
หากต้องการหลีกเลี่ยง visitor pattern สามารถใช้ IIFE style switch ผ่านฟังก์ชัน utility
runได้แค่ใช้
switchภายในฟังก์ชันที่เรียกใช้ทันที แล้วให้มัน infer return typeถ้าไม่ชอบ
()ท้ายสุดจนอยากเลี่ยง IIFE ก็แยกนิยามฟังก์ชันไว้ต่างหากแล้วค่อยเรียกก็ได้กำลังเขียนคอมไพเลอร์ด้วย TypeScript อยู่ และเห็นด้วยว่ามันไม่ได้แย่อย่างที่คิด
ตอนแรกเริ่มด้วย Deno เหมือนผู้เขียน แต่สุดท้ายย้ายไป Bun แม้จะยังมีส่วนที่หยาบ ๆ อยู่บ้าง แต่พอใจกว่า Deno และเร็วมาก
ในฐานะ frontend ของ parser generator มาตรฐาน Ohm-js ใช้งานค่อนข้างสบาย: https://ohmjs.org/
คอมไพเลอร์
tscทางการใหญ่เกินไปมาก จึงไม่แนะนำให้อ่าน และถ้าอยากดูว่าtscทำงานอย่างไร mini-typescript เหมาะกว่า: https://github.com/sandersn/mini-typescript/โดยเฉพาะ branch
centi-typescriptมีประโยชน์: https://github.com/sandersn/mini-typescript/tree/centi-types...หวังว่า WASM จะสามารถเข้าถึง GC และ DOM ได้
นึกว่า TypeScript จะมี overhead เพิ่มเพราะ interface แต่ผิดคาด
เริ่มสงสัยว่าจะนำไปใช้กับด้านอื่นได้ไหม เช่น การ parse ภาษา จะพอใช้ได้หรือเปล่า
มันหายไปทั้งหมดในกระบวนการคอมไพล์
ผลลัพธ์ไม่ได้เหนือความคาดหมายมากนัก เพราะ ตัวคอมไพเลอร์ TypeScript เองก็เขียนด้วย TypeScript
TypeScript ในฐานะ ML ได้รับการพิสูจน์แล้วทุกวันใน production environment หนัก ๆ
เคยลองเขียนคอมไพเลอร์ด้วย C# แล้ว สิ่งที่ดูพิเศษในที่นี้น่าจะมีแค่ union type
โดยส่วนตัวเลือกเลี่ยงความเยิ่นเย้อของ visitor pattern และกำลังรอฟีเจอร์ enum แบบปิดเพื่อให้ตรวจสอบความครบถ้วนได้ตอนคอมไพล์
ทางเลือกทั่วไปมักจะฝืน ๆ เช่น ใช้คลาสหนึ่งแทน sum type โดยมี nullable property N ตัว แล้วพึ่งเงื่อนไขในเอกสารว่า “มีได้เพียงตัวเดียวที่ non-null เสมอ” หรือให้หลายคลาสสืบทอดจากคลาสร่วม ทั้งสองแบบให้ความรู้สึกหนัก
ถ้าจะสร้าง union type หลายชุดที่มีส่วนทับซ้อนกัน ทั้งสองแนวทางก็ต้องอาศัยการทำซ้ำหรือการจัดองค์ประกอบที่ฉลาด