3 คะแนน โดย GN⁺ 2024-10-25 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Pretty C เป็นภาษาสคริปต์ใหม่ที่เข้ากันได้กับ C และเพิ่มความสะดวกอย่าง dynamic typing, generic iteration, และ resource tracking ให้กับโปรแกรม C
  • วิธีใช้งานคือเป็นไลบรารีแบบ header-onlyที่เพียง include pretty.h ไฟล์เดียว และสามารถใช้ร่วมกับไฟล์ C และไลบรารี C เดิมได้
  • ฟีเจอร์ที่มีได้แก่ alias อย่าง string, any, nil, การอนุมานชนิดด้วย var/let, การแสดงผลแบบ generic ด้วย print/println, การเปรียบเทียบแบบ generic ด้วย equal, และ macro ternary ที่อ่านง่าย
  • เพื่อลดงานซ้ำด้านการวนลูปและการจัดการหน่วยความจำ มีไวยากรณ์แบบ macroอย่าง foreach, forthese, fortimes, forrange, new, vector, delete, with, lambda, try/catch
  • บางฟีเจอร์มีเงื่อนไขเช่น GCC, Clang, C23+, C11+ และตัวแปรที่ผูกด้วย with เป็น void * จึงอาจต้องแปลงชนิดก่อนใช้งาน

เป้าหมายและวิธีใช้งานของ Pretty C

  • Pretty C เป็นภาษาสคริปต์ที่เข้ากันได้กับ C และเพิ่ม dynamic typing, generic iteration, resource tracking รวมถึงความสะดวกด้านไวยากรณ์หลายอย่างให้กับโปรแกรม C
  • ยังคงรักษาความเข้ากันได้ย้อนหลังกับ C และไลบรารี C เดิม
  • ได้แรงบันดาลใจจาก Lua, Python, JavaScript, และ Lisp
  • การติดตั้งสามารถ clone repository หรือคัดลอกเฉพาะไฟล์ pretty.h ไปใช้ก็ได้
    • สามารถ include จากไฟล์ C ใดก็ได้ด้วย #include "pretty.h"
    • หรือจะกำหนด path ของ Pretty C ใน include path ก็ใช้งานได้

เฮดเดอร์ที่ include มาให้พื้นฐานและ macro แบบง่าย

  • Pretty C include standard header มาให้เพื่อให้ใช้ชนิดข้อมูลและรูปแบบตัวดำเนินการที่ใช้บ่อยได้ทันที
    • stdbool.h: true, false, bool
    • stdint.h: ชนิดจำนวนเต็มความกว้างคงที่ เช่น uint64_t
    • iso646.h: ตัวดำเนินการทางเลือก เช่น and แทน &&, or แทน ||
  • ยังมีmacro แบบง่ายที่นิยามไว้ให้ใช้งานบ่อยด้วย
    • max, min: ค่าสูงสุดและต่ำสุดของตัวเลขสองค่า
    • len: ความยาวของอาร์เรย์
    • default: ให้ค่า fallback
    • limit: จำกัดค่าให้อยู่ในช่วง
    • between: ตรวจว่าตัวเลขอยู่ในช่วงหรือไม่
    • divisible: ตรวจว่าหารด้วยอีกจำนวนหนึ่งลงตัวหรือไม่

ชนิดข้อมูลและ alias

  • Pretty C มีalias ของชนิดข้อมูล C แบบสั้นให้ใช้
    • string คือ char*
    • byte คือ char
    • bytes คือ char*
    • any คือ void*
    • มีรูปย่อของจำนวนเต็มอย่าง uchar, ushort, uint, ulong
  • ยังมีalias ด้านไวยากรณ์ที่ได้รับอิทธิพลจาก Lua และ Lisp
    • eq, is: ==
    • bitnot, bitxor: alias สำหรับการทำงานระดับบิต
    • success, fail, failure: alias สำหรับแพตเทิร์น success == 0
    • below, above, upto, downto: ตัวดำเนินการเปรียบเทียบ
    • even, odd, positive, negative, zero, empty: predicate สำหรับตัวเลขและข้อมูล
    • nil: NULL
    • until: while แบบปฏิเสธ
    • elif: else if
    • ifnt, elifnt: รูปแบบตระกูล if(!...)
    • repeat: alias ของ do แบบ Lua
    • done, finish, pass: alias ของ break, continue
    • always, forever, loop, indefinitely: รูปแบบสำหรับลูปไม่สิ้นสุด
    • never, comment: รูปแบบที่ไม่รันโค้ดแต่ยังเปิดให้คอมไพเลอร์วิเคราะห์ได้

