7 คะแนน โดย GN⁺ 2025-08-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Zig ใช้ ไวยากรณ์แบบวงเล็บปีกกา คล้ายกับ Rust แต่ปรับปรุงด้วยความหมายของภาษาที่เรียบง่ายกว่าและตัวเลือกด้านไวยากรณ์ที่ประณีตกว่า
  • ลิเทอรัลจำนวนเต็ม ทุกตัวเริ่มต้นเป็น comptime_int และจะถูกแปลงแบบชัดเจนเมื่อมีการกำหนดค่า ส่วน ลิเทอรัลสตริง ใช้รูปแบบ raw string แบบย่อที่อิงกับ \\
  • record literal ในรูปแบบ .x = 1 ทำให้ค้นหาการเขียนค่าให้ฟิลด์ได้ง่าย และทุกชนิดข้อมูลถูกแสดงอย่างสม่ำเสมอด้วยรูปแบบ prefix
  • ใช้ and·or เป็นคีย์เวิร์ดควบคุมการไหล และคำสั่ง if·loop สามารถละวงเล็บปีกกาได้ตามต้องการ โดยตัว formatter จะช่วยรับประกันความปลอดภัย
  • โดยไม่มีเนมสเปซ Zig จัดการ ทุกอย่างเป็น expression เพื่อรวมไวยากรณ์ของชนิดข้อมูล·ค่า·แพตเทิร์นเข้าด้วยกัน และใช้งาน generic·record literal·ฟังก์ชันในตัว (@import, @as เป็นต้น) ได้อย่างกระชับ

ภาพรวม

  • Zig มีรูปลักษณ์คล้าย Rust แต่เลือกใช้โครงสร้างภาษาที่เรียบง่ายกว่า
  • การออกแบบไวยากรณ์มุ่งเน้นที่ ความเป็นมิตรต่อ grep, ความสม่ำเสมอของไวยากรณ์, และ การลดสิ่งรบกวนทางสายตาที่ไม่จำเป็น

ลิเทอรัลจำนวนเต็ม

const an_integer = 92;  
assert(@TypeOf(an_integer) == comptime_int);  
  
const x: i32 = 92;  
const y = @as(i32, 92);  
  • ลิเทอรัลจำนวนเต็มทั้งหมดมีชนิดเป็น comptime_int
  • เมื่อต้องกำหนดค่าให้ตัวแปร ต้องระบุชนิดอย่างชัดเจนหรือใช้ @as เพื่อแปลง
  • รูปแบบ var x = 92; ใช้งานไม่ได้ และต้องระบุชนิดอย่างชัดเจน

ลิเทอรัลสตริง

const raw =  
    \\Roses are red  
    \\  Violets are blue,  
    \\Sugar is sweet  
    \\  And so are you.  
    \\  
;  
  • แต่ละบรรทัดเป็นโทเค็นแยกกัน จึงไม่มีปัญหาเรื่องการเยื้อง
  • ไม่จำเป็นต้อง escape \\ เอง
โฆษณา

record literal

const p: Point = .{  
    .x = 1,  
    .y = 2,  
};  
  • รูปแบบ .x = 1 ช่วยแยกความต่างระหว่างการอ่าน/เขียนได้ดี
  • สัญกรณ์ .{} แยกจาก block ได้ชัดเจน และแปลงเป็นชนิดผลลัพธ์โดยอัตโนมัติ

สัญกรณ์ชนิดข้อมูล

u32        // จำนวนเต็ม  
[3]u32     // อาร์เรย์ความยาว 3  
?[3]u32    // อาร์เรย์ที่เป็น null ได้  
*const ?[3]u32 // พอยน์เตอร์ค่าคงที่  
  • ชนิดข้อมูลทั้งหมดใช้สัญกรณ์แบบ prefix
  • การ dereference ใช้สัญกรณ์แบบ suffix (ptr.*)

identifier

const @"a name with space" = 42;  
  • ใช้หลีกเลี่ยงการชนกับคีย์เวิร์ด หรือกำหนดชื่อพิเศษได้
โฆษณา

การประกาศฟังก์ชัน

pub fn main() void {}  
fn add(x: i32, y: i32) i32 {  
    return x + y;  
}  
  • คีย์เวิร์ด fn กับชื่อฟังก์ชันอยู่ติดกัน ทำให้ค้นหาได้ง่าย
  • การระบุชนิดคืนค่าไม่ใช้ ->

