แนะนำภาษาโปรแกรม Cognition
ประเด็นปัญหา
- นักเขียนภาษา Lisp อ้างว่าสามารถสร้างการเมตาโปรแกรมมิ่งและระบบทั่วไปด้วยโค้ดแบบ S-expression และระบบแมโครเชิงฟังก์ชัน
- แต่ Lisp ก็มีปัญหาพื้นฐาน
- การทำเมตาโปรแกรมมิ่งไม่เหมือนกับการเขียนโปรแกรมทั่วไป จึงทำให้ Lisp ต้องมีไวยากรณ์ที่ค่อนข้างตายตัวเสมอ (เช่น เครื่องหมายวงเล็บหรืออักขระสำหรับ look-ahead)
- วงเล็บเปิดบอกให้อ่านต่อไปจนกว่าจะพบวงเล็บปิดที่ตรงกัน
- ดังนั้นอักขระวงเล็บซ้าย/ขวาจึงกลายเป็นสิ่งที่ไม่สามารถปรับเปลี่ยนได้ภายในภาษา (แม้ในเชิงแนวคิดอาจไม่เป็นเช่นนั้น แต่ในบางการนำไปใช้ก็ไม่สามารถทำได้)
- ที่สำคัญกว่านั้น การเปลี่ยนลำดับการจัดประเภทอักขระเหล่านี้ภายหลังโดยไม่พึ่งการจัดการสตริงแทบเป็นไปไม่ได้
- ภาษาตัวอื่นๆ ก็มีวิธีการอื่นในการตัดสินใจว่าจะอ่านข้อมูลต่อไปจากอักขระใดบ้าง
- ขั้นตอนนี้คือไวยากรณ์ (syntax)
- Cognition ใช้ antisyntax แบบ postfix แบบครบวงจร จึงต่างออกไป
- คล้ายกับภาษาประเภท concatenative แต่ภาษาประเภท concatenative ก็มีปัญหาสองข้อสำคัญ
- การนำอักขระวงเล็บซ้าย/ขวาเข้ามา (ซึ่งจริงๆแล้วเป็น prefix notation)
- อักขระอัญประกาศสำหรับสตริง
- จึงไม่เหมาะกับการเป็นภาษาโปรแกรมแบบทั่วไป
- ในการนำไปใช้งานไวยากรณ์แบบ C ของ Lisp ก็พบปัญหาแบบเดียวกัน (มีการใช้ escape character มากเกินไป และต้องพึ่งอักขระช่องว่างเพื่อแยกจุดเริ่มต้น/จุดสิ้นสุดของโทเค็นบางตัว)
- Racket มีระบบแมโคร แต่ไม่ทำงานอย่าง dynamic ที่ runtime และใช้การ preprocess
แนะนำ Cognition
- เป็นโปรเจกต์ที่ทำงานร่วมกับ Matthew Hinton มาหลายเดือน
- เป้าหมายคือการทำให้เกิดระบบไวยากรณ์ที่ยืดหยุ่นที่สุดในหนึ่งในระบบที่รู้จัก โดยใช้ postfix แบบครบถ้วน
- อาจต้องมีความรู้พื้นฐานด้าน grammar/tokenization/parsing แต่พยายามอธิบายให้เข้าใจง่ายที่สุดเท่าที่ทำได้
- ที่เก็บโค้ด: https://github.com/metacrank/cognition
Baremetal Cognition
- Baremetal Cognition คล้ายกับ Brainfuck แต่สามารถทำเมตาโปรแกรมมิ่งขั้นสูงได้
- ตัวอย่างโค้ด bootstrap:
ldfgldftgldfdtgldf dfiff1 crank f
- ช่องว่างและการขึ้นบรรทัดใหม่มีความสำคัญมาก
- บรรทัดที่ 2 คือมีช่องว่างหลัง
df
- บรรทัดที่ 3 มีอักขระช่องว่าง
- จากนั้นจึงสามารถแนะนำแนวคิดใหม่อีก 2 อย่างคือ delimiter และ ignore
Tokenization
- Delimiter ทำให้ tokenizer แยกจุดเริ่มต้นและจุดสิ้นสุดของโทเค็นได้
- รายการ tokenizer อักขระตัวเดียวเป็นที่สาธารณะ และสามารถแก้ไข/อ่านได้ภายใน Cognition
- อักขระที่ถูก ignore จะถูก tokenizer ข้ามทั้งหมดในขั้นตอนแรกของ read-eval-print loop เสมอ
- กล่าวคือ ข้ามชุดอักขระ ignore ที่ตั้งไว้เมื่อเริ่มเก็บโทเค็น
- โดยปกติ ทุกอักขระเป็น delimiter และไม่มีอักขระ ignored
- ด้วยรายการ delimiter และ ignored-character สามารถสลับโหมด blacklist/whitelist ได้สำหรับอักขระเหล่านี้ (เพื่อให้กระชับและใช้งานได้จริง)
Falias
- Falias คือรายการคำที่เมื่อถูกวางลง stack จะถูก execute
- Falias ทุกตัวจะ execute ค่าบนสุดของ stack (เทียบได้กับ
eval ใน Stem)
f เป็น Falias พื้นฐาน: มัน execute stack top d โดยไม่ต้องวาง f ลงสแตก
d แปลง delimiter list ให้เป็นค่า string ของคำ (หมายความว่า l จะถูกยกเว้นจาก delimiter)
- ในสภาพแวดล้อมมาตรฐาน คำใดๆ จะไม่ถูก execute ยกเว้น Falias พิเศษ
ข้อสังเกตเรื่อง delimiter
- delimiter มีกฎที่น่าสนใจ
- หากอักขระไม่ได้เป็น ignore ในลูป tokenize อักขระ delimiter จะถูกใส่ต่อเข้ากับโทเค็นปัจจุบันและเก็บต่อไป
- ตรงนี้ตรงข้ามกับ singlet (ซึ่งจะใส่ตัวเองแล้วข้ามเพื่อจบการเก็บโทเคน)
- สามารถกำหนดได้ว่าถูก blacklist หรือ whitelist
- สามารถ blacklist/whitelist รายการ delimiter, singlet และ ignored character
- โดยเริ่มต้นมี blacklist ของ delimiter, whitelist ของ singlet และ whitelist ของ ignored character เท่ากับไม่มีเลย
- อักขระอื่นๆ ทั้งหมดจะถูกเก็บเป็นส่วนหนึ่งของโทเค็นปัจจุบันต่อเนื่องกัน จนกว่า loop จะหยุดโดยกฎ delimiter หรือ singlet
โค้ด bootstrap ต่อ
ldf
- ทำให้
l ไม่เป็น delimiter
gldftgldfdtgldf dfiff1 crank f
- เนื่องจาก
d เป็น delimiter จึง gl ถูกวางบน stack และเรียกใช้งาน Falias f เพื่อทำให้ gl ไม่เป็น delimiter
tgl ถูกวางบน stack และ df ทำให้ tgl กลายเป็นอักขระที่ไม่เป็น delimiter
dtgl ถูกวางบน stack และ \ndf ทำให้ \n (newline) เป็นอักขระที่ไม่เป็น delimiter (newline อยู่จริงๆในโค้ด)
- ตามกฎ delimiter จึงใส่ช่องว่างและ
\n ลง stack (รวมช่องว่างในบรรทัดที่ 3)
- โทเค็น
\n จะถูกสร้างอีกตัว
- stack ตอนนี้เป็นดังนี้ (จากล่างขึ้นบน):
3. dtgl
2. [อักขระช่องว่าง]\n
- [อักขระช่องว่าง]\n
df ตั้งค่า \n ให้เป็นอักขระที่ไม่ใช่ delimiter
if ตั้ง \n เป็นอักขระ ignore (ข้ามตอนเริ่ม tokenization)
f execute dtgl เพื่อสลับโหมด dflag ระหว่าง whitelist/blacklist ของ delimiter
- ตอนนี้อักขระที่ไม่ใช่ delimiter ทั้งหมดกลายเป็น delimiter และอักขระ delimiter ทั้งหมดกลายเป็นไม่ใช่ delimiter
- สุดท้ายช่องว่างกับ newline กลายเป็น delimiter ของโทเค็น และถูก ignore เมื่อเริ่ม tokenization
- จากนั้น
1 จะถูก tokenize และวางบน stack, จากนั้น crank ถูก tokenize และถูก execute โดย f (1 ในกรณีนี้ถือเป็นตัวเลข แต่ใน Cognition ทุกอย่างเป็นคำ)
- การ bootstrap sequence เสร็จสมบูรณ์แล้ว! สิ่งที่
crank ทำจะอธิบายในส่วนต่อไป
สรุป bootstrap
- Cognition สามารถเปลี่ยนกฎ tokenization แบบ dynamic ได้ด้วยการเขียนโปรแกรม
- เรื่องนี้ทำไม่ได้ในภาษาอื่น
- สามารถเขียน tokenizer สำหรับภาษาภายนอกภายใน Cognition และ tokenize ตามที่ต้องการได้
- เนื่องจากใช้ postfix และไม่ใช้ look-ahead ทำให้ทำได้
- ไม่ต้อง parse โทเค็นมากกว่าหนึ่งตัวก่อนประเมิน expression
- ด้วย Falias คุณสามารถ execute คำได้โดยไม่ต้องเริ่มจากคำต้นทางหรือตัว keyword เริ่มต้น
Crank
- ระบบ metacrank ช่วยกำหนดวิธีเริ่มต้นสำหรับการ execute คำใน stack
- คำสั่ง
crank รับตัวเลขเป็นอาร์กิวเมนต์ และจะ execute stack top โดยอัตโนมัติเมื่อมีการใส่คำใน stack n คำ
- ตัวอย่างโค้ด (
crank 1 คือค่าเริ่มต้น):
5 crank 2
crank 2 crank
1 crank unglue swap quote prepose def
- ในสภาพแวดล้อม
crank 1 สามารถหยุดการใช้ f ระหว่างการประเมินโทเค็น
- ประเมินหนึ่งคำต่อหนึ่ง token ที่ถูก tokenize
- เพราะโปรแกรม syntax ที่ใช้ช่องว่างและ newline ทำให้ตีความโค้ดได้อย่างเป็นธรรมชาติ
- โค้ดเริ่มจากการพยายามประเมิน
5 (ไม่ใช่ builtin จึงประเมินตัวเอง)
crank ถูกตั้งค่าให้ execute เมื่อมีการใส่ 5 คำลงสแต็ค
2crank, 2, crank, 1 ถูกวางบน stack ทั้งหมด (เพราะตั้งค่า crank 5 ทำให้ crank เป็น builtin และจึงไม่ถูก execute):
4. 2crank
3. 2
2. crank
- 1
crank เป็นคำที่ 5 จึงถูก execute (ตั้งไว้ crank 1)
unglue เป็น builtin ที่ดึงค่าของคำบนสุดของ stack (ผ่าน 1 ที่เรียกใช้)
- คือดึง function pointer ที่ผูกกับ builtin
crank
- Stack มีลักษณะดังนี้:
3. 2crank
2. 2
- [CLIB]
- CLIB คือ function pointer ที่ชี้ไปยัง builtin
crank
- การ execute
swap:
3. 2crank
2. [CLIB]
- 2
- execute
quote (builtin ที่ quote ค่าบนสุด):
3. 2crank
2. [CLIB]
- [2]
- execute
prepose (คล้าย compose ของ Stem แต่เป็นการวางไว้หน้าสุดและเรียกว่า VMACRO)
2. 2crank
- ([2] [CLIB])
- เรียก
def
- ใส่
2 ลง stack และนิยามคำ 2crank ให้เรียก function pointer ของ builtin crank
- ต้องอธิบายเพิ่มเติมว่า VMACRO คืออะไร และความแตกต่างระหว่าง stack ของ Cognition กับ Stem stack
ความต่างกับ Stem
- ใน Stem stack คำสามารถวางลง stack ได้โดยตรง
- ใน Cognition คำจะไม่ถูกประเมิน แต่ถูกใส่ไว้ใน container แล้วค่อยวางใน stack
- ใน Stem
compose แบบคำสั่งทำงานกับคำ (หรือ container ที่มีคำเดียว) กับ container อื่น
- สิ่งนี้ช่วยให้ API ของ Cognition สอดคล้องกันมากขึ้น
- คำเช่น
cd ก็นำแนวคิดนี้ไปใช้ด้วย
Macro
- อีกความต่างอีกอย่างหนึ่งเมื่อเทียบกับ Stem quote และ container ของ Cognition
- เมื่อประเมิน macro สิ่งทั้งหมดใน macro จะถูก evaluate และ crank จะถูก ignore
- เมื่อคำถูกผูกแล้ว เวลาเช็ก/ประเมินคำนั้น
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
สรุปความคิดเห็นสำคัญบางข้อได้ดังนี้: