1 คะแนน โดย GN⁺ 2024-05-10 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Datatype99 เป็นไลบรารีแบบใช้แมโครที่มอบ algebraic data type, การทำ pattern matching แบบ exhaustive และการทำ introspection ตอนคอมไพล์สำหรับ C99 แบบล้วน
  • มีเป้าหมายเพื่อจับ variant ที่ระบุชนิดผิด, pattern matching ที่ไม่ครบถ้วน, และการเข้าถึงฟิลด์ที่ผิดพลาดได้ตั้งแต่ ตอนคอมไพล์ โดยต้องการเพียงคอมไพเลอร์ C99 ที่เป็นไปตามมาตรฐาน
  • ภายใน datatype จะถูกขยายเป็น tagged union และ value constructor ส่วน match จะถูกขยายเป็นคำสั่ง switch และ layout ของข้อมูลที่สร้างขึ้นจะเป็นไปตาม semantics ของการสร้างโค้ดแบบกำหนดรูปแบบ
  • การติดตั้งทำได้โดยเพิ่ม datatype99.h และ dependency คือ Metalang99 เข้าไปใน include path และบน GCC/Clang แนะนำให้ใช้ตัวเลือกคอมไพล์เพื่อลดเอาต์พุตข้อผิดพลาดจากการขยายแมโคร
  • สามารถรวมเข้ากับโค้ดเบส C ได้ด้วย #include <datatype99.h> และเป็นที่ทราบว่าสามารถทำงานได้บน GCC, Clang, MSVC, TCC รวมถึงรองรับ C++11 ขึ้นไป

สิ่งที่ Datatype99 มอบให้

  • Datatype99 มอบ algebraic data type ที่ปลอดภัยและเข้าใจง่ายสำหรับ C99
  • ขอบเขตความสามารถครอบคลุมถึงการทำ pattern matching แบบ exhaustive และ การทำ introspection ตอนคอมไพล์
  • ทำงานได้โดยไม่ต้องมีเครื่องมือสร้างโค้ดภายนอก และอิมพลีเมนต์ด้วย C99 ล้วน
  • คุณสมบัติหลัก
    • ความปลอดภัยของชนิดข้อมูล: จับ variant ที่ระบุชนิดผิด, pattern matching ที่ไม่ exhaustive, และการเข้าถึงฟิลด์ที่ผิดพลาดได้ตั้งแต่ตอนคอมไพล์
    • ความพกพา: ต้องใช้เพียงคอมไพเลอร์ C99 ที่เป็นไปตามมาตรฐาน และไม่ต้องใช้ standard library, ความสามารถเฉพาะของคอมไพเลอร์/แพลตฟอร์ม หรือ VLA
    • ความคาดเดาได้: มีการกำหนด semantics ของการสร้างโค้ด ไว้ จึงรับประกันได้ว่า layout ของข้อมูลที่สร้างจะเหมือนเดิมเสมอ
    • ข้อผิดพลาดที่เข้าใจได้: ไลบรารีเองสามารถตรวจจับ syntax error บางส่วนในโค้ดที่ผิดได้
    • กรณีใช้งานจริง: ถูกใช้ใน OpenIPC เพื่อพัฒนาซอฟต์แวร์สตรีมมิงแบบเรียลไทม์สำหรับกล้อง IP รวมถึง อิมพลีเมนต์ RTSP 1.0 และโค้ดปิดอีกราว 50,000 บรรทัด