การประกาศตัวแปร

const mid = lo + @divFloor(hi - lo, 2);  
var count: u32 = 0;  
  • ใช้ const และ var
  • การระบุชนิดอยู่ในลำดับ ชื่อ: ชนิด

การควบคุมการไหล: and/or

while (count > 0 and ascii.isWhitespace(buffer[count - 1])) {  
    count -= 1;  
}  
  • and, or เป็นคีย์เวิร์ดควบคุมการไหล
  • การคำนวณแบบบิตใช้ &, |

คำสั่ง if

.direction = if (prng.boolean()) .ascending else .descending;  
โฆษณา
  • วงเล็บจำเป็น แต่วงเล็บปีกกาเป็นทางเลือก
  • zig fmt ช่วยรับประกันการจัดรูปแบบที่ปลอดภัย

ลูป

for (0..10) |i| {  
    print("{d}\n", .{i});  
} else @panic("loop safety counter exceeded");  
  • ทั้ง for และ while รองรับส่วน else
  • จัดวาง iterator และชื่อตัวแปรองค์ประกอบได้อย่างเข้าใจง่าย

เนมสเปซและการ resolve ชื่อ

const std = @import("std");  
const ArrayList = std.ArrayList;  
  • ไม่อนุญาตให้ shadow ตัวแปร
  • ไม่มีเนมสเปซและไม่มี glob import

ทุกอย่างคือ expression

const E = enum { a, b };  
const e: if (true) E else void = .a;  
โฆษณา
  • รวมไวยากรณ์ของชนิดข้อมูล·ค่า·แพตเทิร์นเข้าด้วยกัน
  • สามารถวาง conditional expression ในตำแหน่งของชนิดข้อมูลได้

generic

fn ArrayListType(comptime T: type) type {  
    return struct {  
        fn init() void {}  
    };  
}  
  
var xs: ArrayListType(u32) = .init();  
  • generic แสดงด้วยไวยากรณ์แบบการเรียกฟังก์ชัน (Type(T))
  • อาร์กิวเมนต์ชนิดข้อมูลต้องระบุอย่างชัดเจนเสมอ

ฟังก์ชันในตัว

const foo = @import("./foo.zig");  
const num = @as(i32, 92);  
  • เรียกใช้ความสามารถที่คอมไพเลอร์มีให้ด้วย prefix @
  • @import แสดงพาธไฟล์อย่างชัดเจน
  • อาร์กิวเมนต์ต้องเป็น string literal เท่านั้น

