3 คะแนน โดย GN⁺ 2024-04-01 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ข้อเสนอ JavaScript Signals ของ TC39 เป็นแนวทางระยะแรกเพื่อทำให้ primitive สำหรับ reactive สำหรับติดตามสถานะ UI และสถานะที่คำนวณได้อย่างมีประสิทธิภาพกลายเป็นมาตรฐาน และปัจจุบันยังเป็นร่างระดับ Stage 1
  • ข้อเสนอนี้มุ่งเน้นที่ ความหมายหลักของกราฟ Signal และกลไกการติดตามอัตโนมัติที่เฟรมเวิร์กสามารถใช้ร่วมกันได้ มากกว่า API ระดับผิวหน้าที่นักพัฒนาแอปพลิเคชันจะใช้โดยตรง
  • Signal.State, Signal.Computed, Signal.subtle.Watcher คือ API หลัก โดยการคำนวณมุ่งไปที่ lazy evaluation, การแคช, การติดตาม dependency อัตโนมัติ และการทำงานแบบ glitch-free
  • เป้าหมายของ Signals ในตัวคือเพิ่ม ความสามารถในการทำงานร่วมกัน ระหว่างเฟรมเวิร์กหลายตัว เช่น Angular, Ember, MobX, Preact, Qwik, Solid, Svelte, Vue และเปิดทางให้ DevTools รองรับการดีบักและการวิเคราะห์ประสิทธิภาพได้
  • กลุ่มข้อเสนอตั้งใจจะผ่าน polyfill ระดับ production หลายตัว, การผสานรวมกับเฟรมเวิร์ก, การตรวจสอบกับแอปพลิเคชันขนาดใหญ่ และ benchmark ด้านประสิทธิภาพก่อน Stage 2 โดยการทำให้เป็นมาตรฐานอาจใช้เวลาอย่างน้อย 2–3 ปีขึ้นไป

ตำแหน่งของข้อเสนอ JavaScript Signals

  • JavaScript Signals ถูกแนะนำในฐานะข้อเสนอ Stage 1 ของกระบวนการ TC39
  • เอกสารปัจจุบันเป็นความพยายามในการปรับทิศทางร่วมของระบบนิเวศ JavaScript คล้ายกับ Promises/A+ ก่อนการทำให้ Promise เป็นมาตรฐานใน ES2015
  • มี polyfill ให้ทดลองใช้งานโดยตรง
  • champion ของข้อเสนอและผู้เขียนดั้งเดิมจัดทำร่างปัจจุบันโดยอาศัยข้อมูลการออกแบบจากหลายเฟรมเวิร์กและไลบรารี

ปัญหาที่การทำมาตรฐานมุ่งแก้

  • UI ที่ซับซ้อนต้องจัดเก็บค่า คำนวณ ทำให้ค่าไม่ถูกต้อง ซิงก์ และผลักไปยังเลเยอร์ view โดย Signals พยายามจัดหา โครงสร้างพื้นฐานสำหรับการจัดการสถานะ ให้กับงานซ้ำ ๆ เหล่านี้
  • ในตัวอย่าง Vanilla JS นั้น counter, isEven, parity, render พัวพันกันโดยตรง ทำให้เกิดปัญหาต่อไปนี้
    • สถานะและระบบเรนเดอร์เชื่อมโยงกันแน่นเกินไป
    • เกิดการคำนวณและเรนเดอร์ที่ไม่จำเป็น แม้ parity จะไม่เปลี่ยน เช่น กรณี counter เปลี่ยนจาก 2 เป็น 4
    • เมื่อชิ้นส่วน UI อื่นต้องการ subscribe เฉพาะบางส่วนของ counter, isEven, parity การจัดการ subscribe และยกเลิกด้วยมือจะซับซ้อนขึ้น
    • หากเพิ่ม pub/sub หลายระดับ จะเพิ่ม boilerplate และ bookkeeping ของ subscription รวมถึงความเสี่ยง memory leak
  • ตัวอย่างที่ใช้ Signals ใช้ Signal.State และ Signal.Computed เพื่อจัดการค่า การคำนวณ และ side effect ด้วยวิธีเดียวกัน
    • ไม่ต้อง subscribe ด้วยมือ
    • computed Signal จะค้นหา Signal ที่ตนพึ่งพาโดยอัตโนมัติ
    • การคำนวณจะทำงานเฉพาะเมื่อมีการร้องขอค่าอย่างชัดเจนเท่านั้น
    • computed Signal จะแคชค่าล่าสุดไว้

API หลักของข้อเสนอ

  • Signal<T> ถูกนิยามเป็นค่าที่อ่านได้ซึ่งมี get(): T
  • Signal.State<T> คือ Signal ที่เขียนค่าได้
    • constructor รับค่าเริ่มต้นและตัวเลือก
    • อ่านค่าด้วย get() และเปลี่ยนค่าด้วย set(t)
  • Signal.Computed<T> คือ Signal ที่คำนวณได้ จาก Signal อื่น
    • คำนวณจากค่าที่ callback คืนกลับ
    • ติดตาม dependency โดยอัตโนมัติ
    • ค่าจะถูกคำนวณแบบ lazy และถูกแคชไว้
  • Signal.subtle รวม API ขั้นสูงที่ใกล้กับการใช้งานของผู้เขียนเฟรมเวิร์กหรือการ implement DevTools
    • untrack(cb) ทำให้อ่าน Signal ได้โดยไม่ถูกติดตาม
    • currentComputed() คืนค่า computed Signal ที่กำลังถูกติดตามในปัจจุบัน
    • introspectSources, introspectSinks, hasSinks, hasSources เป็น API สำหรับสังเกตกราฟ
    • Watcher เป็นฐานสำหรับตรวจจับการเปลี่ยนแปลงของ Signal และ implement effect กับ scheduling ในระดับเฟรมเวิร์ก
  • SignalOptions<T> รองรับฟังก์ชันเปรียบเทียบแบบกำหนดเอง equals และ hook watched / unwatched