การติดตั้งและการตั้งค่าการบิลด์

  • Datatype99 ประกอบด้วยไฟล์เฮดเดอร์ datatype99.h เพียงไฟล์เดียวและ dependency Metalang99
  • หากต้องการใช้ในโปรเจกต์ ต้องเพิ่ม datatype99 และ metalang99/include เข้าไปใน include directory
  • บน GCC แนะนำให้กำหนด -ftrack-macro-expansion=0 และบน Clang ให้กำหนด -fmacro-backtrace-limit=1 เพื่อลดเอาต์พุตข้อผิดพลาดจากการขยายแมโครที่ไม่จำเป็น
  • หากใช้ CMake แนะนำให้ใช้ FetchContent
    • datatype99/CMakeLists.txt เริ่มต้นจะดาวน์โหลด Metalang99 v1.13.5 จาก GitHub Releases
    • พฤติกรรมนี้สามารถ override ได้โดยเรียก FetchContent_Declare ไว้ก่อน
  • เฮดเดอร์ที่พึ่งพา Datatype99 สามารถทำเป็น precompiled header ได้ เพื่อลดเวลาคอมไพล์โดยไม่ต้องคอมไพล์ใหม่ทุกครั้งที่ include

วิธีใช้งาน: ใช้ tagged union ให้ปลอดภัยขึ้น

  • โดยพื้นฐานแล้ว Datatype99 คือ syntax sugar สำหรับ tagged union ที่มอบรูปแบบการใช้งานที่ปลอดภัยและกระชับกว่า
  • ใน C ปกติ หากต้องการแทน binary tree ต้องเขียน tag enum และ union ด้วยตัวเอง
  • ใน Datatype99 สามารถประกาศโครงสร้างเดียวกันได้ดังนี้
datatype(
    BinaryTree,
    (Leaf, int),
    (Node, BinaryTree *, int, BinaryTree *)
);
  • ในวิธีแบบ switch ปกติ แม้จะเผลอเข้าถึง tree->data.node หลัง case Leaf: คอมไพเลอร์ก็อาจไม่เตือน
  • แต่เมื่อใช้ match และ of การ bind ตาม variant จะมองเห็นได้เฉพาะในสาขานั้น และหากเข้าถึงอย่างไม่เหมาะสมจะคอมไพล์ไม่ผ่าน
int sum(const BinaryTree *tree) {
    match(*tree) {
        of(Leaf, x) return *x;
        of(Node, lhs, x, rhs) return sum(*lhs) + * x + sum(*rhs);
    }

    return -1;
}
  • ตัวแปรที่ of สร้างให้ เช่น x, lhs, rhs จะเป็นตัวแปรแบบพอยน์เตอร์ จึงสามารถแก้ไขค่าได้
  • การสร้าง variant ใช้ value constructor ที่ถูกสร้างขึ้นภายใน
BinaryTree leaf5 = Leaf(5);
BinaryTree leaf7 = Leaf(7);
BinaryTree node = Node(&leaf5, 123, &leaf7);

ไวยากรณ์และ semantics ของการสร้างโค้ด

  • Datatype99 มีแมโครไวยากรณ์อย่าง datatype, record, match, of, otherwise, MATCHES, ifLet เป็นต้น
  • มีทั้งชื่อแมโครแบบย่อและเวอร์ชันที่ลงท้ายไว้พร้อมกัน
    • ตัวอย่าง: match99, of99, derive99
    • หากต้องการหลีกเลี่ยงการชนกันของชื่อ สามารถกำหนด DATATYPE99_NO_ALIASES ก่อน include datatype99.h
    • สำหรับเฮดเดอร์ของไลบรารี แนะนำให้ใช้แมโครแบบลงท้าย
  • datatype จะสร้างองค์ประกอบต่อไปนี้
    • typedef แบบ forward declaration
    • โครงสร้างสำหรับแต่ละ variant ที่ไม่ว่าง
    • typedef สำหรับชนิดของฟิลด์ใน variant
    • typedef สำหรับ sum type
    • tagged union ที่มี tag enum และ union
    • value constructor แบบ inline static สำหรับแต่ละ variant
    • การเรียก deriver ที่ระบุไว้ใน derive(...)
  • แม้ทุก variant จะว่างเปล่า ตามมาตรฐาน C แล้ว union ก็ยังต้องมีสมาชิกอย่างน้อยหนึ่งตัว จึงมี char dummy; ถูกใส่ไว้
  • record คือ struct ที่มีการกำหนด derivation process และแม้ไม่มีฟิลด์ก็จะสร้าง char dummy; เช่นกัน
  • match จะเปรียบเทียบอินสแตนซ์ของ sum type กับแต่ละ variant ตามลำดับ แล้วรันคำสั่งของสาขาที่ตรง ก่อนจะไปยังคำสั่งถัดไป
  • โครงสร้าง match และ ifLet แบบสมบูรณ์จะถูกขยายเป็น C statement เดียวในแต่ละกรณี
  • MATCHES ใช้ตรวจสอบแบบจริง/เท็จว่าอินสแตนซ์ของ sum type เป็น variant ที่ระบุหรือไม่
  • matches ถูกเลิกใช้แล้ว และแนะนำให้ใช้ MATCHES