การอนุมานชนิด การแสดงผล การเปรียบเทียบ และ ternary

  • การอนุมานชนิดใช้ได้บน GCC, Clang หรือ C23+
    • มีรูปแบบตัวอย่างเช่น var t = time(0);, let lt = localtime(&t);, local at = asctime(lt);
  • การแสดงผลแบบ genericใช้ได้บน C11+
    • print ใช้แสดงค่าที่ส่งเข้าไป
    • println เพิ่มขึ้นบรรทัดใหม่หลังแสดงผล
  • การเปรียบเทียบความเท่ากันแบบ genericก็มีให้เป็นฟีเจอร์ของ C11+
    • เช่นการเทียบสตริง equal("NA", line)
    • และมีตัวอย่างอย่าง equal(0.3, 0.2 + 0.1)
  • ตัวดำเนินการ ternary สามารถทำให้อ่านง่ายขึ้นด้วย macro แบบข้อความ
    • when ขยายเป็นสตริงว่างเพื่อช่วยด้านการอ่าน
    • unless ขยายเป็น not จึงทำงานเป็นรูปปฏิเสธของ when
    • then คือ ?
    • other และ otherwise คือ :
    • only ใช้เมื่อไม่ต้องการส่วน otherwise
    • otherwhen ใช้สำหรับต่อเงื่อนไขถัดไป

Macro สำหรับการวนลูป

  • Pretty C ทำ abstraction ของแพตเทิร์น for loop ที่ใช้บ่อยด้วยmacro
  • foreach (var, type, length, ...)
    • ใช้วนผ่านอาร์เรย์หรือพื้นที่หน่วยความจำ
    • ในแต่ละรอบ var จะเป็น pointer ไปยังสมาชิกของอาร์เรย์นั้น
    • เพราะเป็น pointer จึงสามารถแก้ไขสมาชิกนั้นได้โดยตรงเมื่อจำเป็น
  • forthese (var, type, ...)
    • วนผ่านอาร์กิวเมนต์แบบ variadic ที่ส่งเข้ามา และ bind แต่ละค่าเข้ากับ var ชนิด type
  • fortimes (var, times)
    • ย่อแพตเทิร์นที่พบบ่อยของการวนซ้ำตั้งแต่ 0 จนถึงจำนวนครั้งบวกที่กำหนด
  • forrange (var, init, target)
    • วนผ่านช่วงตัวเลขตั้งแต่ init ถึง target
    • init และ target สามารถเป็นจำนวนเต็ม signed หรือ unsigned แบบใดก็ได้
    • ถ้า init มากกว่า target ขั้นของการวนจะลดลง
  • forrangeby (var, type, init, target, by)
    • วน var ที่กำหนด type จาก init ถึง target ด้วยช่วงห่าง by

การจัดสรรหน่วยความจำและยูทิลิตีสำหรับบล็อก

  • macro สำหรับ allocation ได้รับอิทธิพลจาก C++ และช่วยทำให้แพตเทิร์นการจัดสรรหน่วยความจำทั่วไปง่ายขึ้น
    • new (type, ...): สร้าง struct เป็นต้น ในรูปแบบคล้าย C++ new
    • vector (length, type, ...): ทำให้แพตเทิร์นการจัดสรรอาร์เรย์ตามความยาวและชนิดที่กำหนด พร้อมค่าเริ่มต้น เรียบง่ายขึ้น
    • delete (...): เหมือน free แต่ใช้ได้สำหรับคนที่ชอบชื่อแบบ C++
  • ยูทิลิตีสำหรับบล็อก มี macro สำหรับ local binding, deferred execution, และงานแบบเป็นบล็อก
    • lambda (ret, name, ...): ทำให้ใช้ nested function, lambda, closure ใน C ได้บน GCC, Clang หรือ C++
    • with (var, close, ...): ระบุฟังก์ชันปล่อยทรัพยากร close ล่วงหน้า จึงมีประโยชน์กับอ็อบเจ็กต์ที่จัดสรรแบบไดนามิกหรือ file handle
      • ข้อเสียคือ var ที่ bind ไว้จะเป็น void * จึงอาจต้อง cast เป็นชนิดที่ต้องการก่อนใช้งาน
    • defer: ถูกถอดออกแล้ว และแนะนำให้ใช้ defer แบบใหม่ที่ใช้งานได้แล้วแทน
    • try และ catch: ช่วยให้เขียนการจัดการข้อผิดพลาดใน C ได้ในรูปแบบที่หรูขึ้น
    • NOERR, NOERROR: มีไว้เพื่อความสะดวกกับ error switch-case