วิธีทำงานและโมเดลการรัน

  • Signal แทน data cell ที่เปลี่ยนแปลงได้ตามเวลา และแบ่งเป็น state หรือ computed
  • computed Signal จะบันทึก Signal ที่อ่านระหว่างการรันโดยอัตโนมัติ และเมื่อถูกอ่านในภายหลังจะตรวจสอบว่า dependency เดิมเปลี่ยนไปหรือไม่
  • การคำนวณเป็นแบบ pull-based
    • แม้ dependency เปลี่ยน ก็ไม่คำนวณใหม่ทันที
    • จะคำนวณใหม่เมื่อจำเป็นในตอนที่มีคนอ่านด้วย .get()
  • การเขียน State Signal จะสะท้อนผลแบบ synchronous
    • หลัง .set() หากอ่าน computed Signal ที่พึ่งพาค่านั้น จะคำนวณใหม่ทันทีเมื่อจำเป็น
    • ไม่มี batching ในตัว
  • callback notify ของ Watcher อาจถูกเรียกแบบ synchronous ระหว่าง .set()
    • อย่างไรก็ตาม ระหว่าง notify จะไม่สามารถอ่านหรือเขียน Signal ได้
    • งานอ่าน/เขียนจริงต้องถูก schedule ในภายหลัง
  • หาก callback ของ computed Signal โยน exception ออกมา exception นั้นจะถูกแคชเหมือนค่า และจะถูกโยนอีกครั้งเมื่ออ่าน Signal นั้นใหม่

แรงจูงใจในการทำมาตรฐาน

  • การ implement Signal ของแต่ละเฟรมเวิร์กมีกลไกติดตามอัตโนมัติของตนเอง ทำให้ยากต่อการแชร์ model, component และ library ระหว่างเฟรมเวิร์ก
  • เป้าหมายของข้อเสนอคือแยก reactive model ออกจาก view สำหรับการเรนเดอร์
    • ช่วยให้นักพัฒนาไม่ต้องเขียนโค้ดที่ไม่ใช่ UI ใหม่เมื่อเปลี่ยนเทคโนโลยีการเรนเดอร์
    • ทำให้สร้าง reactive model ด้วย JavaScript ที่แชร์ได้ในหลายบริบท
  • ด้านประสิทธิภาพและหน่วยความจำ ระบุว่า implementation ในตัวอาจมีประสิทธิภาพกว่าการ implement ด้วย JS ในระดับค่าคงที่เล็กน้อย แต่เอนจินจะไม่ได้เปลี่ยนอัลกอริทึมอย่างวิเศษ
  • ด้าน DevTools นั้น Signals ในตัวอาจแสดงข้อมูลต่อไปนี้ได้ดีขึ้น
    • call stack ของ chain ของ computed Signal
    • กราฟอ้างอิงระหว่าง Signal
    • ความสัมพันธ์ dependency ที่จำเป็นต่อการดีบักการใช้หน่วยความจำ
  • หากรวมอยู่ใน standard library ก็คาดหวังผลพลอยได้ เช่น ลดขนาด bundle, เพิ่มเสถียรภาพและคุณภาพ, และสร้างคำศัพท์ร่วมระหว่างโปรเจกต์

เป้าหมายและข้อจำกัดด้านการออกแบบ

  • ฟังก์ชันหลักรวมถึง Signal ที่เขียนค่าได้, Signal ที่คำนวณได้, การตอบสนองต่อสถานะ dirty, scheduling ของเฟรมเวิร์กเอง, untrack, และการประกอบกันของหลาย codebase
  • computed Signal ตั้งเป้าให้เป็น glitch-free
    • เพื่อหลีกเลี่ยงการคำนวณที่ไม่จำเป็น จะรันส่วนของกราฟที่อาจ dirty โดยเรียงลำดับเชิง topology
    • พยายามกำจัดการคำนวณซ้ำ
  • ไม่ใส่ forced scheduling ในตัวแบบสไตล์ Promise เพื่อให้เฟรมเวิร์กสามารถ scheduling เองได้
  • เพื่อป้องกันการใช้ callback แบบ synchronous reaction ผิดวิธี จึงห้ามอ่านและเขียน Signal ภายใน notify ของ Watcher
  • untrack ถูกมองเป็นทางออกฉุกเฉินที่ไม่ปลอดภัย
    • หาก Signal ที่อ่านโดยไม่ถูกติดตามส่งผลต่อผลลัพธ์การคำนวณ เมื่อ Signal นั้นเปลี่ยน computed Signal อาจไม่อัปเดต
  • API ให้ความสำคัญกับการเป็นฐานสำหรับ implementation ของเฟรมเวิร์ก และไม่ได้ออกแบบมาให้ ergonomic เป็นพิเศษสำหรับนักพัฒนาแอปทั่วไป

effect และ Watcher

  • ข้อเสนอนี้ไม่มีฟังก์ชันในตัวอย่าง effect()
  • scheduling ของ effect เกี่ยวพันกับรอบการเรนเดอร์ของเฟรมเวิร์ก, disposal และการจัดการ ownership จึงไม่ใช่สิ่งที่ API มาตรฐาน JavaScript จะแก้โดยตรง
  • แต่ Signal.subtle.Watcher ให้ฐานระดับต่ำสำหรับ implement effect
    • เมื่อ dependency ของ Signal ที่ถูก watch เปลี่ยน notify จะถูกเรียก
    • ใช้ getPending() เพื่อตรวจสอบ Signal ที่ยัง dirty อยู่ได้
    • effect ที่ต้อง dispose ต้องจัดการด้วย unwatch
  • Signal ที่ Watcher กำลังดูอยู่อาจยังคงมีชีวิตตราบใดที่ state ภายในยังเข้าถึงได้ ดังนั้นเมื่อ cleanup effect จึงจำเป็นต้องเรียก Watcher.prototype.unwatch

ฟีเจอร์ที่ยังไม่อยู่ในร่างปัจจุบัน

  • Async ยังไม่ถูกรวมไว้ในโมเดลปัจจุบัน
    • Signals ถูกมองเป็นค่าที่ประเมินได้แบบ synchronous เสมอ
    • มีบางวิธีที่สามารถ model สถานะ loading เป็น exception ได้ และมีการพูดคุยเพื่อปรับปรุงใน Issue #30
  • Transactions ก็ยังไม่ถูกรวมไว้
    • หากต้องรักษาสถานะ “from” และ “to” พร้อมกันในการเปลี่ยนหน้าจอ จะเกิดปัญหาการ fork สถานะของกราฟ Signal
    • การพูดคุยที่เกี่ยวข้องอยู่ใน Issue #73
  • convenience methods บางส่วนก็ยังไม่อยู่ในร่างปัจจุบัน
  • ฟีเจอร์ที่ขาดไปถูกตัดออกเพราะยังไม่มีฉันทามติระหว่างเฟรมเวิร์กและสามารถ workaround ในเลเยอร์ที่สูงกว่าได้ แต่หลัง prototype แล้วอาจถูกนำกลับมาพิจารณาอีกครั้ง