derive และ helper attribute

  • derive(...) ใช้สำหรับสร้างโค้ดแบบ global ให้กับ sum type หรือ record
  • deriver ของ sum type จะถูกเรียกในรูปแมโครที่เข้ากันได้กับ Metalang99 โดยรายการ variant จะถูกส่งเป็น tuple list
  • deriver ของ record ก็จะถูกเรียกเป็นแมโครที่เข้ากันได้กับ Metalang99 เช่นกัน โดยรายการฟิลด์จะถูกส่งเป็น tuple list ในรูป (<type>, <field-name>)
  • derive helper attribute คือ อาร์กิวเมนต์แบบมีชื่อ ที่ส่งต่อให้ deriver
  • helper attribute ใช้รูปแบบ object-like macro
#define <variant-name>_<namespace>_<attribute-name> attr(/* attribute value */)
  • แมโครสำหรับจัดการ attribute ที่มีให้
    • DATATYPE99_attrIsPresent / DATATYPE99_ATTR_IS_PRESENT: ตรวจสอบว่ามี attribute หรือไม่
    • DATATYPE99_attrValue / DATATYPE99_ATTR_VALUE: ดึงค่าของ attribute ที่มีอยู่
    • DATATYPE99_assertAttrIsPresent: หากไม่มี attribute ที่จำเป็นจะเกิด fatal error

รูปแบบการใช้งานที่ควรระวัง

  • ห้ามใช้ break/continue ระดับบนสุดภายใน statement ที่ส่งให้ of และ ifLet
    • continue ภายในลูป for/while ด้านในยังใช้ได้
    • ควบคุมลำดับการทำงานระดับบนสุดควรแทนด้วย label และ goto
  • หากต้องการกำหนดอาร์เรย์เป็นพารามิเตอร์ของ variant ต้องใส่ไว้ใน struct แยกต่างหาก
  • binding ที่ of แนะนำเข้ามาจะ mutable เสมอ ดังนั้นหากค่าที่ส่งให้ match เป็น const ต้องระวังไม่ให้แก้ไขมัน
  • หากต้องการให้คำจำกัดความ datatype อ่านง่าย สามารถใช้ // clang-format off และ // clang-format on กับ Clang-Format ได้
  • derive helper attribute ควรถูก #undef เสมอหลังคำจำกัดความ datatype นั้น เพื่อหลีกเลี่ยงการทำให้ namespace ปนเปื้อน
  • หากความหมายของพารามิเตอร์ variant ไม่ชัดเจนจากบริบทเพียงอย่างเดียว สามารถใช้ type alias หรือโครงสร้างแยกเพื่อให้ชื่อมีความอธิบายได้มากขึ้น