วิธีการมีส่วนร่วม

  • เมื่อต้องเพิ่มฟังก์ชัน มีคำขอให้ทำตาม Linux Kernel Style Guide
    • แนะนำให้รัน make indent ก่อน commit
  • นิยมให้ทำตาม A’s Commit Message Guide แต่ไม่ได้บังคับ

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

 
GN⁺ 2024-10-25
ความคิดเห็นจาก Hacker News
  • ประเพณีการสร้าง ภาษาเฉพาะโดเมน ภายใน C มีมานานแล้ว
    Stephen Bourne อยากเขียนเชลล์ให้เป็น ALGOL จึงเคี่ยวเข็ญ C preprocessor อย่างไม่ลดละ จนทำให้มันดูเหมือนภาษาที่เขาชอบ
    https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh...

    • ตัวอย่างที่เขียนด้วยวิธีนั้นอยู่ที่นี่
      https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh...
    • มันแย่น้อยกว่าที่คาดไว้ไม่ถึงครึ่งด้วยซ้ำ
    • เป็น ตำนาน Unix ชั้นยอด ว่ากันว่าเป็นแรงบันดาลใจให้การประกวดโค้ด C แบบอ่านยาก
      https://www.ioccc.org/
  • อยากให้ใครสักคนอธิบายหน่อยว่านี่เป็นมุกตลก หรือผู้เขียนสับสนจริง ๆ กันแน่
    ไม่มีอะไรสมเหตุสมผลเลย ไม่ใช่ “scripting” ด้วยซ้ำ บอกว่าจะนำ “strong typing” เข้ามา แต่ไม่ได้ทำอะไรกับ type เลย และ alias ของ operator ที่บอกว่าได้แรงบันดาลใจจาก Lua กับ Lisp ก็ไม่ได้มีอยู่ในสองภาษานั้นสักภาษา แต่ก็ไม่ได้ดูเหมือนล้อเลียนอย่างชัดเจน เลยสับสนจริง ๆ

    • ดูเหมือนจะจริงจังนะ เพราะบอกว่า “ทิ้ง Lua, Python, JavaScript, Ruby และภาษาอื่น ๆ อีกหลายสิบภาษาไปซะ Pretty C คือภาษา scripting ขั้นสุดยอด เร็วปานสายฟ้า และยังมี strong typing!!”
    • ผมไม่คิดเลยว่าผู้เขียนกำลังสับสน แน่นอนว่าการสับสนเองก็ไม่เป็นไร
  • อ้างว่าเป็นภาษา scripting แต่ก็ยังต้อง คอมไพล์ โปรแกรมอยู่ดี น่าหัวเราะ
    ถ้าเอา CINT(https://root.cern.ch/root/html534/guides/users-guide/CINT.ht...) มาต่อ ก็รันทันทีและใช้ REPL ได้

    • ถ้าเป้าหมายของ repo นี้คือสร้างความเจ็บปวด ก็น่าจะใส่ shebang ลงในไฟล์ให้รันได้ไปเลยไม่ใช่เหรอ [0]
      ก่อนหน้านี้เคยเห็นบล็อกโพสต์ที่เจาะลึกว่า ./foo ทำงานอย่างไร และไฟล์ ELF ถูก execute อย่างไร น่าจะใช้วิธีเดียวกันลงทะเบียนโปรแกรม .c ให้คอมไพล์แล้วรันได้ด้วย
      [0] https://gist.github.com/jdarpinian/1952a58b823222627cc1a8b83...
    • ใช้ tcc ยังจะดีกว่า [0] เบากว่าสัตว์ประหลาดตัวนั้นเยอะ และเป็น C ไม่ใช่ C++
      [0] https://bellard.org/tcc/tcc-doc.html
    • ตอนนี้ CERN ใช้ cling แล้ว (https://github.com/root-project/cling)
    • ใครเป็นคนกำหนดว่าภาษา scripting ห้ามถูกคอมไพล์กันล่ะ และ Clang-REPL ก็เป็นอีกวิธีหนึ่งที่ทำให้เป็นมิตรกับ REPL ได้
  • “มอบ syntax sugar มากพอที่จะทำให้นักพัฒนา C เกิดหัวใจวายแบบเบาหวาน”
    ชอบประโยคนี้มาก

  • เห็นมีหลายอย่างที่ควรเพิ่มเข้าไปใน CNoEvil[0] กับ ogw[1] ทุกครั้งที่โปรเจกต์นี้โผล่กลับมาทุก ๆ ไม่กี่เดือน ดูเหมือนจะมีเพิ่มเสมอ
    [0] https://git.sr.ht/~shakna/cnoevil3/
    [1] https://git.sr.ht/~shakna/ogw

    • “นำไอเดียแย่ ๆ จำนวนมากมาบดอัดใส่เป็นสัตว์ประหลาดอันน่ารังเกียจ”
      เป็นประโยคที่ทำให้รักจากก้นบึ้งของหัวใจจริง ๆ
  • นี่กำลังทำผิดพลาดแบบเดียวกับ Python 2 สตริงกับไบต์ ไม่ใช่ type เดียวกัน และไม่ควรถูกปฏิบัติแบบนั้น

    • ข้อความในเชลล์ เช่นค่าภายใน argv หรือ output ของ subprocess ควรมองว่าเป็น type อะไรดี
      ในนั้นอาจมีค่าขยะอะไรก็ได้ จึงไม่ใช่สตริง UTF-8 ที่ถูกต้อง แต่เครื่องมืออย่าง awk กับ grep ก็ถูกใช้กันทุกที่
      สตริงกับไบต์อาจมองได้ว่าเป็น type ทั่วไปเดียวกัน เพียงแต่บางครั้งการให้ type ภายในที่ต่างออกไปกับไบต์ UTF-8 ที่ถูกต้องก็มีประโยชน์ Rust จัดการส่วนนี้ได้ค่อนข้างดีด้วย OsString กับ String
    • โปรเจกต์นี้คือ pretty.c และบอกว่าตั้งใจให้ “เข้ากันได้ย้อนหลัง กับ C และไลบรารีทั้งหมด” แล้วจะมีตัวเลือกไหนสมเหตุสมผลไปกว่าการคง multibyte string ของ C ไว้อีกล่ะ
      https://en.cppreference.com/w/c/string/multibyte
    • ไม่เห็นด้วย หลักการนี้ตั้งอยู่บนสมมติฐานทั้งหมดต่อไปนี้: ข้อมูลสตริงถูกเข้ารหัสอย่างถูกต้อง, มี encoding ของสตริงเพียงแบบเดียว (มักเป็น UTF-8), ต้องตรวจสอบความถูกต้องตอนสร้างข้อมูลสตริง, ห้ามตัด codepoint เชิงตรรกะโดยเด็ดขาด, ห้ามทำงานแบบสตริงกับไบต์ที่ “ไม่ valid”, encoding ที่ถูกต้องคือทั้งจุดเริ่มต้นและจุดจบของการตรวจสอบความถูกต้อง
      ไม่มีข้อใดเป็นจริงเสมอไป การห่อ byte sequence ที่ผ่านการตรวจสอบแล้วไว้ใน type ที่สร้างได้ผ่านการตรวจสอบเท่านั้นมีประโยชน์ แต่พอทำแบบนั้นแล้ว Utf8String กับ EmailAddress ก็แทบเป็นหมวดเดียวกัน ไม่มีเหตุผลที่ type system จะต้องปฏิบัติกับ encoding เป็นกรณีพิเศษเพียงอย่างเดียว
    • ขึ้นอยู่กับว่าคุณนิยาม “สตริง” อย่างไร
      ถ้าหมายถึง “ข้อความที่มนุษย์อ่านได้” ก็เห็นด้วยว่าสตริงไม่เหมือนกับอาร์เรย์ไบต์ใด ๆ แต่หลายภาษาไม่ได้บังคับนิยามนั้น
  • ทำให้นึกถึงโค้ดเบส C++ ที่เคยต้องเข้าไปตรวจสอบเมื่อนานมาแล้ว ทั้งหมดเขียนเหมือนกับว่าเป็น Java ทุกอย่างใช้ camelCase ตัวแปรของคลาสทุกตัวมี getter/setter และมี interface อยู่ทั่วไปหมด

    • นั่นยังไม่ได้เห็นของจริง ต้องดู ซอร์สของ Bourne shell ใน Unix Seventh Edition
      https://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd...
      ยากจะเชื่อว่าไม่ใช่ ALGOL
    • camelCase, getter/setter, interface ในหลายกรณีก็ไม่ได้ต่างจากแนวทางปฏิบัติมากนัก ตัวอย่างเช่นโค้ดของ Windows ก็เป็นแบบนั้น แน่นอนว่าไม่ได้ถึงขั้นตัวแปรทุกตัวก็เถอะ หลายส่วนของการออกแบบ Java ก็ คัดลอกมาจาก C++ เช่นกัน
    • เคยเห็นโค้ดเบสคล้าย ๆ กันที่เขียนโดยคนที่ใช้ Java มานานเกินไป ถึงขั้นมี คลาส String ของตัวเองที่จริง ๆ แค่ห่อ std::string ไว้ แต่ใส่เมธอดสไตล์ Java เข้าไปด้วย
    • ดูเหมือนโชคดีที่ไม่ได้ใช้ MSVC
      https://learn.microsoft.com/en-us/cpp/cpp/property-cpp?view=...
    • ผมก็เคยเจอแบบนั้นเหมือนกัน แถมยังส่งสตริงเป็น f(String *) แบบ Java ทำให้โค้ด C++ เป็นแนว f(new String("Hello")
  • ถ้าเรื่องนี้น่าสนใจ คุณอาจชอบ libcello.h ด้วย
    https://www.libcello.org

    • ใช่แล้ว นั่นเป็นหนึ่งในแรงบันดาลใจ
  • ชอบชื่อของชนิดข้อมูลต่าง ๆ และการเลือกมาโครฟังก์ชันในตัวอย่าง min ก็สมบูรณ์แบบ Len ก็ถูกใจ
    ตัวดำเนินการบูลีนแบบมีชื่ออาจจะเกินไปนิด แต่ก็น่าลอง ส่วน ternary operator เดิมทีอ่านยากอยู่แล้ว จึงยังมีพื้นที่ให้ปรับปรุงได้มาก ผมยังไม่ถูกโน้มน้าวเต็มที่กับนิยามลูปทั้งหมด แต่บางอันก็มีเหตุผล การติดตามทรัพยากร น่าประทับใจ และรูปแบบ for... ดูขี้เหร่นิดหน่อย อาจตั้งชื่ออื่นได้
    โดยรวมเป็นความพยายามที่ค่อนข้างแข็งแรง ผมอยากให้ดีไซน์ประมาณ 8/10 วิธีที่ร่างด้วยมาโคร C นั้นสง่างามจริง ๆ และดูเหมือนโค้ดที่ดีจริง ๆ ไม่แน่ใจว่าจะใช้เองไหม เพราะมันเป็นภาษาใหม่ และผมก็ชอบ C อยู่แล้ว ถึงอย่างนั้น วิธี implement ก็ชัดเจนมาก จึงอาจช่วยให้ผู้คนเรียนรู้ C และคิดเรื่องการออกแบบภาษาได้

    • ไม่จำเป็นต้องใช้ทั้งหมด โปรเจกต์ของผมส่วนใหญ่ใช้แค่บูลีน, len(), max/min และ มาโครตัวดำเนินการ บางตัว เพราะยังไม่ค่อยต้องใช้ฟีเจอร์น่าอร่อยอื่น ๆ มากนัก ดังนั้นอยากให้ลองใช้แค่มาโครตัวดำเนินการสักไม่กี่ตัวดูก็ได้
  • ถ้า ifnt มีไว้สำหรับ if(!...) แล้ว unless ดูอ่านง่ายกว่า ifnt

    • อีกประเด็นที่เถียงกันได้เรื่องชื่อ คือการเรียกลูปไม่สิ้นสุด for(;;) ว่า always
      ในภาษาอื่นเคยเห็นใช้ loop แต่ Qt เรียกว่า forever ซึ่งสวยจริง ๆ และก็ให้ความรู้สึกเป็น Qt มาก
    • กลับกัน ifnt เวลาอ่านออกเสียงแล้วตลกดี
    • ใช่ แต่ผมกัน unless ไว้สำหรับเงื่อนไขแบบ ternary เพราะฝั่งนั้นมีประโยชน์กว่ายังไงล่ะ
      อ้อ เดี๋ยวนะ นี่ John Tromp นี่นา คนสร้าง BLC! ผมเป็นแฟนครับ