1 คะแนน โดย GN⁺ 4 일 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • YAML 1.2 เป็นรูปแบบการทำซีเรียลไลซ์แบบอิงการเยื้องสำหรับไฟล์คอนฟิกแบบซ้อนที่มนุษย์เขียนเอง และควรถูกแยกออกจากคำวิจารณ์เรื่องความไม่เสถียรที่เกิดจากประสบการณ์กับ PyYAML รุ่นเก่า
  • รูปแบบไฟล์คอนฟิกมีพัฒนาการมาเพื่อแก้ข้อจำกัดของยุคก่อนหน้า เช่น ความแบนของ INI, ความเยิ่นเย้อของ XML, และการไม่มีคอมเมนต์หรือสตริงหลายบรรทัดใน JSON
  • TOML ชัดเจนสำหรับโครงสร้างตื้น ๆ อย่าง pyproject.toml และ Cargo.toml แต่เมื่อเป็น โครงสร้างซ้อนลึก ภาระในการอ่านและแก้ไขจะเพิ่มขึ้นเพราะต้องเขียน path ซ้ำและใช้ array of tables
  • YAML 1.2 Core Schema จะตีความ yes, no, on, off, y, n เป็นสตริง และตัดเลขฐาน 60 กับ timestamp ในฐานะชนิดหลักออกไป จึงลดปัญหา การอนุมานชนิดโดยปริยาย แบบในอดีต
  • py-yaml12 คือพาร์เซอร์และฟอร์แมตเตอร์ YAML 1.2 สำหรับ Python ที่พัฒนาโดยใช้ Rust ให้ค่าเริ่มต้นที่ปลอดภัย, ผ่าน yaml-test-suite 100%, และมีประสิทธิภาพแข่งขันได้กับส่วนขยาย C ของ PyYAML

ประเด็นปัญหา

  • ในช่วงไม่กี่ปีที่ผ่านมา กระแสเรื่องรูปแบบไฟล์คอนฟิกได้ขยับไปทาง “YAML แย่ TOML ดี” แต่การประเมิน YAML ต้องมองทั้งประวัติ การเปลี่ยนแปลงของสเปก และสภาพของเครื่องมือในปี 2026 ไปพร้อมกัน
  • คำวิจารณ์ YAML นั้นสมเหตุสมผลมาเป็นเวลานาน และแม้แต่ผู้ใช้ที่ระมัดระวังก็ยังเจอพฤติกรรมไม่คาดคิดได้ แต่สเปกได้เปลี่ยนไปแล้ว และระบบนิเวศของเครื่องมือก็กำลังตามทัน
  • คำวิจารณ์ YAML ในปัจจุบันจะเข้าใจได้ก็ต่อเมื่อมองสายวิวัฒนาการของรูปแบบไฟล์คอนฟิกด้วย ซึ่งเป็นข้อถกเถียงที่รูปแบบรุ่นถัดไปคอยแก้ความสุดโต่งของรุ่นก่อนหน้าซ้ำ ๆ มาโดยตลอด

ประวัติย่อของรูปแบบไฟล์คอนฟิก

  • ไฟล์ INI ปรากฏขึ้นในช่วงต้นทศวรรษ 1980 พร้อมกับ MS-DOS และ Windows ยุคแรก เป็นรูปแบบที่เรียบง่าย อ่านง่าย แก้ไขโดยมนุษย์ได้ มีคู่คีย์-ค่า, section ในวงเล็บเหลี่ยม, และคอมเมนต์ด้วยอัฒภาค
  • INI เพียงพอกับความต้องการในยุคนั้น เช่น คอนฟิกไดรเวอร์อุปกรณ์, การระบุ path ของฟอนต์, และการตั้งค่าแอปพลิเคชัน แต่มีข้อจำกัดเชิงโครงสร้างคือซ้อนได้ไม่เกินหนึ่งระดับ และไม่มีสเปกทางการ ทำให้พาร์เซอร์แต่ละตัวมีภาษาถิ่นของตัวเอง
  • XML ถูกนำไปใช้อย่างแพร่หลายในโลกซอฟต์แวร์องค์กรช่วงปลายทศวรรษ 1990 โดยรองรับลำดับชั้นตามอำเภอใจ, schema, namespace, การแปลงข้อมูล, และโครงสร้างแบบ self-describing แต่เมื่อใช้จริงกับไฟล์คอนฟิกกลับยาวเยิ่นเย้อมากจนดูแลด้วยมือได้ยาก
  • JSON เกิดขึ้นในฐานะปฏิกิริยาแบบเบากว่า XML และด้วยความเรียบง่ายของ JavaScript object literal จึงเข้ามาแทน XML ใน Web API ช่วงปลายยุค 2000 ถึงต้นยุค 2010 แต่เมื่อใช้เป็นไฟล์คอนฟิกกลับมีข้อจำกัดเรื่องไม่มีคอมเมนต์, ไม่มีสตริงหลายบรรทัด, และไม่อนุญาต comma ต่อท้าย
  • YAML เกิดในปี 2001 และ TOML ในปี 2013 เพื่อเติมช่องว่างที่ JSON ทิ้งไว้ โดย YAML ใช้ไวยากรณ์อิงการเยื้อง รองรับการซ้อนได้อิสระ, หลายเอกสาร, การอ้างอิง, และชนิดที่ผู้ใช้กำหนดเอง ส่วน TOML มุ่งเป็น “INI ที่ถูกทำให้เป็นมาตรฐาน” พร้อมชนิดแบบ explicit และสเปกทางการ
  • รูปแบบใหม่ในแต่ละยุคต่างเริ่มจากการแก้ความสุดโต่งของรูปแบบก่อนหน้า และปัญหาของ YAML จำนวนมากในปัจจุบันก็เป็นผลจากสเปกบางเวอร์ชันและพาร์เซอร์ที่ผูกติดกับเวอร์ชันนั้น มากกว่าจะมาจากการออกแบบของรูปแบบเอง