แผนพัฒนาและกำหนดการมาตรฐาน

  • ข้อเสนอนี้ถูกเสนอเป็นวาระ TC39 Stage 1 ในเดือนเมษายน 2024 และเอกสารอธิบายว่าปัจจุบันอาจมองได้คล้าย Stage 0
  • ก่อนเสนอ Stage 2 มีแผนทำงานต่อไปนี้
    • พัฒนา implementation ของ polyfill ระดับ production หลายตัว
    • ทดสอบกับเฟรมเวิร์กหลากหลายและผ่านการทดสอบสไตล์ test262
    • ตรวจสอบประสิทธิภาพผ่านชุด benchmark ของ signal/framework ที่ครอบคลุม
    • ผสานรวม API ที่เสนอเข้ากับเฟรมเวิร์ก JS ตัวแทนหลายตัวและแอปพลิเคชันขนาดใหญ่บางส่วน
    • ทำความเข้าใจความเป็นไปได้ในการขยาย API และตัดสินใจว่าจะรวมไว้หรือไม่
  • กลุ่มข้อเสนอจะเดินหน้าอย่างระมัดระวังเพื่อหลีกเลี่ยงการทำ Signals ในรูปแบบที่ผิดให้กลายเป็นมาตรฐานเร็วเกินไป
  • FAQ คาดว่า Signals มาตรฐานจะใช้ได้ทั่วเบราว์เซอร์โดยไม่ต้องใช้ polyfill ต้องใช้เวลาอย่างน้อย 2–3 ปี
  • polyfill ปัจจุบันใช้งานได้ แต่แนะนำว่าไม่ควรพึ่งพาความเสถียร เพราะ API อาจเปลี่ยนระหว่างกระบวนการ review