ข้อผิดพลาด, IDE, ความเข้ากันได้

  • syntax error บางส่วนสามารถถูกตรวจจับได้โดยตัวไลบรารีเอง
    • รูปแบบที่ไม่ใช่ tuple เช่น Bar(int)
    • comma ที่หายไป
    • trailing comma ที่ห้ามใช้
  • ข้อผิดพลาดอื่น ๆ จะปรากฏผ่านการวินิจฉัยปกติของคอมไพเลอร์
    • ชื่อชนิดข้อมูลที่ไม่มีอยู่จริง
    • match ที่ไม่ exhaustive
    • binder ใน of ที่มากเกินไป
    • อาร์กิวเมนต์ variant ที่ระบุชนิดผิด
    • การ return โดยไม่ dereference pointer binding
  • ระบุว่าจากประสบการณ์แล้วเกือบ 95% ของข้อผิดพลาดแสดงผลออกมาอย่างมีความหมาย
  • หากข้อผิดพลาดเข้าใจยาก สามารถใช้ -E เพื่อตรวจดูโค้ดที่ถูกสร้างขึ้นได้ และเนื่องจาก semantics ของการสร้างโค้ดถูกกำหนดไว้อย่างเป็นทางการ จึงมักไม่เกิดโค้ดที่เหนือความคาดหมาย
  • VS Code สามารถเปิดใช้งานคำแนะนำสำหรับชนิดข้อมูลที่ถูกสร้างขึ้นได้อัตโนมัติ แต่ไม่รองรับการไฮไลต์ไวยากรณ์ของแมโคร
  • เป็นที่ทราบว่า Datatype99 ทำงานได้บน GCC, Clang, MSVC, TCC
  • รองรับ C++11 ขึ้นไปด้วย

เหตุผลที่มุ่งเป้าไปที่ C และข้อจำกัด

  • ซอฟต์แวร์เดิมที่เขียนด้วย C ล้วนสามารถได้รับประโยชน์จาก Datatype99 ได้
  • สามารถรวมเข้ากับโค้ดเบส C เดิมได้เพียง #include <datatype99.h>
  • ในบางสภาพแวดล้อมยังคงใช้ C ล้วนด้วยเหตุผลทางประวัติศาสตร์ เช่น อุปกรณ์ฝังตัว, Linux และระบบปฏิบัติการอื่น ๆ
  • ABI ที่เสถียรของ C มีความสำคัญต่อโปรเจกต์ระบบปลั๊กอินอย่าง MetaCall
  • C ถูกนำเสนอว่าเป็นภาษาที่เติบโตเต็มที่ มีสเปกที่สมบูรณ์ และมีไลบรารีจำนวนมาก
  • หากสามารถใช้ภาษาที่ทันสมัยกว่าหรือระดับสูงกว่าได้ ก็แนะนำให้ใช้ภาษาเหล่านั้นแทน C แบบเก่า แต่สำหรับหลายคน ตัวเลือกนั้นไม่มีอยู่หรือมีต้นทุนสูง
  • ความแตกต่างระหว่าง Datatype99 กับ Metalang99 อยู่ที่บทบาท
    • Metalang99 คือภาษาฟังก์ชันนัลสำหรับเมตาโปรแกรมมิง
    • Datatype99 คืออิมพลีเมนเตชันของ algebraic data type ที่เขียนด้วย Metalang99