ปัญหาที่เคยเป็นเหตุผลให้ต่อต้าน YAML

  • คำวิจารณ์ YAML ไม่ได้เป็นปัญหาที่ถูกปั้นขึ้น แต่มีฐานมาจากประสบการณ์จริงที่โปรแกรมเมอร์พบเจอมาหลายปี
  • ปัญหา Norway ที่โด่งดังที่สุดคือใน YAML 1.1 ค่า scalar แบบไม่ใส่เครื่องหมายคำพูด NO จะถูกตีความเป็นบูลีน false ทำให้ no ในรายการรหัสประเทศกลายเป็นค่าเท็จ
    countries:
      - dk
      - fi
      - is
      - no
      - se
    
    ["dk", "fi", "is", false, "se"]
    
  • กฎเดียวกันนี้ยังใช้กับ yes, on, off, y, n และรูปแบบตัวพิมพ์ใหญ่เล็กอีกหลายแบบ ขณะที่ port mapping อย่าง 22:22 อาจถูกอ่านเป็นจำนวนเต็มฐาน 60, หมายเลขเวอร์ชันอย่าง 10.23 ถูกอ่านเป็นเลขทศนิยมแทนสตริง, และค่าที่ดูเหมือนวันที่ถูกตีความเป็น timestamp
  • ในโค้ดด้าน data science และ machine learning ชื่อตัวแปรธรรมชาติอย่าง n และ y ก็อาจถูกตีความเป็นคีย์ False และ True แทนที่จะเป็นคีย์สตริง ภายใต้กฎบูลีนโดยปริยายของ YAML 1.1
    {"variables": {"x": "features", False: "sample_size", True: "target"}}
    
  • การอนุมานชนิดโดยปริยายแบบก้าวร้าวของ YAML 1.1 ตั้งใจให้การอ่านค่าอย่าง true แบบไม่ใส่เครื่องหมายคำพูดดูสะดวกขึ้น แต่ในไฟล์คอนฟิกจริงกลับทำให้พฤติกรรมคาดเดาได้ยากขึ้น
  • ไฟล์คอนฟิกมักไม่ได้ถูกแก้ไขบ่อย และหลายครั้งก็ถูกแก้โดยคนที่ไม่ใช่ผู้เขียนเดิม การ parse ผิดแบบเงียบ ๆ จึงอาจแพร่กระจายไปทั่วทั้งระบบเป็นเวลาหลายเดือน ทำให้พื้นที่นี้เป็นจุดที่รับพฤติกรรมไม่คาดคิดได้ยากที่สุด
  • ความซับซ้อนของสเปก YAML ทั้งฉบับก็เป็นภาระเช่นกัน โดยสเปก YAML 1.2.2 เป็นเอกสารขนาดใหญ่ มี 10 บท และโครงสร้าง section ที่มีเลขลำดับถึง 4 ระดับ
  • รูปแบบการเขียนสตริงหลายบรรทัดมีหลายวิธี และ anchor กับ alias แม้จะสร้างระบบอ้างอิงที่ทรงพลัง แต่ก็มักหนักเกินความจำเป็นสำหรับงานคอนฟิกส่วนใหญ่
  • ปัญหาความปลอดภัยจากการ deserialize object ด้วยแท็ก โดยเฉพาะช่องโหว่ของ yaml.load() ใน Python กลายเป็นช่องทางโจมตีที่เป็นที่รู้จัก และคำวิจารณ์เหล่านี้ก็ใช้ได้จริงกับ YAML 1.1 และเครื่องมือรอบตัวมันในยุคนั้น

สิ่งที่ TOML ทำได้ดี

  • TOML เป็นรูปแบบที่สะอาด อ่านง่าย และไม่กำกวม สำหรับโครงสร้างคอนฟิกที่แบนหรือซ้อนไม่ลึก
  • ไวยากรณ์ TOML คุ้นตาสำหรับคนที่เคยเห็นไฟล์ INI แต่เพิ่มชนิดแบบ explicit, สเปกทางการ, และการรองรับ nested table ผ่านคีย์ที่คั่นด้วยจุด
  • pyproject.toml และ Cargo.toml มักมี section ที่นิยามชัดเจน ลึกเพียงหนึ่งหรือสองระดับ จึงเป็นกรณีที่ TOML เหมาะมาก
  • ใน TOML สตริงจะถูกครอบด้วยเครื่องหมายคำพูดเสมอ จึงไม่กำกวมว่า no เป็นบูลีนหรือคำธรรมดา ขณะที่ integer, float, และวันที่ก็เป็นชนิดชั้นหนึ่ง และคอมเมนต์ก็ทำงานตามที่คาดหวัง
  • การยอมรับ PEP 518 ใน ecosystem ของ Python packaging และการใช้ Cargo ในชุมชน Rust เป็นหลักฐานว่า TOML เป็นตัวเลือกที่เหมาะกับขอบเขตปัญหาประเภทนี้
  • สเปก TOML สั้นพอที่โปรแกรมเมอร์ฝีมือดีจะเขียนพาร์เซอร์ที่เข้ากันได้ในช่วงสุดสัปดาห์ได้ จึงส่งผลให้ระบบนิเวศของพาร์เซอร์มีขนาดใหญ่ ทดสอบมาดี และสม่ำเสมอ
  • TOML ไม่มีปัญหาแตกแยกระหว่างเวอร์ชันแบบที่เกิดระหว่าง YAML 1.1 กับ 1.2 โดย TOML 1.0 ก็คือ TOML 1.0 เหมือนกันทุกที่

จุดที่ TOML เริ่มหนักมือ

  • เมื่อไฟล์คอนฟิกต้องแสดงความลึก โครงสร้างซ้อนของ TOML จะต้องพึ่ง section header แบบคั่นด้วยจุด ([servers.alpha]) หรือ array of tables ([[products]]) และยิ่งซ้อนมากก็ยิ่งอ่านยาก
  • Martin Vejnár แห่ง PyTOML เคยปฏิเสธไม่ให้ไลบรารีของตนกลายเป็น dependency ของ pip โดยให้เหตุผลจากประสบการณ์ว่าเมื่อ schema ของคอนฟิกซับซ้อนขึ้น ไวยากรณ์ TOML จะดูไม่สวยและอ่านยาก
  • ใน YAML การเยื้องช่วยบอกลำดับชั้นได้ในทันที แต่ใน TOML ต้องเขียน path เต็มซ้ำใน section header ทุกครั้ง
    services:
      web:
        image: nginx:latest
        environment:
          DB_HOST: postgres
          DB_PORT: 5432
        resources:
          limits:
            memory: 512M
            cpu: "0.5"
    
    [services.web]
    image = "nginx:latest"
    
    [services.web.environment]
    DB_HOST = "postgres"
    DB_PORT = 5432
    
    [services.web.resources.limits]
    memory = "512M"
    cpu = "0.5"
    
  • ผู้อ่าน TOML ต้องประกอบโครงสร้างแบบต้นไม้ขึ้นใหม่ในหัวจากลำดับของชื่อแบบมี qualifier ที่วางแบน ๆ และจากการวัดในเอกสารของ StrictYAML ไฟล์ TOML ใช้อักขระมากกว่าราว 50% สำหรับข้อมูลชุดเดียวกัน เพราะต้องเขียน prefix ของ path ซ้ำ
  • Python ได้แสดงให้เห็นมานานแล้วว่าการใช้การเยื้องเป็นโครงสร้างนั้นไม่ใช่จุดอ่อน แต่เป็นจุดแข็ง เพราะช่วยกำจัดบั๊กประเภทที่โครงสร้างทางสายตาไม่ตรงกับโครงสร้างทางไวยากรณ์
  • YAML ได้รับข้อดีของโครงสร้างแบบเยื้องนี้โดยตรง ขณะที่ TOML ไม่บังคับการเยื้อง ทำให้ความสัมพันธ์ระหว่างคีย์กับ table ที่ครอบอยู่มีอยู่แค่ใน section header ไม่ได้อยู่ในการจัดวางจริงของไฟล์
  • สำหรับไฟล์คอนฟิกที่ซ้อนลึก TOML เป็นรูปแบบที่สแกนอ่านยาก และแก้ไขได้ยากโดยไม่มั่นใจ

การเปลี่ยนแปลงใน YAML 1.2

  • สเปก YAML 1.2 เผยแพร่ในปี 2009 และฉบับแก้ไขเพื่อความชัดเจน 1.2.2 เสร็จสมบูรณ์ในเดือนตุลาคม 2021
  • ใน YAML 1.2 Core Schema มีเพียง true, false และ True, False, TRUE, FALSE เท่านั้นที่ถูกมองเป็นบูลีน ส่วน yes, no, on, off, y, n เป็นสตริงธรรมดา
  • literal ตัวเลขฐาน 60 ที่ทำให้เกิดปัญหา 22:22 ถูกถอดออกไปทั้งหมด และ timestamp ก็ไม่ใช่ชนิดหลักอีกต่อไป ดังนั้นใน Core Schema ค่า 2026-05-05 ที่ไม่ใส่เครื่องหมายคำพูดจะเป็นสตริง ไม่ใช่วันที่ที่ตรวจจับอัตโนมัติ
  • JSON กลายเป็น subset ที่เข้มงวดและถูกต้องของ YAML 1.2 ดังนั้นเอกสาร JSON ที่ถูกต้องทุกฉบับจะ parse เป็น YAML ได้เหมือนกัน
  • กฎการตีความแท็กเข้มงวดและชัดเจนขึ้น และตัวสเปกเองก็ถูกเขียนอย่างชัดเจนขึ้นพร้อมดูแลแบบเปิดบน GitHub
  • YAML ที่ผู้คนวิจารณ์กันมาโดยมากคือ YAML 1.1 แต่สเปกที่ควบคุมภาษาในปัจจุบันคือ YAML 1.2 ที่ปลอดภัยและคาดเดาได้มากกว่า
  • ปัญหาคือประสบการณ์ YAML ของผู้ใช้ไม่ได้มาจากสเปกโดยตรง แต่มาจากพาร์เซอร์ และสำหรับผู้ใช้ Python ส่วนใหญ่ พาร์เซอร์นั้นก็คือ PyYAML ที่ยังใช้ YAML 1.1 และแทบไม่ได้เปลี่ยน semantic หลักมาตั้งแต่ปี 2006

ภูมิทัศน์ของพาร์เซอร์ YAML ใน Python

  • PyYAML เป็นไลบรารี YAML มาตรฐานโดยพฤตินัยของ Python โดยห่อ C library อย่าง LibYAML เพื่อประสิทธิภาพ และมีเส้นทางสำรองแบบ pure Python ด้วย
  • PyYAML มียอดดาวน์โหลดหลายล้านครั้งต่อสัปดาห์และเป็น dependency ของแพ็กเกจจำนวนมาก แต่ยังใช้ YAML 1.1 ซึ่งเป็นรากของประสบการณ์แบบ “YAML parse รหัสประเทศเป็นบูลีน” ใน ecosystem ของ Python
  • ใน repository ของ PyYAML มี open issue มากกว่า 200 รายการ และ open pull request มากกว่า 100 รายการ โครงการยังมีการดูแล แต่เคลื่อนตัวช้า และการเปลี่ยน major version ไปสู่ semantic ของ YAML 1.2 ก็ยังไม่เกิดขึ้นจริง
  • ruamel.yaml รองรับ YAML 1.2 และมีความสามารถแก้ไขแบบ round-trip ที่เก็บคอมเมนต์, flow style, และลำดับคีย์ไว้ได้ จึงทรงพลังกว่า PyYAML มากสำหรับงานที่ต้องเก็บคอมเมนต์หรือแก้ไขแบบรับรู้รูปแบบเดิม
  • ruamel.yaml ทำงานช้ากว่าเส้นทางเร็วแบบ C ของ PyYAML อย่างมาก เพราะโหมด round-trip เริ่มต้นใช้ implementation แบบ pure Python เป็นหลัก อีกทั้งยังมีประวัติด้าน packaging ที่ทำให้ pipeline การแจกจ่ายสับสนจากปัญหา namespace package และสาย dependency
  • StrictYAML เป็นการใช้ subset ของ YAML ที่ตั้งใจตัดการอนุมานชนิดโดยปริยายทั้งหมด, แท็ก, anchor, และ flow style ออกไป
  • StrictYAML ในเชิงปรัชญาใกล้กับ TOML มากกว่า YAML เต็มรูปแบบ เป็นรูปแบบที่ปลอดภัยและเรียบง่ายซึ่งใช้ไวยากรณ์การเยื้องแบบ YAML แต่รองรับเฉพาะ Python, ไม่มี implementation ในภาษาอื่น, และไม่ได้มุ่งปฏิบัติตามสเปกเต็มรูปแบบ
  • ในภูมิทัศน์นี้ ยังขาดไลบรารีที่ทั้งเร็ว, รองรับ YAML 1.2 ได้ครบ, และแทนที่ interface พื้นฐานของ PyYAML ได้ง่าย

