ฟีเจอร์ส่วนขยายภาษาที่หายไปของคอมไพเลอร์ MetaWare High C (2023)
(duriansoftware.com)- High C Compiler สำหรับ FM TOWNS ในปี 1989 ไม่ได้แค่รองรับสภาพแวดล้อม DOS แต่ยังมีฟีเจอร์ภาษาเชิงผู้ใช้หลายอย่างที่หาได้ยากในคอมไพเลอร์ C ยุคนั้น
- เมื่อใช้ร่วมกับ DOS extender ของ Phar Lap จึงกลายเป็นคอมไพเลอร์ C แบบ 1st-party ของ FM TOWNS ท่ามกลางกระแสการพัฒนาที่ใช้ประโยชน์จาก 80386 แบบ 32 บิตบนสภาพแวดล้อม MS-DOS 16 บิต
- ขีดล่างใน numeric literal, อาร์กิวเมนต์แบบมีป้ายชื่อ, ช่วงของ case, ฟังก์ชันซ้อน, generator ฯลฯ เป็นฟีเจอร์ที่เข้ามาในมาตรฐาน C/C++ ช้ากว่านั้นมาก หรือบางอย่างก็ยังไม่มีในมาตรฐานจนถึงปัจจุบัน
- ฟังก์ชันซ้อนให้ “full function value” ในรูปแบบ non-escaping closure ที่ส่งทั้ง function pointer และ context pointer ไปด้วยกัน ทำให้แสดงออกได้มากกว่า function pointer ของ C แบบปกติ
- generator ถูกทำเป็น syntax sugar บนฟังก์ชันซ้อน ทำงานด้วยโครงสร้างเรียบง่ายที่แปลงเนื้อหาในลูป
forของผู้เรียกให้เป็นฟังก์ชันซ้อน แล้วส่งเป็นอาร์กิวเมนต์yield
ตำแหน่งของ FM TOWNS และ High C
- คู่มือคอมไพเลอร์ C จากทศวรรษ 1980 ที่พบในกองหนังสือเกี่ยวกับ FM TOWNS มีส่วนขยายภาษามากกว่าที่คาดไว้
- การใช้ C และภาษาตระกูลเดียวกันในสภาพแวดล้อมจริงจำเป็นต้องพึ่ง ส่วนขยายของผู้ผลิต มาเป็นเวลานาน
- ในสภาพแวดล้อมปัจจุบันที่มี GCC, Clang, MSVC เป็นศูนย์กลาง ส่วนขยายมักเน้นไปที่การจัดการเฉพาะแพลตฟอร์มหรือการควบคุมรายละเอียดระดับต่ำ
- ในทศวรรษ 1980 มีบริษัทขนาดเล็กจำนวนมากกว่าแข่งขันกันให้ถูกนำไปใช้ ทำให้ฟีเจอร์ส่วนขยายหลากหลายกว่าด้วย
- Phar Lap สร้างหนึ่งใน DOS extender ยุคแรก ๆ ที่ทำให้ใช้ประโยชน์จากโปรเซสเซอร์ 80386 แบบ 32 บิตได้ในสภาพแวดล้อม MS-DOS 16 บิต
- MetaWare พอร์ต High C Compiler ไปยัง DOS extender SDK ของ Phar Lap ตามคำว่าจ้างของ Phar Lap
- Fujitsu ผสาน DOS extender ของ Phar Lap เข้ากับ OS ของแพลตฟอร์ม FM TOWNS ที่ใช้ 80386 และ High C จึงกลายเป็นคอมไพเลอร์ C แบบ 1st-party ของแพลตฟอร์มนี้
- FM TOWNS เปิดตัวในปี 1989 ก่อนที่ C89 ซึ่งเป็นมาตรฐาน ANSI C ฉบับแรกจะได้รับการให้สัตยาบันไม่นาน
ฟีเจอร์อำนวยความสะดวกเล็ก ๆ ที่มาก่อนมาตรฐาน
-
ตัวคั่นขีดล่างใน numeric literal
- สามารถใส่ ตัวคั่นขีดล่าง ภายในตัวเลขเพื่อให้อ่าน numeric literal ยาว ๆ ได้ง่ายขึ้น
- C++ นำตัวคั่นด้วยเครื่องหมายอัญประกาศเดี่ยว เช่น
1'000'000เข้ามาใน C++14 - C เพิ่งนำฟีเจอร์คล้ายกันเข้ามาใน C23
-
อาร์กิวเมนต์แบบมีป้ายชื่อ
- สามารถใส่ชื่ออาร์กิวเมนต์ได้สำหรับฟังก์ชันที่มีพารามิเตอร์มาก หรือใช้ชนิดที่ความหมายไม่ชัดเจน ณ จุดเรียกจำนวนมาก เช่น
bool - อาร์กิวเมนต์แบบมีป้ายชื่อของ High C ทำงานคล้ายฟีเจอร์ยอดนิยมของ Python
- ป้ายชื่ออาร์กิวเมนต์เป็นทางเลือก
- หากมีป้ายชื่อ สามารถระบุอาร์กิวเมนต์ในลำดับใดก็ได้ด้วยไวยากรณ์
argumentName => value - สามารถผสมอาร์กิวเมนต์ที่ไม่มีป้ายชื่อกับอาร์กิวเมนต์ที่มีป้ายชื่อได้ แต่พารามิเตอร์ทุกตัวของฟังก์ชันต้องมีอาร์กิวเมนต์ที่สอดคล้องกัน
- C และ C++ มาตรฐานยังไม่มีฟีเจอร์นี้
- สามารถใส่ชื่ออาร์กิวเมนต์ได้สำหรับฟังก์ชันที่มีพารามิเตอร์มาก หรือใช้ชนิดที่ความหมายไม่ชัดเจน ณ จุดเรียกจำนวนมาก เช่น
-
ช่วงของ case
- มีฟีเจอร์จับคู่ช่วงของค่าในครั้งเดียว เหมือน
case low..highของ Pascal - C และ C++ มาตรฐานไม่ได้รับฟีเจอร์นี้เข้ามา
- มีฟีเจอร์จับคู่ช่วงของค่าในครั้งเดียว เหมือน
ฟังก์ชันซ้อนและ full function value
- High C สามารถประกาศ ฟังก์ชันซ้อน ภายในฟังก์ชันได้เหมือน Pascal
- วิธีการทำงานใกล้เคียงรูปแบบที่สมบูรณ์กว่ามาตรฐาน Pascal หรือส่วนขยายฟังก์ชันซ้อนของ GCC
- High C ไม่เพียงประกาศฟังก์ชันซ้อนได้ แต่ยังประกาศชนิด full function value ได้ด้วย
- ต่างจาก function pointer ของ C แบบดั้งเดิม เพราะเก็บ context pointer ร่วมกับ function pointer
- ช่วยให้ฟังก์ชันซ้อนสามารถตามหาคอนเท็กซ์ที่มัน capture ไว้ได้อีกครั้ง
- เป็น non-escaping closure ที่อายุการใช้งานไม่ยืดต่อไปหลังฟังก์ชันภายนอก return แล้ว
- ส่วนขยายฟังก์ชันซ้อนของ GCC เขียนโค้ดที่รันได้ลงบน call stack เพื่อ thunk context pointer เพราะต้องการให้ฟังก์ชันซ้อนถูกอ้างอิงเป็น function pointer ปกติ
- วิธีนี้นำไปสู่ความเสี่ยงด้านความปลอดภัยอย่างมาก และทำให้หลายแพลตฟอร์มปิดใช้งานฟีเจอร์ดังกล่าวอย่างสมบูรณ์
- การอ้างอิงฟังก์ชันท้องถิ่นของ High C ใช้ได้เหมือนค่า first-class แต่ไม่ยืดอายุไปถึงหลังฟังก์ชันภายนอก return แล้ว
- ฟังก์ชันซ้อนยังสามารถ
gotoไปยังฟังก์ชันแม่ได้ด้วย- ทำให้ทำ non-local exit ออกจากฟังก์ชันซ้อนได้เหมือนบล็อกของ Smalltalk
- สามารถใช้สิ่งนี้สร้างฟังก์ชันที่ทำงานเหมือน control flow ได้
- Objective-C ได้ blocks ที่ใช้เป็น escaping closure ได้ในปี 2009 และ C++ นำ lambdas เข้ามาในปี 2011
- ทั้งสองฟีเจอร์ไม่มีความสามารถด้าน non-local exit
- C มาตรฐานยังไม่มีฟีเจอร์ฟังก์ชันซ้อนอย่างเป็นทางการ
Generator coroutine
- MetaWare ให้ความสำคัญกับฟีเจอร์ generator ถึงขั้นจัดไว้ทั้งบทหนึ่ง
- High C รองรับ generator coroutine สไตล์ Python ใน plain C ตั้งแต่ปี 1989
- ฟังก์ชัน generator ประกาศด้วยไวยากรณ์
void foo(Arg arguments) -> (Yield yields)- ภายในฟังก์ชันสามารถเรียกฟังก์ชันวิเศษ
yield(values...)หลายครั้งเพื่อสร้างลำดับของค่า - ผู้เรียกวนผ่านค่าที่สร้างขึ้นตามลำดับด้วยไวยากรณ์ลูป
forแบบใหม่ในรูปfor variable... <- foo(arguments...) do { ... }
- ภายในฟังก์ชันสามารถเรียกฟังก์ชันวิเศษ
- การทำงานนี้สามารถผสานกับฟังก์ชันซ้อนได้อย่างซับซ้อน
- ฟังก์ชันซ้อนภายใน generator สามารถ capture การทำงานของ
yieldจาก generator ภายนอกได้ - ฟังก์ชันซ้อนสามารถเรียกตัวเองแบบ recursive เพื่อวนผ่านต้นไม้หรือโครงสร้างข้อมูล recursive แล้ว
yieldในแต่ละขั้นได้
- ฟังก์ชันซ้อนภายใน generator สามารถ capture การทำงานของ
- รูปแบบนี้ดูเหมือนเป็นวิธีที่ทำได้ยากใน Python หรือภาษากระแสหลักจำนวนมากที่มี generator coroutine
วิธีทำงานของ generator และความต่างจากภาษามาตรฐาน
- generator ของ High C ทำงานเป็น syntax sugar บนฟังก์ชันซ้อน โดยไม่ต้องมี runtime ขั้นสูง
- การประกาศ generator ในรูป
void foo(Arg arguments) -> (Yield yields)เทียบเท่ากับการประกาศฟังก์ชันปกติvoid foo(void yield(Yield yields)!, Arg arguments)yieldเป็นพารามิเตอร์โดยนัยของชนิด “full function value”- การเรียก
yield(values)ภายในเนื้อหา generator คือการเรียกฟังก์ชันปกติไปยังพารามิเตอร์ฟังก์ชันโดยนัยนี้
- ฝั่งผู้เรียก เนื้อหาในลูป
forจะถูกแปลงเป็นฟังก์ชันซ้อน- ฟังก์ชันซ้อนนี้ถูกส่งเป็นอาร์กิวเมนต์
yieldของ generator - โครงสร้างเรียบง่ายแต่ได้ผล
- ฟังก์ชันซ้อนนี้ถูกส่งเป็นอาร์กิวเมนต์
- เพราะฟังก์ชันซ้อนรองรับ non-local exit คำสั่ง
break,continue,gotoที่ออกไปนอกเนื้อหาลูปforจึงทำงานด้วยการgotoไปยังตำแหน่งนอกลูปที่เหมาะสม - มีความเป็นไปได้น้อยที่ C มาตรฐานจะพยายามรวมฟีเจอร์แบบนี้เข้ามา
- C++20 มีฟีเจอร์ coroutine ที่ยืดหยุ่นมากและซับซ้อน โดยอิงจากการแปลง coroutine ณ compile time
- ดูเหมือนว่าสามารถใช้สิ่งนี้สร้าง generator ได้
- แต่ผลลัพธ์ที่ได้คงไม่ผสานกับฟังก์ชันท้องถิ่นได้อย่างตรงไปตรงมาเช่นนี้
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
โชคดีที่ผมมี High C/C++ Language Reference ฉบับภาษาอังกฤษอยู่หนึ่งเล่ม
http://jdebp.uk./FGA/metaware-iterator-driven-for.html
http://jdebp.uk./Proposals/metaware-iterator-driven-for.html
breakหรือreturnถูกคอมไพล์อย่างไร มันต้องแปลงให้ฟังก์ชันyieldคืนค่าเป็น status code แล้วตรวจที่จุดเรียกหรือเปล่า?case, named arguments, nested functions, static nested functions, ฟีเจอร์ที่คล้าย generatorตัวอย่างเช่น สามารถเขียนได้ในรูปแบบ
int a = 1_234_567;,case 5 .. case 6:,test(b:3, a:4);static nested function จะเข้าถึงตัวแปรใน frame ของฟังก์ชันภายนอกไม่ได้ จึงเกิด error อย่าง
Error: static function test.foo.plus cannot access variable i in frame of function test.fooฟีเจอร์ที่คล้าย generator อยู่ที่ https://dlang.org/spec/statement.html#foreach_over_struct_an...
เช่น ถ้าสร้างบริการแคชในหน่วยความจำ ตัวรายการแคชเองควรไม่ให้ garbage collector ติดตามจะดีกว่า เพราะ garbage collector มักไม่รู้รูปแบบการเข้าถึงจริง จึงอาจกลายเป็นตัวขัดขวางได้ แต่ส่วนประกอบอื่น ๆ ส่วนใหญ่ของบริการนั้นกลับเหมาะกับการมี garbage collector มากกว่า
ทำไม named arguments ถึงอยู่ในรูป
test(a:4, b:3)ไม่ใช่test(.a=4, b.=3);?แล้วก็สงสัยว่าใน C จะจัดการ first-class types ได้อย่างไร
lcc-winเพิ่ม operator overloading, default function arguments และ function overloading เข้าไป ในเอกสารให้ดูหัวข้อ “generic functions” [1]คอมไพเลอร์ C ของ Plan 9 ก็เพิ่ม language extensions หลายอย่าง และบางส่วน เช่น anonymous structs/unions ก็เข้าไปอยู่ในมาตรฐาน C ในภายหลัง ปัจจุบัน GCC รับแฟล็ก
-fplan9-extensions[2] และสามารถเปิดใช้ฟีเจอร์ที่ค่อนข้างมีประโยชน์ได้ เช่น การแปลง struct pointer เป็น anonymous field โดยอัตโนมัติในการเรียกฟังก์ชันและการกำหนดค่า[1] https://lcc-win32.services.net/C-Tutorial.pdf
[2] https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
น่าเสียดายที่มันไม่ได้แพร่หลายไปทั่วโลกและส่งอิทธิพลต่อมาตรฐานภาษา น่าทึ่งที่มีฟีเจอร์แบบนี้มาตั้งนานแล้ว
Hacker News ก็เคยพูดถึงมาก่อน: https://news.ycombinator.com/item?id=38938402
มีสำเนา PDF อยู่ที่ไหนสักแห่งไหม?
yieldอยู่แล้วตั้งแต่กลางถึงปลายทศวรรษ 1970 [0] ภาษา Icon ในยุคใกล้เคียงกันก็มีฟีเจอร์ generator คล้ายกัน [1] และใช้คำว่าsuspendแทนyieldเท่าที่รู้ Ada (1983) ก็มีฟีเจอร์แบบนี้ด้วยฟีเจอร์ภาษาเหล่านี้ไม่ได้ไม่เป็นที่รู้จักโดยสิ้นเชิง
[0] https://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-2...
[1] https://dl.acm.org/doi/pdf/10.1145/800055.802034
อธิบายทั้งขีดล่างในตัวเลข, ช่วงของ
case, named parameters, nested functions และแม้กระทั่งตัวแปรฟังก์ชันแบบสมบูรณ์https://bitsavers.org/pdf/metaware/…
ให้ดู Appendix A ซึ่งอยู่ก่อนท้ายไฟล์ราว 50 หน้า
สมัยก่อนตอนเรียนและเขียนโค้ด ผมได้รู้จักผ่านเว็บที่ค่อนข้างน่าสงสัยนิด ๆ
แล้วก็จะพบประวัติศาสตร์อันหลากหลายของภาษาโปรแกรมระบบด้วย จะเห็นด้วยว่าการออกแบบ C และ Go คล้ายกันแค่ไหนในแง่ที่เมินสิ่งที่เกิดขึ้นใน ecosystem อื่น ๆ และประสบการณ์ในอดีต
ลิงก์คู่มือคอมไพเลอร์อยู่ที่ https://winworldpc.com/product/metaware-high-c-cpp/33x
PDF คู่มือ C มีข้อความลิขสิทธิ์ปี 2007
https://f.duriansoftware.com/@joe/113195961485703110
¥nไม่ใช่\nดูเหมือนว่าตัวอย่างโค้ดเหล่านี้จะเขียนด้วย Shift-JIS ใน Shift-JIS ตำแหน่งของ\ใน ASCII จะเป็น¥[0] https://en.wikipedia.org/wiki/JIS_X_0201
สำหรับการใช้งานแบบนี้ EUC-JP ดีกว่า เพราะไม่มีปัญหานี้ ใน Pascal ถ้าใช้คอมเมนต์แบบ
(* *)และไม่ใช้คอมเมนต์แบบ{ }ก็จะไม่มีปัญหานี้แม้ใช้ Shift-JISแทนที่จะเป็นแบบนั้น มีความเป็นไปได้สูงว่ามีการใช้ JIS X 0201(https://en.m.wikipedia.org.org/wiki/JIS_X_0201) ซึ่งเป็นพื้นฐานของ Shift-JIS
C:¥ไม่ใช่C:\Call (Param_A => 1, Param_B => "Foo");, ขีดล่างในตัวเลขฐานใดก็ได้ (X : Integer := 1_000;), ซับโปรแกรมซ้อนกัน และการตรวจสอบตามช่วงค่าดูเหมือนเรามักลืมกันว่า C ในยุคนั้นเป็นภาษาที่ดิบอย่างไม่น่าเชื่อเมื่อเทียบกับภาษาอื่น ๆ หลายภาษา
ผมไม่รู้กฎการเขียนภาษาญี่ปุ่นหรือกฎ kerning มากพอ แต่ดูเหมือนเอาฟอนต์แบบความกว้างแปรผันที่มีทั้งคันจิและอักษรละตินมายัดให้พอดีกับช่องแบบความกว้างคงที่
อย่างไรก็ดี ดีตรงที่ตัวอย่างโค้ดไม่ได้ใช้ฟอนต์ 8pt เหมือนหนังสือหลายเล่มที่ผมมี
try_fold()(https://scribe.rip/@veedrac/rust-is-slow-and-i-am-the-cure-3...)แต่ก็คงเพราะเหตุผลนั้นเอง ส่วนขยายเหล่านี้จึงไม่ค่อยเป็นที่รู้จัก และต้องถูกค้นพบใหม่และประดิษฐ์ซ้ำใน C/C++ สมัยใหม่ในอีกหลายทศวรรษต่อมา