1 ความคิดเห็น

 
GN⁺ 2024-05-10
ความคิดเห็นจาก Hacker News
  • ทุกครั้งที่ใช้ภาษาเชิงคำสั่ง ก็แทบจะคิดถึง ชนิดข้อมูลเชิงพีชคณิต อยู่เสมอ
    ที่ทำงานต้องใช้ Java และแม้จะเริ่มมองว่า Java ไม่ได้แย่เท่าที่เคยถูกตำหนิในอดีต แต่ก็มีหลายสิบครั้งที่คิดว่า “ถ้า Java มี discriminated union แบบ F# ก็คงดี”
    มันเลียนแบบได้ด้วยเทคนิคหลายแบบ และหลายกรณีแค่ enum ก็เพียงพอ แต่ส่วนใหญ่ยังขาดความยืดหยุ่นและความกระชับของชนิดข้อมูลเชิงพีชคณิตที่แท้จริง ที่สำคัญที่สุดคือไม่มี pattern matching ดี ๆ แบบที่ได้จากภาษาเชิงฟังก์ชัน
    ส่วนขยาย C ตัวนี้ดูเหมือนจะมี pattern matching อย่างที่ต้องการ จึงดูดีทีเดียว และคงต้องลองดูว่าใช้กับโปรเจกต์ Arduino ได้ไหม
    • คนที่ไม่เคยใช้ ชนิดข้อมูลเชิงพีชคณิตและ pattern matching จะไม่เข้าใจว่ามันวิเศษตรงไหน และแม้แต่คนที่คุ้นเคยแล้วก็อาจไม่รู้ว่ามันวิเศษตรงไหนจนกว่าจะต้องทำงานกับภาษาที่ไม่มีสิ่งนี้
      คนที่เพิ่งรู้จักมักพูดไม่หยุดว่านี่เป็นสิ่งประดิษฐ์ที่ยอดเยี่ยมที่สุดนับตั้งแต่ขนมปังหั่นแผ่น :)
    • sealed interface ของ Java 21 ทำให้ทำ pattern matching ได้
    • Kotlin เข้ากันได้กับ JVM และมี ชนิดข้อมูลเชิงพีชคณิต
      สำหรับ Java มี https://github.com/functionaljava/functionaljava ซึ่งหยุดซัพพอร์ตแล้วแต่เสถียร
    • ชนิดข้อมูลเชิงพีชคณิตและ pattern matching จริง ๆ แล้วทำงานได้ดีมากใน ภาษาเชิงคำสั่ง ด้วย ตัวอย่างเช่น Rust
  • ถ้าต้องเริ่มพัฒนาผลิตภัณฑ์ใหม่ตั้งแต่ต้น จะกำหนดให้ discriminated union ที่มี pattern matching แบบครบถ้วนซึ่งคอมไพเลอร์บังคับตรวจ เป็นข้อกำหนดจำเป็น
    มันทรงพลังมากจนขาดไม่ได้
    • สิ่งที่อยากได้ที่สุดใน Dart คือ union type แต่น่าเสียดายที่ดูเหมือนจะยังไม่ถูกเพิ่มในเร็ว ๆ นี้
      ล่าสุดมีการรองรับ pattern matching ที่คอมไพเลอร์บังคับตรวจบน sealed class แล้ว จึงเหมือนมาได้ครึ่งทาง
    • ตอนนี้มีภาษาอะไรบ้างที่ตรงกับคำอธิบายแบบนั้น? ยังไม่ค่อยเข้าใจว่าหมายถึงอะไรแน่ แต่ถ้าได้ดูตัวอย่างจากภาษาที่รองรับน่าจะเข้าใจมากขึ้น
  • แน่นอนว่าดูดีกว่า และน่าจะทำงานได้ดีกว่าความพยายามของผมที่เคยทำไว้ก่อนหน้านี้ [1] แต่โค้ดมากกว่าถึง 8 เท่า และพึ่งพา ชุดเครื่องมือมาโคร Metalang9 ที่ยอดเยี่ยมแต่ก็น่ากลัวเล็กน้อย
    ถ้าอยากดูว่าชนิดข้อมูลเชิงพีชคณิตทำงานภายในอย่างไร ผมคิดว่า libsum เป็นสื่อเริ่มต้นที่ดี
    [1] https://github.com/naasking/libsum
    • เห็นว่าผมกดดาว repo นั้นไว้ แปลว่าน่าจะเคยดูตอนออกแบบ Datatype99 :)
  • นี่เป็นผลงานของพ่อมดชัด ๆ
    ผมรู้จัก C มาเกือบ 20 ปี แต่ไม่เคยคิดเลยว่า ระบบมาโคร จะทรงพลังพอให้ทำมนตร์ดำแบบนี้ได้
    เจ๋งจริง ๆ
    • ผู้เขียนอายุแค่ 19 ปี เอง จู่ ๆ ก็รู้สึกว่าตัวเองโง่ขึ้นมา
    • metalang99 ที่ผู้เขียนคนเดียวกันทำก็น่าสนใจเช่นกัน
    • x-macro หรือวิธีใช้มาโครแบบนี้ค่อนข้างหวือหวา
      ตามธรรมเนียมแล้วมักใช้เพื่อสร้างและเข้าถึง generic ที่ปลอดภัยด้านชนิด หรือเพื่อลดโค้ดซ้ำในการนิยาม hardware register และ interrupt
      มันให้ความรู้สึกเหมือนต้องคำสาปนิด ๆ แต่แก่นจริง ๆ นั้นเรียบง่ายมาก และเป็นเครื่องมือที่เชื่อถือได้ในการลดความซับซ้อนทางความเข้าใจและโค้ดซ้ำในโปรเจกต์ที่ใช้ C
    • ชนิดข้อมูลเชิงพีชคณิตโดยทั่วไปคือการแทนที่สตริงบน generic struct และ union พร้อมติดแท็ก union
      ในแง่การใช้มาโครแล้วไม่ได้ซับซ้อนขนาดนั้น
  • ใน Wikipedia มีเนื้อหาที่น่าสนใจเกี่ยวกับเรื่องนี้ เป็นวิธีใช้งาน union ในฐานะ “ลำดับชั้นของคลาสในการเขียนโปรแกรมเชิงวัตถุ”
    https://en.wikipedia.org/wiki/Tagged_union#Class_hierarchies...
    มีบล็อกโพสต์ยาว ๆ ที่พูดเรื่องเดียวกันด้วย แต่ดูเหมือนผู้เขียนยังไม่เคยเห็นส่วนนั้นใน Wikipedia
    https://nandakumar.org/blog/2023/12/paradigms-in-disguise.ht...
    น่าชื่นชมที่ผู้พัฒนา datatype99 แสดงปัญหาของวิธีแก้ขัดแบบนี้ไว้ใน README ทันที
  • ยังมี https://melt.cs.umn.edu/ ด้วย มีส่วนขยายที่เพิ่ม template และชนิดข้อมูลเชิงพีชคณิต ให้กับ C
    https://github.com/melt-umn/ableC-template-algebraic-data-ty...
  • ข้อความที่ว่า “อย่าใช้ break/continue ระดับบนสุดภายใน statement ที่ส่งให้ of และ ifLet ให้ใช้ goto label แทน” ดูเหมือนเป็น กับดักให้พลาด ที่ค่อนข้างใหญ่
    ถึงอย่างนั้น นอกเหนือจากจุดนี้ก็เจ๋งมาก
    • การใช้ goto เองไม่ใช่ปัญหา แต่ต้องรู้ว่า ห้ามใช้ break/continue ภายในบล็อกแบบนั้น
      นึกถึงตอนที่เคยสร้าง immediate-mode UI ด้วยมาโคร กรณีของผมไม่ได้เป็นปัญหา แต่ในบางบล็อกสามารถใช้ "break" ได้
      เช่น ในบล็อก win_form สามารถออกด้วย "break" ได้ และ "goto" ก็ทำงานด้วย ในทางกลับกัน บล็อก win_command จะไม่ดัก "break" ดังนั้นถ้าใช้ break หรือ goto ภายใน win_command ก็จะออกจากบล็อกชั้นนอกที่ครอบ win_command อยู่ ซึ่งน่าจะเป็นบล็อก win_form ปกติมักใช้กับกรณีอย่างปุ่ม "Cancel"
    • ข้อดีของพื้นที่มาโครของ Rust คือ ไม่เพียงเป็นไปได้ที่จะเขียนโค้ดเพื่อตรวจเงื่อนไขแบบนี้ แต่ยังทำได้จริง นึกภาพออก และยังอาศัย ไลบรารีโอเพนซอร์ส ที่ติดตั้งง่ายได้ด้วย
      ไม่ใช่แค่ดีกว่าเล็กน้อยในบางด้าน แต่เป็นการขจัดความขัดข้องเล็ก ๆ จำนวนมากที่สะสมตามกาลเวลาออกไปอย่างราบรื่นทั่วทั้งระบบนิเวศ เหมือนปัญหาที่สาย CPython มีชนิดคำศัพท์ของตัวเองมากเกินไป หรือในไลบรารีประมวลผลสมรรถนะสูงก็มีปัญหาแบบนั้น ผลสะสมจึงใหญ่
      เช่น ส่วนของมาโครนี้ที่ก่อปัญหา หากเป็นมาโคร Rust ที่เขียนดี ๆ ก็คงไม่เป็นปัญหาตั้งแต่แรก มันเป็นผลพลอยได้จากการที่คนเก่ง ๆ พยายามเลี่ยงข้อจำกัดของ C