โมเดลการใช้งานที่สรุปใน FAQ

  • Signals ในตัวเป็นอิสระจากเทคโนโลยีการเรนเดอร์
    • อธิบายว่ารองรับได้ทั้งรูปแบบอย่าง Preact ที่ใช้ VDOM, Solid ที่ใช้ native DOM และ Vue ที่ใช้แนวทางผสม
  • โดยทั่วไปนักพัฒนาแอปควรใช้ Signals ผ่านเฟรมเวิร์ก
    • เฟรมเวิร์กจะจัดการ Watcher, untrack, ownership, disposal และ scheduling การเรนเดอร์ DOM
  • สามารถใช้ร่วมกับ SSR, hydration และ resumability ได้
    • Qwik ใช้ Signals ร่วมกับคุณสมบัติเหล่านี้ และฝ่ายข้อเสนอเห็นว่า resumable Signals ของ Qwik สามารถ model ได้ด้วยการผสม State และ Computed
  • Signals และ Proxy เป็นสิ่งที่เสริมกัน
    • Proxy ดักจับการทำงานระดับตื้นของ object ส่วน Signals ประสานกราฟ dependency ของ data cell
    • หากวาง Signals ไว้หลัง Proxy จะทำให้ nested reactive structure ergonomic ขึ้นได้
  • Signals เป็น cell ที่แสดงค่าปัจจุบัน ไม่ใช่ stream
    • หากเขียนค่าไปยัง State Signal สองครั้งติดกันโดยไม่ทำอะไรเลย การเขียนครั้งแรกอาจไม่ปรากฏต่อ computed Signal หรือ effect
    • เอกสารมองว่านี่เป็นคุณสมบัติอีกด้านของการทำงานแบบ glitch-free และระบุว่าสำหรับ stream นั้นโครงสร้างอื่นอย่าง async iterable หรือ observable เหมาะสมกว่า

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

 
GN⁺ 2024-04-01
ความคิดเห็นจาก Hacker News
  • มีแค่ฉันคนเดียวไหมที่รู้สึกว่าตัวอย่าง JavaScript แบบล้วนๆกลับอ่านและจัดการได้ง่ายกว่า
    เขาบอกว่า “การตั้งค่าดูรกและมี boilerplate เยอะ” แต่ตัวอย่าง signals เองก็ดูรกและมี boilerplate เยอะเหมือนกัน แถมยังเพิ่มแนวคิดใหม่ที่มือใหม่เข้าใจได้ยากเข้าไปอีก
    ประเด็นที่ว่า “ถ้า counter เปลี่ยนจาก 2 เป็น 4 parity ไม่เปลี่ยน แต่กลับมีการคำนวณและเรนเดอร์ที่ไม่จำเป็น” ฟังดูเหมือน premature memoization
    ถ้าส่วนอื่นของ UI อยากเรนเดอร์ตามการอัปเดตของ counter ก็จริงที่ตัวอย่าง strawman นั้นไม่เหมาะ และในกรณีนั้นก็ใช้วิธีอื่นอย่าง signals, event handling หรือ central state store (แนว Redux) ได้
    ถ้าส่วนอื่นของ UI พึ่งพาแค่ isEven หรือ parity อย่างเดียว ก็อาจเปลี่ยนแนวทางได้ถ้านั่นเป็นโครงสร้างหลักของแอป แต่ส่วนใหญ่ไม่ใช่แบบนั้น และการที่ “ฟังก์ชัน render ที่พึ่งพา parity อย่างเดียวต้องรู้ว่าต้อง subscribe กับ counter” ก็ไม่ใช่ภาระที่ไม่ยุติธรรมเสมอไป อีกทั้งฟังก์ชันคำนวณแบบล้วนๆ ยังมีข้อดีตรงที่ดูอินพุตได้ง่าย

    • ไม่เข้าใจว่าทำไมถึงมองว่านี่คือ premature memoization นี่ก็เป็นแค่ตัวอย่างที่ย่อให้เป็นฟังก์ชันง่ายๆ และคงพูดยากว่าคนเราสร้าง use case แบบนี้ขึ้นมาโดยที่ไม่เคยต้องใช้จริง
      การพยายาม ทำ signals ให้เป็นมาตรฐาน ซึ่งเป็นแนวคิดที่ถูกใช้มากขึ้นเรื่อยๆ ในการพัฒนา UI ถือว่าน่าชื่นชม ต่อให้พักข้อถกเถียงรายละเอียดอย่าง boilerplate เยอะแค่ไหน หรือจำเป็นต้องสร้างระบบ event เองไหม ถ้าหลายเฟรมเวิร์กใช้ signals ก็อาจมีเหตุผลอยู่ และถึงจะใช้เวลาก็ควรลองทำให้เป็นมาตรฐาน
    • เห็นด้วย แต่ถ้าไปดูเอกสาร signal ของ Preact จะเห็นบริบทชัดกว่ามาก
      https://preactjs.com/guide/v10/signals
      ใน Preact เวลาส่ง signal ลงไปตาม tree ผ่าน props หรือ context จะส่งแค่การอ้างอิงถึง signal และเพราะคอมโพเนนต์มอง signal ไม่ใช่ค่าของมัน การอัปเดต signal จึงอาจไม่ต้องทำให้คอมโพเนนต์ re-render ใหม่ และสามารถไปยังคอมโพเนนต์ใน tree ที่เข้าถึง .value ได้โดยตรง
      นอกจากนี้ signal ยังติดตามได้ว่าค่าเมื่อไรถูกเข้าถึงและเมื่อไรถูกอัปเดต และใน Preact ถ้าเข้าถึง .value ของ signal ภายในคอมโพเนนต์ คอมโพเนนต์นั้นจะ re-render อัตโนมัติเมื่อค่า signal เปลี่ยน
    • เพราะ JavaScript ไม่มี reactivity ติดมากับภาษาอยู่แล้ว การเพิ่ม reactivity เข้าไปจึงเลี่ยงไม่ได้ที่จะมี ต้นทุนของ abstraction มันมีไว้ใช้เมื่อจำเป็น ไม่จำเป็นต้องเป็นวิธีพื้นฐานในการจัดการสถานะเสมอไป
      จากประสบการณ์ ข้อดีอย่างหนึ่งที่ใหญ่มากคือสามารถ ทำสถานะแบบ reactive ให้เป็นโมดูลได้ ในสไตล์ imperative จะต้องมีสถานะเพิ่มเพื่อคอยติดตามการเปลี่ยนแปลง และความเป็นโมดูลก็มาจาก abstraction ใช้เฉพาะเวลาที่ต้องใช้ก็พอ
      การทำตัวอย่างที่ทั้งเรียบง่ายและยังใช้ได้จริงเป็นเรื่องของการหาสมดุล กรณีที่ reactivity ให้ประโยชน์ชัดเจนมักซับซ้อนกว่า เลยแสดงให้เห็นได้ยากกว่าตัวอย่างที่ง่ายกว่าแต่ใช้ได้จริงน้อยกว่า
    • วิธีอธิบายเรื่องนี้ยังปรับปรุงได้อีก ในตัวอย่างเล็กๆ ปัญหาจะยังไม่ชัด และมันจะเริ่มเห็นเมื่อขนาดใหญ่ขึ้น PR ยินดีต้อนรับ
    • การที่เมื่อเกินระดับความซับซ้อนระดับหนึ่งแล้วต้อง เปลี่ยนแบบการออกแบบ เป็นสิ่งที่ควรหลีกเลี่ยง แนวทาง pure JS มีขีดจำกัดด้านการขยายตัวในแง่ความซับซ้อนของ state graph และปัญหาจริงไม่ใช่เรื่อง usability ก่อนหรือหลังจุดนั้น แต่คือพอข้ามจุดนั้นไปแล้ว usability จะเปลี่ยนแบบไม่ต่อเนื่องทันที
  • ตอนที่ JavaScript เพิ่ม Promises เข้ามา ก็เคยรู้สึกต่อต้านเพราะคิดว่าคงต้องเขียน new Promise ไปทั่ว
    เอาเข้าจริง จำนวนครั้งที่เขียน new Promise เองนับด้วยสองมือยังไหว แต่กลับได้ใช้ .then บ่อยกว่ามาก โดยเฉพาะเวลาจัดการไลบรารีของ third party
    สุดท้ายแล้วผลในชีวิตประจำวันจากการที่ Promise ถูกเพิ่มเข้ามาใน JavaScript คือมันให้อินเทอร์เฟซที่ค่อนข้างเรียบง่าย โดยรวมแข็งแรง และแทบจะเป็นสากล สำหรับพฤติกรรมและความสามารถเฉพาะทางมากมายที่ไลบรารี third party มีให้ ไม่ว่าจะเป็นการอ่านไฟล์ การเรียก API หรือ output จากขั้นตอน build แค่ใช้ .then(res => …) ก็เหมือนมาถึงครึ่งทางของสิ่งที่ใช้งานได้แล้ว
    ถ้า ข้อเสนอ Signal นี้จะทำหน้าที่คล้ายกันท่ามกลางการระเบิดแบบแคมเบรียนของเฟรมเวิร์ก UI เชิง reactive ก็เห็นด้วย และยิ่งไปกว่านั้น มันอาจช่วยให้ reactivity ขยายออกไปนอก UI ได้ด้วย ฉันมักจินตนาการถึง state tree สำหรับการคำนวณซ้ำแบบค่อยเป็นค่อยไปที่ไม่ได้มีไว้สำหรับสถานะ UI อยู่บ่อยๆ

    • ผมมองว่า Promises เข้ามาหลักๆ เพื่อปูทางให้ async/await และสิ่งที่ยกระดับคุณภาพชีวิตอย่างมากจริงๆ ก็คือฝั่งนั้น ในงานจริงแทบไม่ค่อยมีโอกาสได้เขียน new Promise เอง
      .then ของ Promise ยุคแรกถือว่าดีกว่า delegate แบบซ้อนกันมาก และโอเคสำหรับ chain ที่ไม่ซับซ้อน แต่ถ้าต้อง chain Promise คนละแบบตามเงื่อนไข หรือต้องจัดการข้อผิดพลาดต่างกันในแต่ละ chain หรือต้อง return ก่อนเวลา โค้ดอาจอ่านและจัดการได้ยากขึ้นมาก
      ถ้าใช้ async/await ก็สามารถเขียนการเรียกใช้งานให้ดูเหมือนไม่ใช่ Promise ได้ วาง try/catch รอบการเรียก Promise เฉพาะจุดได้ง่าย และการ return ก่อนเวลาก็ดูเป็นธรรมชาติกว่า
  • ไม่เข้าใจว่าทำไมสิ่งนี้ถึงต้องเป็น ส่วนหนึ่งของภาษา ทำเป็นไลบรารีก็ได้และก็มีไลบรารีแบบนั้นอยู่แล้ว เนื่องจากมันมีขนาดเล็ก การรวมเข้าไปในโค้ดก็ไม่ได้เป็นภาระมากนัก และการเพิ่มอะไรเข้าไปในภาษาไม่ควรกลายเป็นเป้าหมายในตัวมันเอง
    การคิดว่าเพียงเพราะไลบรารี UI ของ JS ในปัจจุบันออกแบบ signals ได้ดีมาก จึงควรให้มันกลายเป็นส่วนหนึ่งของภาษา นั่นเป็นความหยิ่งผยองเกินไป signals มีการติดตั้งใช้งานหลายแบบที่มีจุดแลกเปลี่ยนต่างกัน และไม่มีแบบไหนเลยที่สมควรได้สถานะพิเศษในสเปกของ JavaScript
    ก่อนที่ไลบรารีเหล่านี้จะใช้ signals พวกมันก็ใช้ virtual DOM กันมาก่อน โชคดีที่ virtual DOM ไม่ได้กลายเป็นส่วนหนึ่งของ JS แล้ว signals ต่างกันตรงไหน? ไม่ต่างเลย เหตุผลที่จะทำให้เป็นมาตรฐานยังอ่อนกว่าสมัย virtual DOM เสียอีก
    จะเอาทุกอย่างที่กำลังเป็นกระแสมาใส่ทับถมไว้ในรันไทม์ที่แทบไม่มีทางลบฟีเจอร์ที่ไม่ต้องการอีกต่อไปได้โดยไม่ทำให้เว็บพังอย่างนั้นหรือ? มันสั้นighted มาก

    • มีจุดที่พูดถูกอยู่ ไม่ต้องการสิ่งที่ผิด แต่อยากได้สิ่งที่ถูกต้อง
      UI แบบ reactive ชนะแล้ว แม้แต่ในแอปเล็ก ๆ พอเริ่มจัดการสถานะ ความซับซ้อนก็พุ่งจนทำให้การใช้ JS ล้วน ๆ ยากขึ้น สำหรับผม เฟรมเวิร์กเชิง reactive แบบไหนก็ยังดีกว่า JS ล้วน ซึ่งอาจแปลว่ายังมีองค์ประกอบบางอย่างที่ขาดอยู่
      ตอนนี้ก็ผ่านมาราว 10 ปีแล้ว ถึงเวลาคิดถึงเส้นแบ่งที่พอจะทำให้เป็นมาตรฐานได้แล้ว เหมือน Promise ถ้าทำได้ดีจริงก็อาจลดความซับซ้อนของกรณีใช้งานที่พบบ่อยมากได้
      วิธีประเมินที่ดีกว่าคือถามว่า “เฟรมเวิร์ก reactive ที่มีอยู่จะใช้ข้อเสนอนี้ไหม?” ถ้าไม่ใช้ ก็ต้องดูว่าเพราะอะไร อะไรที่ขาด อะไรที่เกินมา และมีอะไรให้เรียนรู้จาก UI และความเป็น reactive ในภาษาอื่นบ้าง ประสบการณ์ที่กระจัดกระจายเหล่านี้มีคุณค่าพอจะนำมากลั่นกรอง
    • เหตุผลที่ดีข้อหนึ่งในการทำ Signals ให้เป็นมาตรฐานคือ การดีบัก ดูเหมือนฝันร้าย ลองนึกภาพต้นไม้ลึกของ computed signal ที่จุดชนวนกันเป็นลูกโซ่ แล้วคุณต้องหาว่าปฏิกิริยาลูกโซ่นั้นเริ่มต้นจากตรงไหน ถ้าทำเป็นมาตรฐานได้ ก็อาจมีเครื่องมือนักพัฒนาที่สร้างขึ้นมารองรับรอบ ๆ มัน
    • คุณพูดแบบเดียวกันนี้กับมาตรฐานไลบรารีส่วนใหญ่ได้เหมือนกัน แต่ตามที่อธิบายในแรงจูงใจ ตอนนี้มีแนวโน้มจะขยาย standard library ของ JS ที่ค่อนข้างเล็ก เพื่อไม่ให้ต้องนำเข้าแพ็กเกจทุกครั้งที่ทำงานทั่วไป
      ความจำเป็นของมันยังถกเถียงกันได้ แต่ถ้าจะขยาย standard library การดูจากสิ่งที่ได้รับความนิยมก็ถือเป็นแนวทางที่ดี
      signals ไม่ใช่ ตัวแทนของ virtual DOM
    • ทำให้นึกถึงข้อเสนอ Observable
  • ถ้าต้องส่งสัญญาณบางอย่างไปทั่วทั้งแอปพลิเคชัน ก็ใช้อีเวนต์
    window.dispatchEvent(new Event('counterChange'));
    แล้วส่วนไหนของแอปที่อยากตอบสนองก็สมัครรับแบบนี้ได้
    window.addEventListener('counterChange', () => { ... do something ... });
    วิธีนี้มีปัญหาอะไร?

    • ในทางประวัติศาสตร์ ตัวอย่างนี้เองคือเหตุผลที่เว็บพัฒนามาเป็น jQuery แล้วจากนั้นก็แยกไปสู่โลกของ Angular และ React
      การจัดการอีเวนต์ เละเทะได้ง่ายมาก ถ้าอยากดูให้ลึกกว่านี้ก็ลองดูเรื่อง event bubbling และ propagation
      แอปขนาดใหญ่ต้องการการจัดการอีเวนต์ที่แข็งแรง และนี่คือข้อดีอย่างหนึ่งของเฟรมเวิร์กอย่าง Angular, Vue ที่ทุกวันนี้อาจไม่เห็นเด่นชัดนัก
      คุณคงไม่อยากใช้ API จัดการอีเวนต์มาตรฐานแบบตรง ๆ โดยไม่มีเฟรมเวิร์ก เพราะพอเริ่มจัดการการเพิ่ม ลบ โคลน จุดชนวน ลบออก หรือเรียกครั้งเดียวสำหรับองค์ประกอบจำนวนมาก ก็อาจเกิดผลข้างเคียงไม่พึงประสงค์อย่างรุนแรงได้
    • ตามบทความ event emitter/observable ทำให้เกิดงานที่ไม่จำเป็นเมื่อถูกเรียกหลายครั้ง
      ความต่างจาก signals คือค่าผลลัพธ์จะถูกคำนวณก็ต่อเมื่อผู้ใช้ปลายทางอ่านค่าเท่านั้น มันแยกช่วงเวลาที่มีการเขียนจริงลงไปใน signal ออกจากการจองคิวอัปเดตการเรนเดอร์แบบอะซิงก์ และสายโซ่การคำนวณที่ตัวเฝ้าดูทำจะเกิดขึ้นเพียงครั้งเดียวระหว่างการเรนเดอร์
      ค่ากลางที่ถูกส่งเข้า signal จะหายไป จึงยากจะทำอะไรที่น่าสนใจมาก ๆ ในนั้น และโดยพื้นฐานแล้วมันใกล้เคียงกับชั้นนามธรรมระดับสูงสำหรับควบคุมจังหวะของรอบการเรนเดอร์มากกว่า
    • Signals ก็ยังเป็น publish/subscribe อยู่ดี แต่ API ใช้งานสะดวกกว่า เพราะตัว listener ถูกเพิ่มและยกเลิกให้อัตโนมัติ
      ประสิทธิภาพก็อาจดีกว่าได้ด้วย เช่น ถ้ามีการคำนวณที่ขึ้นกับสองค่า result = a ? b : 0 เมื่อ a เป็นเท็จ ต่อให้ b เปลี่ยนก็ไม่จำเป็นต้องคำนวณใหม่ ใน signals สิ่งนี้เกิดขึ้นอัตโนมัติ แต่ถ้าเป็น publish/subscribe แบบดั้งเดิมจะต้องเขียนโค้ดค่อนข้างมาก
    • ผมใช้แพตเทิร์นแบบนี้มามากกว่า 10 ปีแล้ว จุดยากคือเมื่อเวลาผ่านไป listener บางตัวจะไป trigger อีเวนต์อื่น แล้วอีเวนต์นั้นก็กลับมาหาลูปแรกอีกที จนเกิด ลูป listener ที่ไม่จบสิ้น
      และก็ยากที่จะรับประกันว่า listener ทุกตัวจะไม่สร้างการ trigger ต่อเนื่องแบบนั้น
    • วิธีนั้นมีข้อเสียทั้งหมดของ สถาปัตยกรรม publish/subscribe ตามที่ข้อเสนอเน้นไว้
  • ตลอดหลายสิบปีที่ผ่านมา ผู้คนพยายามทำความเข้าใจว่าทำไม การติดตามสถานะ และการอัปเดต DOM ถึงได้ยากขนาดนั้น
    แน่นอนว่าต้องมีวินัยอยู่บ้าง แต่ก็ยังรู้สึกว่าเรียบง่ายกว่าวิธีแก้ที่ออกมาทุก ๆ ไม่กี่ปีมาก ทั้ง Backbone, Knockout, Angular, React หรือแม้แต่การแก้ตัวภาษาเอง ดูเหมือนว่าวิธีคิดของผมน่าจะต่างออกไปตั้งแต่รากฐาน
    มันสะท้อนออกมาแม้แต่ในชื่อฟังก์ชัน การอัปเดต innerText กลับถูกเรียกว่า “render” ทั้งที่จริง ๆ แล้วมันไม่ได้เรนเดอร์อะไรเลย อย่างมากก็เป็นเบราว์เซอร์ที่เรนเดอร์ และนั่นก็เหมือนกับงานอื่นทั้งหมดที่เกี่ยวกับการ paint ด้วย มันทำให้ผมงงมาก เพราะรู้สึกเหมือนเป็นความพยายามสิ้นหวังที่จะทำให้หนึ่งในฟังก์ชัน DOM ที่ง่ายที่สุดกลายเป็นเรื่องซับซ้อน

    • สำหรับแอปพลิเคชันที่เรียบง่าย มันง่าย
      แต่พอซับซ้อนขึ้น มันก็ไม่ง่ายแล้ว
    • พอได้ยินคำว่า “ต้องมีวินัยอยู่บ้าง” ก็ทำให้รู้สึกแรง ๆ ว่า ถ้าเป็นสมัยก่อนผมคงโมโหในฐานะโปรแกรมเมอร์ ASM กับพวกโปรแกรมเมอร์ C บ้า ๆ ที่พกพาได้ และต่อมาก็คงโมโหในฐานะโปรแกรมเมอร์ C กับพวกโปรแกรมเมอร์ Java บ้า ๆ ที่เน้น memory safety
      ความก้าวหน้าของการเขียนโปรแกรมอาจมองได้ว่าเป็นกระบวนการกำจัดระเบียบวินัยแบบพิธีการและเข้มงวดที่จำเป็นต่อการได้ผลลัพธ์ที่ดี
      ไม่ได้แปลว่า React คือวิวัฒนาการขั้นถัดไป แต่ signals ชัดเจนว่าเป็นก้าวหนึ่งในทิศทางที่ถูกต้อง
    • หลายสิบปีถือว่านานมาก คุณคงจำได้ว่า DOM update เคยซับซ้อนแค่ไหนในแต่ละเบราว์เซอร์
      การซิงก์ DOM ให้ตรงกับสถานะข้อมูลนั้นไม่ยากเกินไป แต่การทำให้มันมีประสิทธิภาพสูงมากที่ 60fps นั้นยากมหาศาล โดยเฉพาะเวลาจะออกแบบ API ที่ไม่รั่วและไม่ยุ่งยากจนเกินไป
      พูดตามตรง บางทีการวาดพิกเซลลงบน canvas แบบเกมอาจง่ายกว่าการแปลงการเปลี่ยนแปลงไปสะท้อนกับ DOM tree ที่ยังมีชีวิตอยู่ด้วยซ้ำ
    • ผมกำลังสร้าง แอปเทรด FX ขนาดหลายแสนบรรทัดโค้ด มันอยู่ในระดับที่จะมาแทนเดสก์ท็อปแอปตัวใหญ่ ๆ มีนักพัฒนา 20~30 คน และมีลูกค้าหลายรายที่ต่างก็มีนักพัฒนาของตัวเอง ถ้าจะให้ทำแบบไม่มีเฟรมเวิร์กก็คงได้แต่บอกว่าโชคดีนะ
    • คิดเหมือนกันทุกอย่าง ผมเองก็เคยพัฒนา SPA ที่ซับซ้อนมากและมีปฏิสัมพันธ์สูง แต่จนถึงตอนนี้ก็ยังไม่เคยเจอปัญหาที่ของพวกนี้อ้างว่าจะแก้ได้เลย
  • Promises เป็นตัวอย่างความสำเร็จที่ดี แต่ถ้าไม่มี async/await ก็ไม่ได้จำเป็นต้องทำให้เป็นมาตรฐาน
    ร่างปัจจุบันบอกว่าอิงจากแนวคิดของผู้เขียน/ผู้ดูแล Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue และ Wiz ก็เลยสงสัยว่าผู้เขียนไลบรารีที่มีอยู่มองข้อเสนอนี้อย่างไร และที่น่าสนใจก็คือ React ไม่ได้อยู่ในรายชื่อ
    Signals คล้าย channels อยู่บ้าง แต่ต่างกันตรงที่เป็นการกระจายสัญญาณ ไม่ใช่มีผู้รับคนเดียว ถ้าใช้สิ่งนี้ทำให้ web worker สื่อสารผ่าน channel แทน onMessage callback ได้ก็คงเจ๋งดี โดยเฉพาะถ้าสามารถ select บน signals/channels/promises ได้แบบ Go ก็น่าจะได้ประโยชน์เชิงไวยากรณ์มากกว่าการจัดการกลไกส่งข้อความพร้อมกันหลายแบบด้วย callback เช่น ทำให้รวม signals เข้าไปใน Promise.any ได้

    • ไม่เห็นด้วยอย่างแรงกับประโยคที่ว่า “ถ้าไม่มี async/await ก็ไม่จำเป็นต้องทำให้เป็นมาตรฐาน”
      x instanceof Promise ใช้งานไม่ได้แบบนั้นเฉย ๆ ถ้าเมธอด then ในไลบรารีของผมรับ catch callback แต่ของอีกไลบรารีไม่รับ มันก็จะไม่ interoperable กันแบบเงียบ ๆ และไม่มีทางตรวจจับได้ว่าเกิดอะไรขึ้น finally ควรรันเมื่อไร? เราควรคาดหวังระดับความ asynchronous ของ callback ได้แค่ไหน?
      ถ้าไม่มีมาตรฐาน ทุกไลบรารีที่ใช้ Promise ก็ต้องพก polyfill ของตัวเองมา เพราะเชื่อถือของที่มีอยู่แล้วไม่ได้ และก็ไม่สามารถใช้ Promise ของไลบรารีอื่นได้จริง เพราะไม่มีทางมั่นใจได้ว่ามันจะทำงานตามที่คาด
      นี่ไม่ใช่การเดา แต่มันเคยเป็นแบบนั้นจริง ๆ มาหลายปี และเป็นนรกที่หลายคนต้องทนผ่านมันมา
    • ยังมี ข้อดีของการทำให้เป็นมาตรฐาน ที่ไม่เกี่ยวกับ async/await ด้วย JavaScript engine สามารถทำ performance optimization ที่เป็นประโยชน์กับแอปที่ใช้ Promise หนัก ๆ ได้ ซึ่งถ้าไม่เป็นมาตรฐานก็คงทำไม่ได้
    • เหตุผลที่ React ไม่อยู่ในรายชื่อก็เพราะ signals ไม่ได้เป็นส่วนหนึ่งของ React core API แบบเดียวกับ Preact
      ความรู้สึกเลือนรางของผมคือ signals มันคล้าย useEffect() แบบทั่วไปมากเกินไป พอเอาเข้า React แล้วอาจยิ่งทำให้สับสนว่าในรอบการเรนเดอร์เกิดอะไรขึ้นบ้าง จะดีหรือไม่ดี React ก็เลือกแนวทางการอัปเดตที่ต่างจาก signals ไปแล้ว แต่อาจเป็นได้ว่าผมมองเรื่องการนำไปใช้ผิดก็ได้
    • ที่ React ไม่อยู่ในรายชื่อนี้ก็เพราะเอฟเฟกต์ของมันไม่ใช่แบบ imperative แต่เป็น declarative จะมองว่าการเปลี่ยน props และการ re-render เป็น declarative ที่ยกระดับนามธรรมขึ้นอีกชั้นก็ได้ ส่วน useEffect ก็แยกพฤติกรรมแบบ imperative ออกมาไว้อย่างเรียบร้อย
      มันดูคล้าย data binding ของ Ember มาก และสุดท้ายอาจกลายเป็นฝันร้ายแบบ imperative ได้ สภาพตั้งต้นของมันใกล้เคียงกับ “ปืนลั่นใส่เท้าตัวเอง” มาก และถ้าจะกันไม่ให้เป็นแบบนั้นก็ต้องใช้ภาระทางความคิดมหาศาลพร้อม meta pattern อีกมาก
    • บางทีอาจเรียกสิ่งนี้ว่า “EventEmitter” ก็ได้
      https://nodejs.org/en/learn/asynchronous-work/the-nodejs-eve...
  • ฉันไม่เข้าใจตัวอย่างใน README ที่ลิงก์ไว้
    // A library or framework defines effects based on other Signal primitives
    declare function effect(cb: () => void): (() => void);
    เป็นไลบรารีไหน? เฟรมเวิร์กอะไร? ฉันหลงทางตั้งแต่ตรงนี้ effect คืออะไร?
    effect(() => element.innerText = parity.get());
    effect รู้ได้อย่างไรว่าต้องเรียกแลมบ์ดานี้ทุกครั้งที่ parity เปลี่ยน? มันเรียกแลมบ์ดานี้ทุกครั้งที่ signal เปลี่ยนหรือ? ถ้าอย่างนั้นจะพูดเรื่องแคชไปทำไม? น่าจะไม่ใช่
    อย่างไรก็ตาม ถ้าฉันเข้าใจสิ่งที่ผู้เขียนพยายามจะสื่อถูกต้อง แนวคิดเรื่อง signal เองก็ดูสมเหตุสมผลดี เพียงแต่ปัญหาใหญ่ของสถาปัตยกรรมแบบแยกส่วนประเภทนี้คือ เมื่อแอปพลิเคชันซับซ้อนมากพอ คุณจะหลงทางตอนพยายามตามว่าเหตุการณ์ใดเกิดขึ้นเพราะอะไร ตามอุดมคติแล้ว signals ควรแก้ไข stack trace เพื่อให้ตอน callback ถูกเรียก มันมี stack trace ของโค้ดที่เป็นตัวกระตุ้น signal ตั้งแต่แรกติดมาด้วยอยู่แล้ว

    • มีหลายไลบรารีที่ export ฟังก์ชันชื่อ effect และทำให้สามารถรันโค้ดอะไรก็ได้เพื่อตอบสนองต่อการอัปเดตของ signal ได้ บทนำเรื่อง signals และ effects ในเอกสารของ Preact อธิบายไว้ดี: https://preactjs.com/guide/v10/signals#effectfn
      เท่าที่ฉันเข้าใจ ฟังก์ชัน effect แบบนี้จะรัน callback หนึ่งครั้งก่อนเพื่อตรวจว่าระหว่างการรันมีการเข้าถึง signal ใดบ้าง แล้วจะเรียก callback ซ้ำทุกครั้งที่ signal ที่ callback นั้นพึ่งพาถูกอัปเดต ถ้าการเข้าถึง signal เป็นแบบ synchronous และ single-threaded ก็สามารถรู้ได้จากข้อเท็จจริงที่ว่ามีการเข้าถึง signal ระหว่างรัน callback ว่า callback นั้นควร subscribe signal นั้น
      ทำแบบนี้ผ่าน getter ก็ได้เช่นกัน ฟังก์ชัน effect จะติดตามว่าในเมธอด getter มีการเข้าถึงพร็อพเพอร์ตีใดของ signal บ้าง และเท่าที่รู้ Vue 2 เคยใช้วิธีนี้ในอดีต หรือจะใช้พร็อกซีเพื่อติดตามการเข้าถึงอ็อบเจ็กต์ก็ได้ ตัวอย่างในข้อเสนอนี้มีเมธอด get ที่ใช้เรียกเพื่อเข้าถึงค่า signal และสามารถติดตาม dependency ผ่านการเรียกเมธอดนี้ได้
      [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
      [2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
    • การเรียก parity.get() จะลงทะเบียนการพึ่งพาของฟังก์ชันที่ส่งให้ effect() เมื่อ parity ถูกอัปเดต ฟังก์ชันนั้นก็จะถูกเรียก
      มันไม่ได้เรียกทุกครั้งที่ signal เปลี่ยน แต่จะเรียกเฉพาะเมื่อ signal ที่มันพึ่งพาเปลี่ยนเท่านั้น
      ในกรณีนี้ parity พึ่งพา isEven และ isEven พึ่งพา counter ดังนั้นเมื่อ counter ถูกอัปเดต โซ่การพึ่งพาทั้งหมดจะถูกทำให้ใช้ไม่ได้ และ parity จะถูกทำให้ใช้ไม่ได้ด้วย ทำให้ callback ถูกรันอีกครั้ง
    • ไม่ว่าชื่อจะเป็นอะไร การ implement signals โดยทั่วไปจะสร้างกราฟการพึ่งพาแบบไดนามิก และจะเกิดเส้นเชื่อมเมื่อมีการอ่านโหนด ภายในบริบทการติดตามแบบ effect สมมตินี้ การอ่านจะสร้างเส้นเชื่อมระหว่าง state node ของ signal กับ computation node ของ effect ซึ่งในทางปฏิบัติคือทำให้อย่างหลัง subscribe การเขียนครั้งถัดไปของอย่างแรก เพื่อจะได้ตัดสินใจได้ว่าควรรันการคำนวณใหม่เมื่อใด
    • effect คือฟังก์ชันอะไรก็ได้ที่คุณอยากเรียก
      ใน signals กลไกติดตาม dependency จะรู้ว่าต้องคำนวณค่าใดใหม่ และผลลัพธ์ก็คือระบบจะรู้ด้วยว่าต้องเรียกฟังก์ชันใดอีกครั้ง
    • ดูเหมือนว่าจะต้องมี watcher เพื่อใช้ implement effect
  • ที่เกี่ยวข้องกันมี S.js: https://github.com/adamhaile/s
    ฉันชอบ signals มาก เวลาเขียน UI ฉันชอบมันมากกว่า primitive อื่นเกือบทั้งหมด ยกเว้นบางทีอาจมีแค่อัลกอริทึมข้อจำกัด cassowary เท่านั้น ในทุกภาษาที่ฉันใช้เล่น ๆ ฉันพยายามเลียนแบบ signals
    แต่ฉันไม่คิดเลยว่านี่ควรเข้าไปอยู่ในตัวภาษา JavaScript เอง ปล่อยภาษาไว้เฉย ๆ สักพักน่าจะดีกว่า ทุกวันนี้คนก็ตามได้ยากพออยู่แล้ว และ TC-39 ก็กำลังทำให้คนจำนวนมากกลัวภาษาและถอยห่างออกไปอยู่แล้ว

  • นี่ดูคล้ายกับ MobX ซึ่งเป็นระบบ effect ของ JS ที่ฉันชอบที่สุดมาก
    เวอร์ชันของ MobX เป็นแบบนี้
    import { observable, computed, autorun } from 'mobx';
    const counter = observable.box(0);
    const isEven = computed(() => (counter.get() & 1) === 0);
    const parity = computed(() => isEven.get() ? "even" : "odd");
    autorun(() => { element.innerText = parity.get(); });
    setInterval(() => counter.set(counter.get() + 1), 1000);

    • MobX ก็คือ signals นั่นแหละ เพียงแต่เป็น signals ที่ติดตาม dependency แบบแฝงผ่านพร็อกซีอ็อบเจ็กต์ แทนการติดตามอย่างชัดเจนผ่าน getter
  • ให้ความรู้สึกแบบ “มาอบเฟรมเวิร์กที่ฉันใช้ช่วงนี้ลงไปใน standard library กันเถอะ!”
    คล้ายกับการสักชื่อแฟนไว้บนตัว

    • นี่ไม่ใช่แบบนั้น
      แต่มันคือการใส่องค์ประกอบพื้นฐานที่เฟรมเวิร์กส่วนใหญ่เริ่มใช้ร่วมกันลงใน standard library
      Promises เองก็ถูกใช้กันอย่างแพร่หลายก่อนจะเข้าไปอยู่ใน standard library และนี่ก็คล้ายกัน