บทสรุป

  • ไวยากรณ์ของ Zig เป็นตัวอย่างของการที่ ตัวเลือกเล็กๆ หลายอย่าง รวมกันจนกลายเป็น ภาษาที่อ่านง่าย
  • เมื่อจำนวนฟีเจอร์ลดลง ไวยากรณ์ที่จำเป็นก็ลดลงด้วย และ โอกาสเกิดการชนกันระหว่างไวยากรณ์ก็ลดลง
  • ยืม แนวคิดที่ดีจากภาษาเดิม มาใช้ แต่เมื่อจำเป็นก็กล้านำไวยากรณ์ใหม่มาใช้

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

 
GN⁺ 2025-08-22
ความคิดเห็นจาก Hacker News
  • บทความนี้เจาะลึกถึง trade-off หลายอย่างที่เกิดขึ้นในการออกแบบไวยากรณ์ภาษา และทำให้รู้สึกประทับใจมากกับความมินิมอล ความสม่ำเสมอ และการโฟกัสเรื่องความอ่านง่ายแบบไม่ปรานีของไวยากรณ์ Zig สิ่งที่ชอบคือมันไม่ใช่ความงามเชิงนามธรรม แต่เป็น "บรทัลลิสม์" ที่ไม่มีอะไรให้ต้องแปลกใจสำหรับการใช้งานเชิงอุตสาหกรรม การออกแบบไวยากรณ์ที่สมดุลแบบนี้หาได้ยากจริง ๆ และคิดว่า Zig ทำออกมาได้ดีมาก

    • น่าเสียดายที่บทความไม่ได้พูดถึงการจัดการข้อผิดพลาดเลย วิธี try/catch ของ Zig ยอดเยี่ยมมาก เป็นแนวทางจัดการข้อผิดพลาดที่ชอบที่สุดในบรรดาหลายภาษา ถ้าได้แนะนำส่วนนี้ด้วยก็น่าจะดียิ่งขึ้น

    • เสน่ห์ที่แท้จริงของ Zig ไม่ใช่ "ความอ่านง่ายที่สวยงามในระดับผิวเผิน" แต่คือความงามที่สม่ำเสมอซึ่งได้มาจากการทำ abstraction อย่างเหมาะสม คล้ายอุปมาเรื่อง S-expression กับ M-expression คือวิธีที่ดีสำหรับกรณีทั่วไปมักจะดีกว่าในระยะยาว เมื่อเทียบกับการออกแบบพิเศษสำหรับกรณียกเว้นหลายแบบ ถ้าเพิ่มกรณียกเว้นสารพัดแบบ C++ สุดท้ายภาระก็จะตกอยู่ที่การต้องท่องจำกฎทั้งหมด ในการออกแบบภาษา ถ้ามัวแต่ไล่ตามความเรียบง่ายและความสม่ำเสมอแบบสุดโต่ง ก็อาจตกไปสู่ "Turing tarpit" ที่ผู้ใช้ต้องแบกรับความซับซ้อนเอง ดังนั้นแนวทางที่ให้กรณีพิเศษถูกแก้ได้อย่างเป็นธรรมชาติจากกฎทั่วไปจึงสำคัญ ตัวอย่างแนวคิดนี้เห็นได้ในคอมิก XKCD เรื่อง New Pet

    • ถ้ามีตัวอย่างที่รู้สึกประทับใจ อยากให้ช่วยแชร์หน่อย

  • สำหรับการระบุชนิดแบบ ชื่อ:ชนิด ของ Zig ที่คล้าย Rust นั้น ส่วนตัวยังชอบแบบดั้งเดิมที่เอาชนิดขึ้นก่อนมากกว่า เวลาย้อนกลับไปดูการประกาศตัวแปร สิ่งที่อยากรู้ที่สุดก็คือชนิดของตัวแปรนั้น ถ้าหาได้ไม่เร็วก็รู้สึกไม่สะดวก โดยเฉพาะใน Rust ที่มีอะไรอย่าง let mut ซ้ำ ๆ โดยไม่จำเป็นจนยิ่งดูยุ่งยาก ส่วนแบบ C, C++ ที่เอาชนิดไว้ก่อนก็ดีเหมือนกัน จริง ๆ แล้วคิดว่าอุดมคติคือใช้ type inference เท่าที่จำเป็นในจุดที่ต้องใช้เท่านั้น

    • คีย์เวิร์ด let ก็มีความจำเป็นในแง่ที่ช่วยให้ชัดเจนว่านี่คือประโยคประกาศ ไม่อย่างนั้นอาจเจอปัญหาการ parse ไวยากรณ์กำกวมแบบใน C++ ได้

    • ฉันเองก็มักจะมองหาชนิดของตัวแปรก่อนเสมอ เลยชอบรูปแบบที่ชนิดอยู่ข้างหน้า ในมุม parser การจัดการชื่อก่อนก็สะดวกกว่า และก็เข้าใจว่า TypeScript ใช้โครงสร้างนี้เพราะต้องเข้ากับ JavaScript ท้ายที่สุดคิดว่าสิ่งสำคัญคือ standard library ที่ใช้ง่าย มากกว่าจะยัดทุกสถานะให้เป็นชนิดแบบตัวอย่างการใช้ type system เกินขอบเขต การสื่อเจตนาให้ชัดเจนสำคัญกว่า

    • เวลากลับขึ้นไปดูชนิดของตัวแปรในโค้ด ถ้าชนิดอยู่ข้างหน้ากลับทำให้หาจุดประกาศของตัวแปรที่กำลังตามหาได้ยากกว่า เพราะชื่อชนิดอยู่ต้นบรรทัดและมีความยาวไม่แน่นอน ทำให้สายตาต้องกวาดซ้ายขวาซ้ำ ๆ เลยรู้สึกว่าไม่มีประสิทธิภาพ

    • ในหลายกรณี ถ้าเอาเมาส์ไปชี้ใน editor ก็เห็นข้อมูลชนิดได้ทันที ตำแหน่งของชนิดในโค้ดจึงอาจไม่สำคัญมากนัก ที่ Rust ดู verbose นั้นส่วนหนึ่งเป็นเหตุผลด้าน implementation เพื่อหลีกเลี่ยงความกำกวมในการ parse ถ้าใช้แบบ C, C++ ที่เอาชนิดไว้ก่อน ก็จะ grep หา declaration ของตัวแปรชื่อหนึ่ง ๆ ได้ยากกว่า และสไตล์ที่เอา return type ไว้ข้างหน้าแม้จะเกิดจากเรื่อง template แต่ในบางกรณีก็ช่วยให้อ่านและค้นหาโค้ดได้ง่ายขึ้น

    • ส่วนตัวชอบรูปแบบระบุชนิดแบบ Pascal มากกว่า แม้ตอนใช้ type inference ก็ไม่ต้องมีลูกเล่นอ้อม ๆ แบบ auto และในมุมการ parse ก็กำกวมน้อยกว่า ใน MyClass x มันไม่ชัดทันทีว่า MyClass เป็นชนิดหรือชื่อตัวแปร รูปแบบนี้จึงช่วยลดความกำกวมได้

  • สำหรับไวยากรณ์ raw/multiline string ของ Zig วิธีที่ต้องใช้ \\ หลายครั้งดูสับสนและสุดโต่งเกินไปมาก

    • ถ้าเคยจัด format multiline string ใน Python, C++, Rust มาก่อน ก็น่าจะเข้าใจความลำบากนี้ดี เพราะต้องคอยกังวลว่า indentation จะถูกรวมเข้าไปในเนื้อหาสตริงด้วย ส่วนภาษาที่มีโหมดตัด indentation แบบ YAML กลับยิ่งทำให้สับสนไปอีก วิธีของ Zig ชัดเจนมากในเรื่อง indentation

    • ตอนแรกก็รู้สึกว่าไวยากรณ์นี้ใช้งานลำบากมาก แต่พอใช้ Zig ไปเรื่อย ๆ ก็เริ่มชินและกลับมองเห็นข้อดีของมัน Zig เป็นภาษาที่ตอนแรกอาจรู้สึกไม่ชอบบางอย่าง แต่พอใช้จริงแล้วจะค่อย ๆ เห็นว่ามันดีตรงไหน

    • อันที่จริงไม่ใช่ว่าไวยากรณ์มันบ้า แต่เป็นเพราะปัญหานี้มันบ้าต่างหาก—ปัญหาการใส่ multiline string ซ้อนอยู่ใน multiline string อีกชั้นอย่างปลอดภัย ใน Zig ไม่ต้องมี escape เพิ่มและไม่ต้องกังวลเรื่อง indentation ซึ่งเป็นข้อดีมาก

    • สำหรับฉัน trimIndent ของ Kotlin, text block ของ Go หรือ Java และโดยเฉพาะ raw string แบบ backtick ของ Go ลื่นไหลกว่ามาก ใน Zig กลับต้องเลี่ยงไปใช้ @embedFile เพราะ \\

    • แม้ในเชิงภาพจะไม่ค่อยชอบ \\ เท่าไร แต่ก็คิดว่านี่เป็นวิธีที่แก้ปัญหา multiline literal และเรื่อง indentation ได้อย่างสะอาดตา ไม่ค่อยรู้จักภาษาอื่นที่แก้ปัญหานี้ได้โดยไม่ต้องพึ่งฟังก์ชัน

  • ไวยากรณ์ของ Zig ให้ความรู้สึกกระจัดกระจาย มีทั้งรูปแบบอย่าง @TypeOf ที่ขึ้นต้นด้วย @ หรือไวยากรณ์ initializer แบบ .{.x} ที่ดูแปลกตา อาจเป็นเพราะยังไม่ชำนาญในการใช้ Zig แต่โดยรวมแล้วให้ความรู้สึกว่าอ่านโค้ดยาก

    • ฉันชอบไวยากรณ์ของ Odin มากกว่า เพราะมินิมอลและขัดเกลามากกว่า ส่วน Zig ให้ความรู้สึกค่อนข้างกระจัดกระจาย

    • . ใน Zig ทำหน้าที่เป็น placeholder สำหรับชนิดที่อนุมานได้ เช่น สามารถ initialize object แบบนี้

      const p = Point{ .x = 123, .y = 234 };
      

      หรือถ้าอยากระบุให้ชัดว่าต้องการ type inference ก็เขียนได้ว่า

      const p: Point = .{ .x = 123, .y = 234 };
      

      ใน argument ของฟังก์ชันก็ละชนิดได้เช่นกัน จึงกระชับกว่า ใน Rust จำเป็นต้องเขียนชนิดอย่างชัดเจนในสถานการณ์แบบนี้

      takePoint(Point{ x: 123, y: 234 });
      

      แม้แต่การ initialize struct แบบซ้อนกัน วิธีอนุมานของ Zig ก็มีประโยชน์กว่ามาก ถ้าต้องเขียนชนิดชัดเจนทุกที่แบบ Rust โค้ดจะรกเร็วมาก ถึงอย่างนั้นก็ยังคิดว่าถ้าตัด dot ที่อยู่ข้างหน้าออกได้จะสะดวกกว่า แต่ดูเหมือนคงไว้เพื่อทำให้ parser ง่ายขึ้น รูปแบบ x: 123 หรือ .x = 123 นั้นยืมมาจาก JS และ C99 ตามลำดับ ส่วนตัวใช้ทั้งสองแบบบ่อยเลยไม่รู้สึกแปลก

  • ชอบ raw string literal ของ C# 11 มากกว่าเยอะ มันใช้ indentation ของบรรทัดแรกเป็นเกณฑ์แล้วจัดบรรทัดที่เหลือให้ตรงกันอัตโนมัติ และยังสามารถใช้วงเล็บปีกกาเป็นตัวอักษรธรรมดาได้ด้วย ถ้ามี $ หลายตัว วงเล็บปีกกาจะถูกมองเป็นค่าตัวอักษรทั้งหมด

    string json = $"""
       {title}
    
         Welcome to {sitename}.
    
       """;
    string json = $$"""
       {{title}}
    
         Welcome to {{sitename}}, which uses the {sitename} syntax.
    
       """;
    
    • (ในฐานะผู้เขียนฟีเจอร์ raw string literal ของ C#) จริง ๆ แล้วใช้ indentation ของบรรทัด """ ปิดท้ายเป็นเกณฑ์ และยังอนุญาตให้ย่อหน้าบรรทัดแรกได้ด้วย ดีใจที่มีคนชอบฟีเจอร์นี้ และกล้าพูดเลยว่ามันเป็นฟีเจอร์ที่ดี
  • ไวยากรณ์ของ Zig ก็โอเค แต่เมื่อเทียบกับ Go ที่เขียนได้สะอาดพอโดยไม่ต้องมี semicolon หรือ : ก็ยังไม่ถึงขั้นเรียกว่า "น่ารัก" ถ้าจะเทียบก็บอกได้ว่าเหนือกว่า Rust มากจริง แต่ Go เองก็ยอดเยี่ยมพอแล้ว

    • ในทางกลับกัน ไวยากรณ์ที่มินิมอลเกินไปแบบ Go บางครั้งกลับอ่านและตีความยากกว่า เวลาที่ใช้ในการอ่านโค้ดมักมากกว่าเวลาที่ใช้เขียน ความกระชับเกินความจำเป็นจึงอาจทำให้ผิดพลาดและ debug ยากขึ้น ตัวอย่างชัด ๆ คือไวยากรณ์ที่ย่อมากเกินไปอย่าง CoffeeScript หรือ J

    • ฉันไม่คิดว่าการตัดองค์ประกอบทางไวยากรณ์ออกจะทำให้ไวยากรณ์ดีขึ้นเสมอไป ถ้าเป็นอย่างนั้นจริง ทุกคนก็คงเขียนกันแบบ Lisp และข้อความก็คงเขียนแบบ scriptio continua (รูปแบบการเขียนโบราณที่ไม่มีเว้นวรรค) ไปแล้ว ดู วิกิพีเดียของ scriptio continua

  • โดยรวมพอใจกับ Zig แต่ก็ยังเสียดายปัญหาต่อไปนี้

    • ระบุค่าที่ block จะคืนกลับได้ยาก ถ้าเหมือน Rust ที่มอง expression สุดท้ายเป็นค่าที่คืนอัตโนมัติก็น่าจะดี แต่ใน Zig ต้องใช้ label เป็นต้น ทำให้ยุ่งยาก
    • ทำ optional type chaining ไม่ได้ (เช่น a?.b?.c) ถ้ามีการรองรับ monadic type ก็น่าจะ chain แบบทั่วไปได้มากกว่านี้ แต่ตอนนี้ยังไม่พอ
    • ไม่มี lambda function ทั้งที่ตอนนี้ก็ใช้ function block ในที่อย่าง loop หรือ catch block ได้อยู่แล้ว ถ้ามี lambda เพิ่มก็น่าจะยืดหยุ่นขึ้นมาก
  • เรื่องการใช้ void เป็นชื่อชนิดนั้น จริง ๆ ใน type theory คำว่า void ไม่ได้ทำหน้าที่เป็น unit แต่หมายถึงชนิดแบบ "uninhabited" ที่ไม่มีค่าใดอยู่ได้ ตามธรรมเนียมแล้ว () หรือ unit คือชนิดที่มีสมาชิกหนึ่งค่า ส่วน void เป็น return type ของฟังก์ชันอย่าง abort

    • ใน C, C++ คำว่า void ก็ใช้งานกันมาได้ดีพอสมควร และโปรแกรมเมอร์สายระบบจำนวนมากก็คุ้นเคยกับมัน ผมคิดว่าการถกเถียงเรื่องคำศัพท์ใน type theory ไม่ค่อยมีความหมายกับการใช้งานจริง คนจำนวนมากที่มาใช้ Zig มีพื้นฐานจาก C, C++ ดังนั้นใช้ void ก็โอเคพอแล้ว

    • abort เป็นชนิดสำหรับสถานะ "ไปต่อไม่ได้" แบบเดียวกับชนิด ! ของ Rust ส่วน void จะใกล้กับ unit หรือ () มากกว่า คือเป็นชนิดที่ไม่มีค่าอย่างที่ใช้งานได้จริง เกร็ดสนุกคือใน TypeScript ถ้าใช้ void กับ generic constraint จะทำให้พารามิเตอร์นั้นเป็น optional ได้

    • ชนิด void มีประวัติยาวนานมาก ย้อนกลับไปได้ถึง ALGOL 68 ซึ่งนิยามชนิด VOID ว่าเป็นชนิดที่มีสมาชิกเพียงค่าเดียว (EMPTY)

  • ค่อนข้างแปลกใจที่ "Zig ไม่มี lambda" ใน C++ ใช้ lambda แทบทุกที่ แล้วอย่างตอน sort array จะนิยาม comparator กันยังไงนะ

    • โดยทั่วไปคือต้องประกาศฟังก์ชันแยกต่างหาก ซึ่งมองว่า Zig ไม่ค่อยสะดวกในจุดนี้

    • สามารถอ้างอิง anonymous struct และฟังก์ชันข้างในมันแบบ inline ได้ จริง ๆ แล้วฟีเจอร์ capture ที่นิยมใช้ใน lambda ไม่มีใน Zig แต่สามารถแทนด้วยการส่ง context parameter (มักเป็น struct) เข้าไปได้

    • โดยพื้นฐานก็เหมือน C คือประกาศฟังก์ชัน comparator แยกไว้ แล้วส่ง pointer ของมันเข้าไปให้ฟังก์ชัน sort

  • หลายคนชอบบอกว่า "ไวยากรณ์ไม่สำคัญ" แต่เอาเข้าจริงกลับกลายเป็นว่า "ไวยากรณ์ไม่สำคัญ เพราะงั้นมาใช้แบบที่ฉันชอบกันเถอะ" ตัวฉันเองก็คุ้นกับไวยากรณ์สาย C ที่แตกแขนงมาแบบ Rust/Zig/Go และยังรู้สึกว่าแนว Haskell/OCaml ที่ใช้ช่องว่างแยกการเรียกฟังก์ชันนั้นแปลกอยู่ เลยมองว่ามันเป็นอุปสรรคต่อการแพร่หลาย สิ่งที่ภาษาอื่นน่าจะเรียนรู้จากความสำเร็จของ Rust คือการผสม "ผักโขม" ของ functional programming เข้าไปใน "บราวนี" ของภาษาระบบได้อย่างลงตัว

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

    • ถ้าอยากได้ภาษาฟังก์ชันที่มีไวยากรณ์แบบสาย C ขอแนะนำ Gleam: gleam.run โค้ดก็สวยมาก

      fn spawn_greeter(i: Int) {
       process.spawn(fn() {
        let n = int.to_string(i)
        io.println("Hello from "  n)
       })
      }
      

      Reason ก็แนะนำได้เหมือนกัน เป็นภาษา based on OCaml แต่มีไวยากรณ์แบบสาย C: reasonml.github.io