แนะนำ py-yaml12

  • py-yaml12 คือพาร์เซอร์และฟอร์แมตเตอร์ YAML 1.2 สำหรับ Python ที่พัฒนาใน Rust เพื่อความเร็วและความถูกต้อง
  • py-yaml12 ถูกสร้างบน saphyr crate ซึ่งเป็นไลบรารี YAML ของ Rust และให้ API ขนาดเล็กที่โฟกัสชัดเจน ได้แก่ parse_yaml(), read_yaml() สำหรับการโหลด และ format_yaml(), write_yaml() สำหรับการทำซีเรียลไลซ์
  • ความเรียบง่าย

    • ออกแบบให้กรณีใช้งานส่วนใหญ่ทำงานกับชนิด built-in พื้นฐานของ Python อย่าง dict, list, int, float, str, None ได้ตั้งแต่ต้นจนจบ
    • ในเส้นทางทั่วไปไม่มี document class พิเศษหรือ node type ที่ผู้ใช้ต้องกำหนดเอง และเพราะ YAML 1.2 เป็น superset ของ JSON ดังนั้น JSON ที่ถูกต้องทุกฉบับจึง parse ได้เหมือนกัน
    • py-yaml12 ผ่านการทดสอบความเข้ากันได้ yaml-test-suite ที่ชุมชนช่วยกันดูแลสำหรับ edge case และการทดสอบความสอดคล้อง ได้ครบ 100%
    • ค่า no ในรายการ regions จะกลายเป็น False แบบเงียบ ๆ ในพฤติกรรม YAML 1.1 ของ PyYAML แต่ในพฤติกรรม YAML 1.2 ของ py-yaml12 จะคงไว้เป็นสตริง "no" ตามที่สเปกกำหนด
    • API สำหรับไฟล์ก็ตรงไปตรงมา โดยเมื่อเขียน Python dictionary ลงดิสก์แล้วอ่านกลับ จะได้ออบเจ็กต์เดิมกลับมาแบบ round-trip โดยไม่สูญเสียข้อมูล
    • สำหรับฟีเจอร์ YAML ขั้นสูงอย่างค่าที่ติดแท็ก py-yaml12 มี wrapper type ชื่อ Yaml ให้ใช้ แต่สำหรับงานคอนฟิกทั่วไปถือเป็นทางเลือกเสริม
  • ความปลอดภัย

    • ค่าเริ่มต้นของ py-yaml12 ไม่ได้ดีแค่ด้านความสะดวกใช้งาน แต่ยังปลอดภัยกว่า เพราะแนวทางตรงข้ามของ PyYAML คือปฏิบัติต่อแท็กเสมือนคำสั่ง จนการแค่อ่านไฟล์ YAML ก็อาจนำไปสู่การรันโค้ด Python ตามอำเภอใจได้
    • หากอ่านไฟล์ YAML ที่สร้าง alias ให้กับ namespace ของแท็ก Python object-apply ของ PyYAML ด้วย yaml.load(f, Loader=yaml.Loader) โค้ด Python จะถูก execute ก่อนที่ฟังก์ชันจะคืนค่าเป็น dictionary ธรรมดา
    • ในตัวอย่าง ผลลัพธ์การ parse อาจดูเป็น {'debug': False, 'retries': 3} แต่ก่อนหน้านั้นตัวแปรสภาพแวดล้อม YAML_PAYLOAD_RAN ถูกตั้งเป็น '1' ไปแล้ว ทำให้ดูจากผลลัพธ์อย่างเดียวจะไม่รู้เลยว่ามีการ execute เกิดขึ้น
    • py-yaml12 จะไม่ execute แท็กที่ผู้ใช้ไม่ได้เลือกอย่างชัดเจน แต่จะเก็บไว้เป็นข้อมูล และแท็กที่ยังไม่ได้ประมวลผลจะถูกคืนค่าเป็น wrapper Yaml ที่เก็บทั้งค่าและแท็กไว้
  • ความเร็ว

    • เบนช์มาร์กของ py-yaml12 เปรียบเทียบประสิทธิภาพการอ่านและเขียนกับเส้นทาง pure Python ปกติของ PyYAML, CSafeLoader และ CSafeDumper ที่ใช้ LibYAML, และ ruamel.yaml ครอบคลุมตั้งแต่ไฟล์ระดับกิโลไบต์ถึงเมกะไบต์
    • เนื่องจาก logic หลักของการ parse และ format เขียนด้วย Rust ที่คอมไพล์แล้ว ไม่ใช่ Python แบบแปลความ py-yaml12 จึงรักษาการปฏิบัติตาม YAML 1.2 ได้ครบถ้วน พร้อมประสิทธิภาพที่แข่งขันได้กับส่วนขยาย C ของ PyYAML
    • ปัจจุบันยังมีตัวเลือกในโลกไลบรารี Python ไม่มากนักที่ให้ทั้งการรองรับ YAML 1.2 แบบครบถ้วนและประสิทธิภาพระดับส่วนขยาย C ของ PyYAML พร้อมกัน

