3 คะแนน โดย GN⁺ 2025-11-04 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • บันทึกการวิเคราะห์ โค้ด C ของอินเทอร์พรีเตอร์ภาษา K ความยาว 50 บรรทัดที่สร้างโดย Arthur Whitney พร้อมตีความสไตล์การเขียนโค้ดอันเป็นเอกลักษณ์ของเขา
  • ในโค้ดมีโครงสร้างเชิงทดลองจำนวนมากที่ต่างจากโค้ด C ทั่วไป เช่น ไวยากรณ์บีบอัดที่อาศัยแมโคร, ส่วนขยาย C ที่ไม่เป็นมาตรฐาน, และ การใช้อาร์กิวเมนต์โดยนัย
  • ผู้เขียนตีความความหมายของแต่ละแมโครและฟังก์ชันด้วยตนเอง พร้อมสำรวจ ปรัชญาของภาษาในสาย APL และ ข้อดีข้อเสียของความหนาแน่นของโค้ด
  • จุดเด่นของโค้ดคือ ความสั้นและพลังในการประกอบสร้างสูง ส่วนจุดด้อยคือ ไวยากรณ์ที่ไม่เป็นมาตรฐานและความสามารถในการอ่านที่ลดลง
  • โดยสรุป โค้ดนี้ถูกมองว่าเป็นตัวอย่างที่แสดงให้เห็นความสำคัญของ วิธีคิดแบบเข้าใจปัญหาอย่างถ่องแท้ก่อนลงมือเขียนโค้ด มากกว่าการ “เขียนให้สั้น”

Arthur Whitney และโค้ดของเขา

  • Arthur Whitney เป็นนักวิทยาการคอมพิวเตอร์ผู้ออกแบบ ภาษา A, K, Q และฐานข้อมูล kdb, Shakti
    • kdb เป็น ฐานข้อมูลอนุกรมเวลาความเร็วสูงมาก ที่ใช้ในภาคการเงิน ส่วน Shakti เป็นเวอร์ชันที่เร็วกว่า ออกแบบมาสำหรับจัดการ ชุดข้อมูลระดับ 1 ล้านล้านแถว
  • ภาษาของเขาเป็น ภาษาเชิงอาร์เรย์ที่ได้รับอิทธิพลอย่างมากจาก APL โดยให้ความสำคัญกับความกระชับและพลังในการแสดงออกเชิงคณิตศาสตร์
  • จุดโฟกัสของบทความนี้ไม่ใช่งานประยุกต์ด้านการเงิน แต่เป็นการวิเคราะห์ สไตล์เฉพาะตัวของโค้ด C ที่ Whitney เขียน

โครงสร้างของอินเทอร์พรีเตอร์ K 50 บรรทัด

  • ในรีโพซิทอรี ksimple ที่เปิดเผยสู่สาธารณะ มี อินเทอร์พรีเตอร์ C ราว 50 บรรทัด ที่ Whitney เขียนเสร็จภายในไม่กี่วันรวมอยู่ด้วย
  • แกนหลักของโค้ดประกอบด้วยไฟล์ a.h และ a.c สองไฟล์ โดยมีจุดเด่นคือ การย่อการประกาศฟังก์ชันด้วยแมโคร และ โครงสร้างที่ใช้พอยน์เตอร์เสมือนเป็นจำนวนเต็ม
  • ไวยากรณ์ typedef char*s,c; ใช้กำหนดให้ s เป็นพอยน์เตอร์สตริง และ c เป็นชนิดอักขระ
  • s Q=(s)128; เป็นตัวอย่างของการใช้พอยน์เตอร์ราวกับเป็นจำนวนเต็ม และตลอดทั้งโค้ด Q ถูกใช้เป็นค่าพิเศษที่แทนสถานะข้อผิดพลาด
  • มีการใช้ ไวยากรณ์ส่วนขยายของ GCC จำนวนมาก เช่น statement expression ในรูป ({e;}) และตัวดำเนินการ ?:

ความหมายของแมโครและฟังก์ชันหลัก

  • #define _(e...) ({e;}) : แมโครสำหรับรวมหลายคำสั่งให้เป็นนิพจน์เดียว
  • #define i(n,e) : รูปแบบย่อของลูปทำซ้ำ ใช้เขียน for loop ให้เหลือบรรทัดเดียว
  • #define Q(e) เป็นต้น คือ แมโครจัดการข้อผิดพลาด โดย Qr, Qd, Qz จะคืนค่าข้อผิดพลาด rank, domain และ not-yet-implemented ตามลำดับ
  • แมโคร _s, _i, f, F ใช้ ทำให้การประกาศฟังก์ชันสั้นลง และใช้อาร์กิวเมนต์ x, a แบบโดยนัย
  • ax, ix, nx เป็นต้น คือ แมโครสำหรับตรวจชนิดข้อมูลและทำดัชนี โดย ax ใช้ตัดสินว่า “x เป็นอะตอม (atom) หรือไม่”
  • f(w,write(1,ax?&x:x,ax?1:strlen(x));x) คือ ฟังก์ชันแสดงผล โดยถ้าเป็นอะตอมจะพิมพ์เป็นอักขระ และถ้าเป็นเวกเตอร์จะพิมพ์เป็นสตริง

วิธีการทำงานของอินเทอร์พรีเตอร์

  • ฟังก์ชัน m(x) ทำหน้าที่ จัดสรรหน่วยความจำและสร้างพอยน์เตอร์ที่รวมข้อมูลความยาวไว้ด้วย โดยเวกเตอร์มีความยาวสูงสุด 255 ไบต์
  • แมโคร g(a,v) ใช้ จัดการการคำนวณของอะตอม/เวกเตอร์แบบรวมศูนย์ และฟังก์ชันอย่าง not, sub, At, _A ก็ถูกนิยามบนพื้นฐานนี้
  • แมโคร G(f,o) ใช้ สร้างฟังก์ชันตัวดำเนินการแบบทวิภาคโดยอัตโนมัติ รองรับการดำเนินการอย่าง <, ==, +, *, &, | เป็นต้น
  • cat, rev, cnt, Tak คือ ฟังก์ชันจัดการเวกเตอร์ โดย rev ใช้ฟังก์ชัน ind เพื่อสร้างดัชนีแบบย้อนลำดับ
  • ฟังก์ชัน e() คือ ตัวประเมินผลแบบเรียกซ้ำ ซึ่งอ่านสตริงจากขวาไปซ้ายและประมวลผล ตัวแปรหนึ่งอักขระ ตัวเลข และตัวดำเนินการ
  • main() รับอินพุต ประเมินผลด้วย e() แล้วพิมพ์ผลลัพธ์ออกมา ในรูปแบบ ลูป REPL

การประเมินสไตล์โค้ด

  • ข้อดี
    • ชุดปฏิบัติการพื้นฐานที่ กระชับ และสร้างจาก แมโครที่ประกอบกันได้
    • ด้วย ความยาวโค้ดที่สั้น จึงมองเห็นตรรกะทั้งหมดได้ในครั้งเดียวโดยแทบไม่ต้องเลื่อนหน้าจอ
    • การแสดงออกที่มีความหนาแน่นสูง ช่วยบีบอัดโครงสร้างเชิงตรรกะของโค้ด
  • ข้อเสีย
    • การจัดการชนิดข้อมูลแบบ ไร้ความหมายเชิงชนิด เช่น ใช้ char* ราวกับเป็นจำนวนเต็ม
    • ความสามารถในการอ่านลดลงจาก การใช้รหัส ASCII โดยตรง, ตัวดำเนินการ ternary ที่ซับซ้อน, และ ไวยากรณ์ที่ไม่เป็นมาตรฐาน
    • การใช้อาร์กิวเมนต์โดยนัยและ ชื่อตัวแปรสั้นมาก ทำให้ยากต่อการเข้าใจเจตนา
  • องค์ประกอบที่เป็นกลาง
    • ไวยากรณ์เฉพาะของ GCC (?:, statement expression) น่าสนใจ แต่ ลดความสามารถในการพกพา
    • การใช้อาร์กิวเมนต์โดยนัย มีประโยชน์ในโค้ดขนาดเล็ก แต่ในโค้ดขนาดใหญ่ก็อาจก่อความสับสนได้
    • ชื่อที่สั้น เมื่อคุ้นเคยแล้วอาจใช้งานได้มีประสิทธิภาพ แต่พลังในการสื่อความหมายค่อนข้างต่ำ

