- บันทึกการวิเคราะห์ โค้ด 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 ความคิดเห็น
ความคิดเห็นจาก Hacker News
แมโครในโค้ดนี้มีไว้เพื่อ บีบอัดการดำเนินการที่ใช้ร่วมกัน ฉันอ่าน J Incunabulum ก่อนแล้วค่อยมาดูโค้ดนี้ เลยรู้สึกว่าถ้าเป็นโปรแกรมเมอร์ที่คุ้นกับ C แล้วเริ่มอ่านจากกลางทาง นิยามแมโครช่วงต้น ๆ อาจทำให้งงได้ เพราะแมโครถูกสร้างซ้อนทับกันไปเรื่อย ๆ โค้ดจึงไต่ขึ้นไปบน บันไดแห่งนามธรรม อย่างรวดเร็ว โดยเฉพาะแมโคร
Iterate(i) ที่ชอบมาก เพราะย่อ loop ที่ยืดยาวให้เหลือแค่ตัวอักษรเดียว เหตุผลที่โค้ดแน่นขนาดนี้อ่านยาก ก็เพราะมันสร้าง abstraction จำนวนมากภายในไม่กี่สิบบรรทัดแล้วใช้งานทันที เลยต้องค่อย ๆ อ่านทีละตัวอักษร ในฐานะคนที่เคยทำงานกับ codebase ขนาดใหญ่ซึ่งเต็มไปด้วยไฟล์บาง ๆ หลายร้อยไฟล์ ความบีบอัดแบบสุดโต่งนี้กลับให้ความรู้สึกสดใหม่ดีgrep *.[ch]โดยไม่ต้องมี subdirectory โดยเฉพาะโปรเจกต์ Java ที่มีไฟล์เล็ก ๆ เยอะเกินไปและแต่ละไฟล์ก็มีเนื้อหาน้อย ทำให้หาของยาก ถ้ามี IDE ก็คงดีขึ้น แต่ฉันไม่ใช้ Whitney เคยพูดในบทสัมภาษณ์ว่าอยากให้โค้ดทั้งหมดอยู่ในหน้าเดียว และ “IDE” ของเขาก็คือ Windows console กับ Notepadถ้าจะเข้าใจโค้ด C ของ Arthur Whitney ต้องเริ่มจากการเรียนรู้ ภาษาในตระกูล APL ก่อน ไม่อย่างนั้นมันก็จะดูเหมือนแค่ C สไตล์ประหลาด ๆ Whitney กำลังพยายามใช้ C ให้เหมือน APL การไม่เว้นวรรค ใช้ชื่อตัวแปรตัวเดียว และเขียนฟังก์ชันบรรทัดเดียว ก็เป็นสไตล์เดียวกับ APL มันคล้ายกับโปรแกรมเมอร์ Pascal ที่เขียน
#define begin {อะไรแบบนั้น แต่ Whitney ไปได้ไกลและแปลกใหม่กว่านั้นมาก#define begin {” ว่า “อ๋อ แบบ Stephen Bourne น่ะสิ”ตอนค้นหา Shakti ฉันเห็นว่าลิงก์ Wikipedia รีไดเร็กต์ไป
k.nycแล้วหน้าเว็บมีแค่ ตัวอักษร ‘k’ ตัวเดียว เปิดดู source ก็พบว่ามีแค่ `k` จริง ๆ มันเหมือน มินิมัลลิสม์แบบ Whitney ในเวอร์ชัน HTML — ตัดทุกอย่างที่ไม่จำเป็นออก แล้วปล่อยให้คอมไพเลอร์จัดการส่วนที่เหลือโดยปริยาย
k
ไม่ว่าจะเห็นด้วยกับสไตล์การเขียนโค้ดของ Whitney หรือไม่ บล็อกโพสต์นี้ก็เป็น บทวิเคราะห์ที่ยอดเยี่ยม ลึกกว่าที่คาดไว้มากสำหรับงานที่ผู้เขียนบอกว่าใช้เวลาเขียนแค่ 8 ชั่วโมง และช่วงสรุปท้ายโดยเฉพาะน่าประทับใจมาก ลิงก์ต้นฉบับ
มันทำให้นึกถึงความพยายามของ Stephen Bourne ที่จะทำให้ C ดูเหมือน Algol ดูจากตัวอย่าง mac.h กับ ตัวอย่าง expand.c ก็สัมผัสได้ถึงอารมณ์คล้ายกัน
ทุกวงการล้วนมีสิ่งที่เรียกว่า “best practice” แต่สิ่งนั้นมักเหมาะกับกรณีทั่วไปเท่านั้น ในบางสถานการณ์ การทำตรงข้ามอาจดีกว่าก็ได้ สุดท้ายแล้ว ปัญญาจากส่วนรวม ควรใช้เป็นค่าเริ่มต้น แต่พอเริ่มคิดด้วยตัวเอง คุณก็มักจะมองเห็นช่องว่างของมัน
ชอบที่บทความนี้ไม่โจมตีโค้ดอย่างก้าวร้าว แต่แสดง มุมมองที่สมดุล อ่านสนุกมาก และคิดว่าจะกลับมาอ่านอีกในภายหลัง
ฉันสงสัยว่าสไตล์การเขียนโค้ดแบบนี้จัดเป็น กระบวนทัศน์เฉพาะทาง อะไรหรือเปล่า แทบไม่เคยเห็นโค้ดแบบนี้ในโปรเจกต์จริงเลย ยกเว้นอะไรอย่าง “business card ray tracer” ซอร์สโค้ด ของภาษา J ที่ Whitney สร้างก็มีสไตล์ บีบอัดสุดขั้ว คล้ายกัน
ถ้าดูนิยามแมโครต่อไปนี้