บทสรุป

  • ข้อถกเถียง YAML กับ TOML ในแบบที่คุ้นเคยกัน มักใกล้เคียงกับการถกเถียงกับรูปแบบที่ในเวอร์ชันมีปัญหานั้นไม่ได้มีอยู่แล้ว โดยคำวิจารณ์เคยเป็นเรื่องจริง แต่มีลักษณะเชิงประวัติศาสตร์
  • คำวิจารณ์ YAML มักชี้ไปที่ YAML 1.1 ที่ผู้ใช้เจอผ่าน PyYAML มากกว่าจะเป็นสเปกหรือ YAML 1.2 ที่ถูกนำไปใช้ได้ถูกต้อง
  • TOML ยังเป็นตัวเลือกที่ดีสำหรับคอนฟิกที่ตื้นและแบน และ pyproject.toml ก็เหมาะกับบทบาทนั้น แต่คำกล่าวว่า YAML ไม่ปลอดภัยหรือคาดเดาไม่ได้โดยเนื้อแท้ ย่อมใช้ไม่ได้เมื่ออยู่ต่อหน้าพาร์เซอร์ YAML 1.2 ที่เข้ากันได้ตามสเปก
  • คำถามสำคัญไม่ใช่ว่ารูปแบบไหนดีที่สุดในเชิงนามธรรม แต่คือรูปแบบไหนและเครื่องมือใดเหมาะกับงานเฉพาะเรื่องนั้นมากกว่า
  • สำหรับไฟล์คอนฟิกแบบซับซ้อน ซ้อนลึก และเขียนโดยมนุษย์ YAML 1.2 พร้อมพาร์เซอร์สมัยใหม่เป็นคำตอบที่แข็งแกร่ง และรูปแบบก็พัฒนาด้วยการให้รุ่นถัดไปแก้จุดหยาบของรุ่นก่อนหน้า
  • ใน Python สามารถลองประสบการณ์ YAML แบบสมัยใหม่และตรงตามสเปกได้ด้วย pip install py-yaml12
  • ในสภาพแวดล้อม R, r-yaml12 ก็ให้ข้อดีเดียวกัน ทั้งการรองรับ YAML 1.2 แบบครบถ้วน, ประสิทธิภาพจาก Rust, และค่าเริ่มต้นที่ปลอดภัย เช่นเดียวกับแพ็กเกจฝั่ง Python

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

 
GN⁺ 4 일 전
ความคิดเห็นจาก Lobste.rs
  • คำอธิบายที่ว่า YAML เกิดขึ้นมาเพื่ออุดช่องโหว่ของ JSON ฟังดูแปลก เท่าที่จำได้ YAML มาจากคอมมูนิตี้ Perl และอาจจะอายุพอ ๆ กันหรือเก่ากว่า JSON ด้วยซ้ำ
    พอลองไปดูประวัติจริง ก็พบว่ามี บทความน่าสนใจ จาก Ingy dot Net หนึ่งในผู้สร้าง YAML ที่ระบุจุดกำเนิดไว้ช่วงเดือนพฤศจิกายน 1999 ถึงมกราคม 2001
    และเมื่อดู บทความประวัติของ JSON จะเห็นว่า JSON ในรูปแบบดั้งเดิมปรากฏขึ้นในเดือนเมษายน 2001 จากวิธีส่งข้อความจากเซิร์ฟเวอร์ไปยังไคลเอนต์ผ่าน hidden iframe และแท็ก <script> และเพิ่งกลายเป็นฟอร์แมตอิสระจริง ๆ ตอนที่ Douglas Crockford เปิดตัว json.org ในปี 2002
    ดังนั้น YAML จึงเกิดก่อน JSON เล็กน้อย หรือถ้ามองแบบเผื่อ ๆ ก็ใกล้เคียงกับการพัฒนาพร้อมกันมากกว่า การบอกว่าเป็นปฏิกิริยาต่อ JSON หรือเกิดมาเพื่ออุดจุดอ่อนของ JSON จึงไม่แม่นนัก และจริง ๆ แล้ว YAML เป็น ปฏิกิริยาต่อ XML มากกว่า ส่วน JSON เองแม้จะเริ่มจากเหตุผลเชิงปฏิบัติในการใส่ข้อมูลลงในแท็ก <script> ตรง ๆ แต่ก็คงปฏิเสธไม่ได้ว่ามันได้รับความนิยมเพราะกลายเป็นทางเลือกที่เรียบง่ายกว่า XML
    ตัวบทความเองก็ดูมีร่องรอยเหมือน LLM เขียน และโปรเจ็กต์ที่ยกมาก็เหมือนจะเป็นงาน vibe coding ด้วย

    • การบอกว่าเดิมที YAML เป็น ปฏิกิริยาและทางเลือกแทน XML นั้นถูกต้อง
    • ตอนที่บทความวิจารณ์ความซับซ้อนของ YAML แต่ภายหลังกลับชมสเปก YAML ใหม่ ฉันก็ได้กลิ่น LLM เหมือนกัน มันวิจารณ์ว่าสเปก 1.2.2 ยาวมากในระดับลึก 4 ชั้น แต่ข้างหลังก็พูดว่า 1.2.2 ยังใหญ่เหมือนเดิม แต่ซับซ้อนน้อยกว่า 1.1 มาก
      แต่ละประโยคฟังดูสมเหตุสมผล ทว่าพอมองรวมกันแล้วกลับชวนสับสน อีกอย่างคือมันปกป้อง TOML ว่าเป็นภาษาที่มีสเปก แต่กลับวิจารณ์ YAML ว่ามีสเปกซับซ้อน ฟังดูแปลกนิด ๆ เหมือนจะถามว่า YAML เดิมไม่มีสเปก แต่ TOML มีมาตั้งแต่แรกหรืออย่างนั้นหรือ
    • ถ้าจะเติมอีกอย่างให้กับประวัตินั้น Crockford เดิมทีทำงานกับ Data-E ซึ่งเป็นส่วนย่อยของ E และ Data-E ใช้แทนข้อมูลอย่างง่ายเท่านั้น ผู้สร้าง E หันไปทางทำให้ ECMAScript ปลอดภัยเชิง capability แทนการผลักดันภาษาของตัวเอง และผลก็คือแนวคิดหลายอย่างจาก E เช่น WeakMap ถูกนำเข้าไปใน ECMAScript
      จริง ๆ แล้ว JSON ใกล้เคียงกับ Data-E ในสไตล์ ECMAScript มากกว่า ถ้าดู หน้าเก็บถาวรของ Data-E ก็จะเห็นว่าพวกเขาเองก็กำลังตอบสนองต่อ XML อยู่เช่นกัน
    • ถ้าจะตีความบทความนี้ในแง่ดีใหม่ อาจพูดได้ว่าไม่ใช่ตัวการพัฒนา YAML เอง แต่เป็น การยอมรับใช้งาน YAML ที่เป็นปฏิกิริยาต่อข้อจำกัดของ JSON ส่วนตัวฉันเองก็เพิ่งมารู้จัก YAML หลังจาก JSON แพร่หลายไปแล้วเหมือนกัน แต่ก็ไม่มีข้อมูลมารองรับมุมมองนี้
  • ถ้าใช้ inline table ตัวอย่าง TOML แบบ “แย่” ก็จะออกมาประมาณนี้

    [services.web]  
    image = "nginx:latest"
    
    environment = {  
      DB_HOST = "postgres",  
      DB_PORT = 5432,  
    }
    
    resources.limits = {  
      memory = "512M",  
      cpu = "0.5",  
    }  
    

    ก็ดูโอเคดี และถ้าวัดกันที่จำนวนตัวอักษร มันยังดูสั้นกว่าตัวอย่าง YAML ด้วยซ้ำ

  • แม้อาจจะเกินขอบเขตไปหน่อยเพราะเป้าหมายของบทความนี้คือการปกป้อง YAML แต่ก็น่าจะพูดถึง TOML 1.1 และฟีเจอร์ inline table ใหม่ด้วย มันแก้ 3 เรื่องที่คนไม่ชอบใน JSON ได้คือ รองรับชื่อคีย์ที่ไม่ต้องใส่เครื่องหมายคำพูด, สตริงคอมเมนต์ และ comma ท้ายรายการ

    • การบอกว่า “คำวิจารณ์ YAML ตอนนี้คือการโจมตีรูปแบบที่มีปัญหาซึ่งไม่มีอยู่แล้ว” ก็อาจพอฟังขึ้น แต่จากนั้นกลับไปโต้แย้งกับ TOML เวอร์ชันเก่า ก็ดูแปลกอยู่เหมือนกัน
      Python 3.15 รองรับ TOML 1.1 แล้ว และการยอมรับ TOML 1.1 ก็ดูจะดีกว่า YAML 1.2 มาก การอัปเดต parser ของ TOML 1.0 ไปเป็น 1.1 น่าจะเป็นงานเล็กน้อยมาก แต่การย้าย YAML 1.1 ไป 1.2 คงไม่ง่ายแบบนั้น ฉันยังหา changelog บน yaml.org แทบไม่เจอ เจอแต่สเปกก้อนใหญ่สองฉบับ
      เรื่องอย่าง “Norway Problem” สำหรับฉันเป็นแค่เชิงอรรถเล็ก ๆ ของเหตุผลที่ไม่ชอบ YAML เหตุผลจริงคือมันแก้ไขยาก ใช้ whitespace ที่มีความหมาย และโดยรวมค่อนข้างซับซ้อน
  • ฉันคิดว่า YAML, JSON, และ TOML ต่างก็มีพื้นที่ของตัวเอง และช่องว่างที่ฉันรู้สึกว่ามีมานานนั้น https://kdl.dev/ เข้ามาเติมได้พอดี
    JSON = การแลกเปลี่ยนข้อมูลที่ซ้อนกันได้ตามอำเภอใจ
    YAML = ฟอร์แมตคอนเทนเนอร์ตื้น ๆ สำหรับเก็บสตริงหลายบรรทัด
    TOML = ไฟล์คอนฟิกที่เกือบแบนราบ
    KDL = การแทน DSL สไตล์ Ruby ในรูปข้อมูล

    • สำหรับโปรเจ็กต์ใหม่ ถ้าต้องมีคอนฟิก ฉันก็ลงเอยที่ KDL เหมือนกัน เพราะมันตัดไวยากรณ์ตัวคั่นหรือกฎการเยื้องที่ไม่เกี่ยวข้องออกไปได้เยอะ
    • KDL ดีจริง ๆ ฉันเอาแต่หาโอกาสจะใช้มัน มีหลายสถานการณ์ที่การมีทั้งแอตทริบิวต์และโหนดลูกแบบ XML ทำให้มาร์กอัปอ่านง่ายขึ้นมาก และมันก็มอบตัวเลือกนั้นให้ด้วย ไวยากรณ์ที่เบา ซึ่งดีมาก
    • พอเห็นตัวอย่างครั้งแรกฉันก็คิดทันทีว่า “นี่หน้าตาเหมือน SDLang เลย” ซึ่งก็ไม่ใช่เรื่องบังเอิญ ขอบคุณที่แนะนำ KDL
  • ฉันไม่ชอบ YAML เพราะเรื่องอย่าง Norway problem แม้ว่า YAML 1.2 จะลดปัญหาลงไปบ้าง แต่เพราะมีสตริงที่ไม่ใส่เครื่องหมายอัญประกาศ สตริงอย่าง "", "Null", "true", "FALSE" ก็ยังคงเป็นปัญหาอยู่ และต้องใช้อินโค้ดเดอร์ที่เข้าใจ YAML ด้วย ฉันก็ไม่ชอบความซับซ้อนโดยรวมเหมือนกัน แต่เหตุผลจริง ๆ ที่ฉัน เกลียด YAML คือเรื่องนี้

    tab characters must not be used in indentation
    ถ้าการเยื้องมีความหมาย การห้ามไม่ให้ผสมแท็บกับช่องว่าง หรือใช้อย่างไม่สม่ำเสมอ ก็ถือว่าโอเคอยู่ แนวทางของ Python 3 ดูค่อนข้างดี
    Indentation is rejected as inconsistent if a source file mixes tabs and spaces in a way that makes the meaning dependent on the worth of a tab in spaces; a TabError is raised in that case.
    แต่ YAML ดูเหมือนจะเป็นรูปแบบไฟล์เดียวที่ทั้งอนุญาตหรือบังคับให้มีการเยื้อง แต่กลับ ไม่รองรับทั้งแท็บและช่องว่างพร้อมกัน
    จะบอกว่า Make ก็ไม่รองรับช่องว่างก็ได้ แต่ .RECIPEPREFIX สามารถตั้งเป็นค่าอื่นที่ไม่ใช่แท็บได้ แถมใน Make เอง แท็บก็อาจมองได้ว่าไม่ใช่การเยื้อง แต่เป็นเครื่องหมายที่ Make ใช้

    • ตอนนี้หาต้นทางไม่เจอ แต่จำได้ว่าเคยอ่านคำพูดของ Guido van Rossum ว่าถ้าจะสร้าง Python ใหม่ตั้งแต่ต้น สิ่งหนึ่งที่เขาอยากเปลี่ยนแน่ ๆ คือ การห้ามใช้ตัวอักษรแท็บจริงในการเยื้อง
      PEP-8 ก็แนะนำอย่างหนักแน่นว่าไม่ควรใช้แท็บในการเยื้อง

      Tabs should be used solely to remain consistent with code that is already indented with tabs.
      -- https://peps.python.org/pep-0008/#tabs-or-spaces

    • อนึ่ง ข้อกำหนดของ NestedText ก็พูดแบบนี้

      Only ASCII spaces are allowed in the indentation. Specifically, tabs and the various Unicode spaces are not allowed.

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

  • พอเจอประโยคที่ว่า “ข้อถกเถียง YAML vs TOML มักเป็นการโจมตีรูปแบบที่มีปัญหาในแบบที่แทบไม่มีอยู่อีกแล้ว ดังนั้นความไม่พอใจจึงเป็นเรื่องจริงแต่เป็นเรื่องทางประวัติศาสตร์” ก็ทำเอาอยากกรีดร้องใส่ GitHub Actions กับ Kubernetes

  • เหตุผลฝั่งปกป้องนั้นหนักแน่นดี แต่สำหรับเอกสารที่ง่ายมาก ๆ TOML อ่านง่ายกว่า และทำให้คนเลือกใช้ TOML แทน YAML ได้ง่ายกว่า
    น่าเสียดายที่การเปลี่ยนการรับรู้ของนักพัฒนาในที่สาธารณะต่อเครื่องมือนั้นมีแรงเฉื่อยยาวมาก ผู้คนอ่านเรื่องเล่าบางอย่างแล้วก็ตัดสินใจ จากนั้นก็ย้ายไปใช้เครื่องมืออื่นที่ยังไม่เคยพลาดต่อหน้าสาธารณะ

    • แต่พอการจัดโครงสร้างรวมถึงอาร์เรย์ เกิน 2 ระดับ ไปแล้ว ก็ไม่เป็นแบบนั้นอีก ณ จุดนั้น การเยื้องช่วยให้เข้าใจโครงสร้างได้ง่ายกว่ามาก
  • ฉันอยากให้พาร์เซอร์ YAML ที่มากับ Ruby interpreter รองรับ YAML 1.2.2
    แต่ฉันก็ไม่แน่ใจว่าจะสลับเวอร์ชันใหม่ให้เป็นค่าเริ่มต้นโดยไม่ทำให้ ecosystem พังได้อย่างไร

  • ฉันอยากให้รูปแบบไฟล์คอนฟิกสามารถระบุ schema ที่เป็นมาตรฐาน ได้ แบบนั้น editor ก็จะเห็นไฟล์คอนฟิกใด ๆ แล้วเตือนเรื่องคีย์พิมพ์ผิดหรือชนิดข้อมูลไม่ตรงได้
    ควรมีการใส่เอกสารอธิบายว่าคีย์แต่ละตัวใช้ทำอะไรผ่านคำใบ้แบบ “hover” และควรให้ auto-complete คีย์ที่ใช้ได้อย่างง่ายดายด้วย ถ้ารองรับ assertion หรือ contract ง่าย ๆ เพื่อจับค่าที่ผิดได้ด้วยก็ยิ่งดี เช่น คีย์ "color" ควรต้องตรงกับ /#[a-fA-F0-9]{6}/
    ในอุดมคติแล้ว ควรใช้สิ่งนี้สร้างโครงสร้างข้อมูลของไฟล์คอนฟิกแบบอัตโนมัติได้ด้วย

    • นั่นก็คือการอธิบาย XML ตรง ๆ เลย
      มีรูปแบบสเปกการตรวจสอบหลายแบบ เช่น XSD หรือ Relax NG และฉันคุ้นกับ XML DTD มากที่สุด เลยพูดถึงตัวอื่นได้ไม่มาก
    • ในไฟล์ JSON การมีพร็อพเพอร์ตี $schema ที่คีย์ระดับบนสุดแล้วอ้างถึงไฟล์ JSON Schema ที่นิยามสคีมาที่ถูกต้อง เป็นแนวทางที่พบได้ค่อนข้างบ่อย โดยแก่นแท้แล้วมันคือ XSD สำหรับ JSON editor ที่ดีมักใช้สิ่งนี้เป็นพื้นฐานเพื่อให้ auto-complete และขีดเส้นใต้สีแดงได้
      ข้อดีคือ TOML กับ YAML โดยคร่าว ๆ แล้วก็เป็น JSON ที่ต่างกันแค่ไวยากรณ์ ดังนั้นจึงมักใช้ไฟล์ JSON Schema ร่วมกันได้ด้วย