บทสรุปและบทเรียน

  • โค้ดนี้ไม่ได้แค่สอนเรื่อง “การเขียนให้สั้น” แต่แสดงให้เห็น วิธีคิดแบบเข้าใจปัญหาอย่างครบถ้วนก่อนจึงลงมือเขียนโค้ด
  • โค้ดของ Whitney เป็น รูปแบบที่ถ่ายทอดแบบจำลองทางคณิตศาสตร์ที่เสร็จสมบูรณ์แล้วลงมาเป็นโค้ด หรือก็คือ “ผลลัพธ์ของการแปลงความคิดให้เป็นโค้ด”
  • ผู้เขียนย้อนมองนิสัยของตนเองที่มัก พยายามแก้ปัญหาภายในตัวโค้ด และย้ำว่า
    ต่อไปควรให้ความสำคัญกับ การทำแบบจำลองเชิงแนวคิดและการจัดระเบียบความคิดก่อนเริ่มเขียนโค้ด
  • ท้ายที่สุด การทดลองนี้ถูกสรุปว่าเป็นประสบการณ์ในการฝึก “ความสามารถในการอ่านโค้ด” และสำรวจ สมดุลระหว่างความหนาแน่นของโค้ดกับความชัดเจนของความคิด

ไอเดียการทดลองต่อในอนาคต

  • ข้อเสนอสำหรับการฝึกขยายอินเทอร์พรีเตอร์:
    • รองรับเวกเตอร์เลขทศนิยมแบบ floating-point
    • รองรับสมาชิกมากกว่า 255 ตัว
    • รองรับตัวเลขหลายหลักและชื่อตัวแปรหลายอักขระ
    • รองรับ array literal และละเว้นช่องว่าง
    • เพิ่มการจัดการหน่วยความจำและการแสดงข้อผิดพลาด
    • ทำฟังก์ชันที่ยังไม่ถูก implement ให้สมบูรณ์
  • การขยายเหล่านี้อาจกลายเป็นการทดลองเพื่อพัฒนาให้เป็น ภาษาที่ใช้งานได้จริง โดยยังคงรักษา สไตล์โค้ดแบบ Whitney ไว้

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

 
GN⁺ 2025-11-04
ความคิดเห็นจาก Hacker News
  • แมโครในโค้ดนี้มีไว้เพื่อ บีบอัดการดำเนินการที่ใช้ร่วมกัน ฉันอ่าน J Incunabulum ก่อนแล้วค่อยมาดูโค้ดนี้ เลยรู้สึกว่าถ้าเป็นโปรแกรมเมอร์ที่คุ้นกับ C แล้วเริ่มอ่านจากกลางทาง นิยามแมโครช่วงต้น ๆ อาจทำให้งงได้ เพราะแมโครถูกสร้างซ้อนทับกันไปเรื่อย ๆ โค้ดจึงไต่ขึ้นไปบน บันไดแห่งนามธรรม อย่างรวดเร็ว โดยเฉพาะแมโคร Iterate (i) ที่ชอบมาก เพราะย่อ loop ที่ยืดยาวให้เหลือแค่ตัวอักษรเดียว เหตุผลที่โค้ดแน่นขนาดนี้อ่านยาก ก็เพราะมันสร้าง abstraction จำนวนมากภายในไม่กี่สิบบรรทัดแล้วใช้งานทันที เลยต้องค่อย ๆ อ่านทีละตัวอักษร ในฐานะคนที่เคยทำงานกับ codebase ขนาดใหญ่ซึ่งเต็มไปด้วยไฟล์บาง ๆ หลายร้อยไฟล์ ความบีบอัดแบบสุดโต่งนี้กลับให้ความรู้สึกสดใหม่ดี

    • ฉันก็คิดคล้ายกันหลังจากอ่าน Incunabulum แต่พอลอง เปลี่ยนชื่อตัวแปรเป็นอีโมจิ แล้ว กลับเข้าใจง่ายขึ้นมาก ดูได้จากภาพโค้ดเวอร์ชันอีโมจิ ว่าปัญหาส่วนหนึ่งไม่ใช่แค่ ความหนาแน่นของข้อมูล แต่ยังรวมถึง รูปแบบตัวอักษร (orthography) ด้วย ภาษาสมัยใหม่ส่วนใหญ่ไม่ยอมให้ใช้สัญลักษณ์หรืออีโมจิใน identifier แต่ถ้าทำให้แยกกันทางสายตาได้แบบนี้ มันน่าจะอ่านง่ายกว่ามาก อีกอย่างคือแม้ editor ส่วนใหญ่จะใช้ syntax highlighting ตามชนิดไวยากรณ์ แต่บางครั้ง การลงสีแบบอิง token (ให้แต่ละ identifier มีสีเฉพาะตัว) กลับมีประโยชน์กว่า “hashed syntax highlighting” ของ Sublime Text เป็นตัวอย่างหนึ่ง พอเปลี่ยนแบบนี้แล้ว โครงสร้างของโค้ดก็มองออกได้ทันที
    • ดูเหมือนนักพัฒนาหลายคนจะชอบ codebase ขนาดมหึมา กันเสียมากกว่า ฉันชอบโครงสร้างที่ค้นหาได้ตรง ๆ ด้วย grep *.[ch] โดยไม่ต้องมี subdirectory โดยเฉพาะโปรเจกต์ Java ที่มีไฟล์เล็ก ๆ เยอะเกินไปและแต่ละไฟล์ก็มีเนื้อหาน้อย ทำให้หาของยาก ถ้ามี IDE ก็คงดีขึ้น แต่ฉันไม่ใช้ Whitney เคยพูดในบทสัมภาษณ์ว่าอยากให้โค้ดทั้งหมดอยู่ในหน้าเดียว และ “IDE” ของเขาก็คือ Windows console กับ Notepad
  • ถ้าจะเข้าใจโค้ด C ของ Arthur Whitney ต้องเริ่มจากการเรียนรู้ ภาษาในตระกูล APL ก่อน ไม่อย่างนั้นมันก็จะดูเหมือนแค่ C สไตล์ประหลาด ๆ Whitney กำลังพยายามใช้ C ให้เหมือน APL การไม่เว้นวรรค ใช้ชื่อตัวแปรตัวเดียว และเขียนฟังก์ชันบรรทัดเดียว ก็เป็นสไตล์เดียวกับ APL มันคล้ายกับโปรแกรมเมอร์ Pascal ที่เขียน #define begin { อะไรแบบนั้น แต่ Whitney ไปได้ไกลและแปลกใหม่กว่านั้นมาก

    • ต่อให้เป็นผู้ใช้ APL เอง มันก็ยังดูแปลกอยู่ดี ภาษา K ที่ Whitney สร้างขึ้นใช้สไตล์แบบนี้ก็จริง แต่ ฟังก์ชันบรรทัดเดียว นั้นทำไม่ได้ใน APL แบบดั้งเดิม APL ไม่มีแมโคร ไม่มี ternary operator และไม่มีชื่อตัวแปรแบบ implicit แก่นของ APL คือ การดำเนินการบนอาร์เรย์แบบไม่เปลี่ยนค่าเดิม แต่สไตล์ C ของ Whitney ไม่ได้ยึดตามปรัชญานั้น
    • มีคนเล่นมุกกับประโยคที่ว่า “เหมือนโปรแกรมเมอร์ Pascal ย้ายมา C แล้วเขียน #define begin {” ว่า “อ๋อ แบบ Stephen Bourne น่ะสิ”
    • ตอนแรกมันดูเหมือนภาษาฟังก์ชัน แต่ไม่นานก็ชวนให้นึกถึง ความสยองของ C preprocessor
    • ในย่อหน้าเปิดของบทความก็อธิบายอยู่แล้วว่า “C ของ Whitney ได้แรงบันดาลใจจาก APL” เป็นการชี้ว่าคอมเมนต์แนวสรุปซ้ำมีเยอะเกินไป
    • การไปเรียน J ก็ดูน่าสนใจเหมือนกัน เพราะเข้าถึงง่ายกว่า APL และใช้ สัญลักษณ์ที่พิมพ์ได้บนคีย์บอร์ดทั่วไป
  • ตอนค้นหา Shakti ฉันเห็นว่าลิงก์ Wikipedia รีไดเร็กต์ไป k.nyc แล้วหน้าเว็บมีแค่ ตัวอักษร ‘k’ ตัวเดียว เปิดดู source ก็พบว่ามีแค่ `k

` จริง ๆ มันเหมือน มินิมัลลิสม์แบบ Whitney ในเวอร์ชัน HTML — ตัดทุกอย่างที่ไม่จำเป็นออก แล้วปล่อยให้คอมไพเลอร์จัดการส่วนที่เหลือโดยปริยาย

  • k

  • ไม่ว่าจะเห็นด้วยกับสไตล์การเขียนโค้ดของ Whitney หรือไม่ บล็อกโพสต์นี้ก็เป็น บทวิเคราะห์ที่ยอดเยี่ยม ลึกกว่าที่คาดไว้มากสำหรับงานที่ผู้เขียนบอกว่าใช้เวลาเขียนแค่ 8 ชั่วโมง และช่วงสรุปท้ายโดยเฉพาะน่าประทับใจมาก ลิงก์ต้นฉบับ

  • มันทำให้นึกถึงความพยายามของ Stephen Bourne ที่จะทำให้ C ดูเหมือน Algol ดูจากตัวอย่าง mac.h กับ ตัวอย่าง expand.c ก็สัมผัสได้ถึงอารมณ์คล้ายกัน

  • ทุกวงการล้วนมีสิ่งที่เรียกว่า “best practice” แต่สิ่งนั้นมักเหมาะกับกรณีทั่วไปเท่านั้น ในบางสถานการณ์ การทำตรงข้ามอาจดีกว่าก็ได้ สุดท้ายแล้ว ปัญญาจากส่วนรวม ควรใช้เป็นค่าเริ่มต้น แต่พอเริ่มคิดด้วยตัวเอง คุณก็มักจะมองเห็นช่องว่างของมัน

    • นั่นแหละถึงไม่ชอบคำว่า “best practice” จริง ๆ แล้วมันก็แค่ ทางประนีประนอมระดับค่าเฉลี่ย เท่านั้น เป็นการแลกความมีประสิทธิภาพกับสมรรถนะ เพื่อให้ได้ การดูแลรักษาและความสม่ำเสมอ
    • การสร้างผลิตภัณฑ์ที่ดี กับ ความอ่านง่ายของ codebase หรือเส้นโค้งการเรียนรู้ เป็นคนละเรื่องกัน ด้านหนึ่งดี ไม่ได้แปลว่าอีกด้านจะดีตามมาโดยอัตโนมัติ
  • ชอบที่บทความนี้ไม่โจมตีโค้ดอย่างก้าวร้าว แต่แสดง มุมมองที่สมดุล อ่านสนุกมาก และคิดว่าจะกลับมาอ่านอีกในภายหลัง

  • ฉันสงสัยว่าสไตล์การเขียนโค้ดแบบนี้จัดเป็น กระบวนทัศน์เฉพาะทาง อะไรหรือเปล่า แทบไม่เคยเห็นโค้ดแบบนี้ในโปรเจกต์จริงเลย ยกเว้นอะไรอย่าง “business card ray tracer” ซอร์สโค้ด ของภาษา J ที่ Whitney สร้างก็มีสไตล์ บีบอัดสุดขั้ว คล้ายกัน

    • ใช่เลย นี่คือ สไตล์การเขียนโค้ดเฉพาะตัว ของ Whitney เขาใช้มันอย่างสม่ำเสมอใน interpreter สำหรับภาษาอาร์เรย์ของเขา และมีชื่อเสียงเรื่องการยัด implementation ทั้งหมดลงในไม่กี่หน้า ยังมีลิงก์เมตาคอมเมนต์ ที่รวบรวมการถกเถียงใน HN ที่เกี่ยวข้องไว้ด้วย
    • มีคนตอบประโยคที่ว่า “ไม่เคยเห็นโค้ดแบบนี้ในโลกจริงเลย” ว่า “คุณโชคดีแล้ว” เพราะนี่ไม่ใช่ C อีกต่อไป แต่เป็นภาษาแบบ internal DSL ที่สร้างขึ้นใหม่บน C C เป็นเพียง เป้าหมายแรกของการคอมไพล์ เท่านั้น
    • มันคล้ายกับ ภาษาในตระกูล APL อย่าง J และ K ใช้สัญลักษณ์ non-ASCII และบรรจุข้อมูลจำนวนมากลงในหน้าเดียวด้วย ความหนาแน่นสุดโต่ง พอชินแล้วก็มีข้อดีตรงที่ลดจำนวนชั้น abstraction ลงได้
    • ยังมีวิดีโอเกี่ยวกับ co-dfns ที่ใช้แนวทางคล้ายกันด้วย แม้จะไม่ใช่ C แต่ก็เขียนในสไตล์ ความหนาแน่นสูง แบบใกล้เคียงกัน
    • มีคนแซวว่า “นี่มัน OCC (Obfuscated C Code) ชัด ๆ”
  • ถ้าดูนิยามแมโครต่อไปนี้

    #define _(e...) ({e;})
    #define x(a,e...) _(s x=a;e)
    #define $(a,b) if(a)b;else
    #define i(n,e) {int $n=n;int i=0;for(;i