อย่างไรก็ตาม ตัวแมโครนี้เองเดิมก็เป็นการพอร์ตฟีเจอร์เนทีฟของ Rust มาอยู่แล้ว ดังนั้นใน Rust ก็คงไม่จำเป็นต้องเขียนมันตั้งแต่แรก และจึงถูกนำไปใช้ในซอฟต์แวร์ของชุมชนได้อย่างเป็นธรรมชาติ

  • goto จะเป็นเรื่องที่ทำให้ยิงเท้าตัวเองก็ต่อเมื่อใช้ย้ายจากฟังก์ชันหนึ่งไปยังอีกฟังก์ชันหนึ่งเท่านั้น และจริง ๆ แล้ว “goto considered harmful” ก็เล็งไปที่แนวปฏิบัตินั้น
    แนวปฏิบัตินั้นหายไปแล้ว และตอนนี้ goto ที่ใช้ภายในฟังก์ชันค่อนข้างไม่เป็นพิษเป็นภัย และในทางปฏิบัติก็แทบเหมือนกับ break/continue
  • สมมติว่าต้องเขียนโปรแกรม C และต้องการ การทำ pattern matching แบบครบถ้วน สำหรับแท็กของ union จริง ๆ สิ่งที่ Datatype99 มอบให้ก็ “พูดง่าย ๆ คือ syntax sugar บน tagged union”
    และสมมติว่ารู้อยู่แล้วว่ามี Rust แต่จะไม่ใช้ Rust ด้วยเหตุผลที่คนเขียนโปรแกรม C ต่างก็รู้กัน
    ถึงอย่างนั้น อย่างน้อย Zig ก็น่าพิจารณา เมื่อสองวันก่อนผมเขียนโค้ดแบบนี้ด้วย Zig
    ใช้ inline else ของ comptime เพื่อสร้างทุกแขนงของ switch statement บน tagged union แล้วบวก offset ให้กับสมาชิกของ union ที่มีฟิลด์ "l" หากเป็นข้อมูลชนิดที่รู้ได้ตอนคอมไพล์ ก็สามารถเปลี่ยนลักษณะของแต่ละแขนงได้หลากหลาย และข้อมูลนั้นก็มีค่อนข้างมาก เนื่องจากทุกเงื่อนไขถูกตัดสินในเวลาคอมไพล์ แต่ละแขนงของ switch จึงมีเฉพาะตรรกะที่จำเป็นต่อการจัดการ variant นั้น ๆ
    “แต่โปรแกรมของฉันเป็น C อยู่แล้วและต้องใช้แค่ในไฟล์เดียว” ถ้าเป็นเช่นนั้นก็ยังอยากให้ลอง Zig ดู อาจจะถูกใจก็ได้
    • เห็นตัวอย่างที่ยกมาแล้วลองพิจารณา Zig ตอนนี้ปวดตาไปหมดแล้ว คิดว่าคงประเมิน Lisp สูงขึ้นอีก
    • Nim ก็ดูมีประโยชน์ได้ และคอมไพล์เป็น C
      https://gist.github.com/unclechu/eb37cc81e80afbbb5e74990b62e...
  • ใช้ struct + union + enum ก็น่าจะได้ประโยชน์ส่วนใหญ่ของ algebraic data type แล้วไม่ใช่หรือ? เคยใช้แพตเทิร์นที่มี union ของหลายชนิด แล้วแนบ enum เพื่อแยกว่าจะเลือกอันไหน
    std::variant ก็ดูเหมือนจะทำงานได้ระดับหนึ่งคล้าย sum type
    ปัญหาเดียวคือสร้าง switch statement ที่เรียบร้อยตามค่าบางอย่างของฟิลด์ไม่ได้ แต่ switch statement แบบซ้อนกันก็ไม่ได้รกขนาดนั้น
    • ใช่ เพียงแค่ใช้ธรรมเนียมและวินัยก็ได้ประโยชน์หลายอย่างของ object-oriented ได้เหมือนกัน แต่ถ้าจะทำแบบนั้น เช่น ต้องจัดการ vtable เอง จึงต้องลงไปถึงรายละเอียดการใช้งานจริงบ่อย ๆ
      ปัญหาของแนวทางนี้คือ ภาระทางความคิด ที่ต้องดูแลรายละเอียดทั้งหมดไม่ให้ตกหล่นนั้นสูงมาก พอเหนื่อยเข้า ก็เริ่มยัดฟังก์ชันเข้าไปในคลาสเดิมแทนที่จะสร้างคลาสใหม่
      สุดท้าย abstraction ก็ถูกมองว่าแพง และผลก็คือใช้ abstraction น้อยลง ทั้งที่อาจแก้ปัญหาได้อย่างงดงามกว่า
      สรุปคือ ความสามารถในการพูดว่า “Darmok and Jalad at Tanagra” แทนที่จะต้องเล่าเรื่องยาวซ้ำทุกครั้งเมื่ออ้างถึงแนวคิดซับซ้อนนั้น เป็นสิ่งที่เปลี่ยนแปลงเกมได้
    • ด้านการ modeling นั้นเลียนแบบได้ แต่ยังไม่ถึงครึ่งหนึ่งของประโยชน์จาก algebraic data type
      pattern matching คือข้อได้เปรียบด้านการใช้งานที่สำคัญ
    • เมื่อจัดการค่าของ sum type สิ่งสำคัญอย่างยิ่งคือ ต้องตรวจสอบให้แน่ใจว่าได้ตรวจครบทุกทางเลือกแล้ว
      ทำด้วยแมโครนั้นยาก แต่ไม่ใช่เป็นไปไม่ได้ โดยพื้นฐานต้องมีแมโครที่เริ่มบริบทการ match ต้องนำตัวแปรเข้ามาเพื่อติดตามทุกทางเลือกที่ตรวจแล้ว และต้องจัดให้เมื่อจบบริบทการ match มีการตรวจสอบว่าได้ตรวจครบทุกทางเลือกแล้วหรือไม่
    • การใช้ C++ ในโค้ดส่วนใหญ่เมื่อจำเป็นจริง ๆ โดยทั่วไปก็โอเค แต่ไม่ค่อยชอบ std::variant
      วิธี match ให้ครบทุกกรณีด้วย std::visit รู้สึกแฮ็ก ๆ ถ้ามันกลายเป็น ฟีเจอร์ระดับ first-class ของภาษา จริง ๆ จะได้ประโยชน์มาก
      มันอาจส่งผลต่อโค้ดในชีวิตประจำวันมากกว่าฟีเจอร์อื่น ๆ ที่ทำกันมาอย่าง coroutine ด้วยซ้ำ
  • ทำสิ่งนี้ออกมาได้ สุดยอดจริง ๆ ขอคารวะ