3 คะแนน โดย GN⁺ 2025-05-25 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • อัลจีบราอิกเอฟเฟกต์ (effect handlers) คือ เครื่องมือควบคุมการไหลของโปรแกรมที่ยืดหยุ่น ซึ่งช่วยให้สามารถทำฟีเจอร์ของภาษาได้หลากหลายรูปแบบ (เช่น การจัดการข้อยกเว้น, generator, coroutine เป็นต้น) ในระดับไลบรารี
  • สามารถนำไปใช้กับ การจัดการคอนเท็กซ์, การฉีดดีเพนเดนซี, การแทนที่สถานะโกลบอล ฯลฯ ซึ่งพบได้บ่อยในสาย functional programming
  • ช่วยให้ การออกแบบ API เรียบง่ายขึ้น และทำให้การส่งผ่านสถานะ/สภาพแวดล้อมภายในโค้ดเป็นแบบอัตโนมัติ
  • ยังมีข้อดีในด้าน การรับประกันความบริสุทธิ์เชิงฟังก์ชัน, ความสามารถในการเล่นซ้ำ, การตรวจสอบความปลอดภัย เป็นต้น
  • ด้วยความก้าวหน้าของเทคโนโลยีคอมไพเลอร์ในช่วงหลัง ปัญหาด้านประสิทธิภาพก็ดีขึ้นมาก

ภาพรวมของอัลจีบราอิกเอฟเฟกต์ (Algebraic Effects)

อัลจีบราอิกเอฟเฟกต์ (หรือที่เรียกว่า effect handlers) เป็นฟีเจอร์ของภาษาโปรแกรมที่กำลังได้รับความสนใจอย่างมากในช่วงหลัง และเป็นหนึ่งในฟีเจอร์หลักของ Ante รวมถึงภาษาเชิงวิจัยหลายตัว (Koka, Effekt, Eff, Flix เป็นต้น) ที่กำลังขยายตัวอย่างรวดเร็ว แม้จะมีเอกสารจำนวนมากอธิบายแนวคิดของ effect handlers แต่คำอธิบายเชิงลึกว่าเหตุใดจึง "จำเป็น" จริง ๆ ยังมีไม่มาก บทความนี้จึงพยายามแนะนำกรณีใช้งานและข้อดีของอัลจีบราอิกเอฟเฟกต์ให้ครอบคลุมมากที่สุด

ทำความเข้าใจไวยากรณ์และความหมายอย่างรวดเร็ว

  • อัลจีบราอิกเอฟเฟกต์เป็นแนวคิดที่คล้ายกับ "ข้อยกเว้นที่สามารถดำเนินต่อได้"
  • สามารถประกาศฟังก์ชันเอฟเฟกต์ได้ เช่น effect SayMessage
  • สามารถระบุได้ว่าฟังก์ชันอาจใช้เอฟเฟกต์ดังกล่าว เช่น foo () can SayMessage = ...
  • สามารถจัดการได้ด้วย handle foo () | say_message () -> ... คล้ายกับ try/catch ของข้อยกเว้น

ด้วยโครงสร้างพื้นฐานลักษณะนี้ จึงสามารถเรียกใช้และควบคุมเอฟเฟกต์ได้

การขยายการควบคุมการไหลแบบกำหนดเอง

เหตุผลสำคัญที่สุดของอัลจีบราอิกเอฟเฟกต์คือ ฟีเจอร์ภาษาเพียงอย่างเดียวนี้สามารถทำสิ่งที่เดิมต้องอาศัยฟีเจอร์ภาษาหลายชนิดแยกกัน (เช่น generator, exception, coroutine, async) ให้กลายเป็น สิ่งที่สร้างได้ในรูปไลบรารี

  • หากใส่ตัวแปรเอฟเฟกต์แบบพหุสัณฐานให้ฟังก์ชัน (can e) ก็จะสามารถส่งต่อและผสมเอฟเฟกต์หลายแบบผ่านอาร์กิวเมนต์ของฟังก์ชันได้
  • ตัวอย่างเช่น ฟังก์ชัน map สามารถประกาศให้ฟังก์ชันที่รับเข้ามาใช้เอฟเฟกต์ใด ๆ e ได้ ทำให้ผสานกับเอฟเฟกต์หลากหลายรูปแบบ (การแสดงผล, async ฯลฯ) ได้อย่างเป็นธรรมชาติ

ตัวอย่างการทำ exception และ generator

  • การทำ exception: หากเกิดเอฟเฟกต์ขึ้นแล้วจัดการโดยไม่เรียก resume ก็จะทำงานเหมือนข้อยกเว้น
  • การทำ generator: นิยามเอฟเฟกต์ Yield แล้วเมื่อมีการ yield ค่าแต่ละครั้ง แฮนด์เลอร์ภายนอกจะเข้ามาแทรกเพื่อควบคุมโฟลว์ตามเงื่อนไขได้ ทำให้เขียนแพตเทิร์นขั้นสูงอย่างการกรองข้อมูลได้ด้วยโค้ดในระดับที่เรียบง่าย

ความสามารถในการผสมเอฟเฟกต์หลายชนิดเข้าด้วยกันก็เป็นข้อได้เปรียบสำคัญเมื่อเทียบกับเทคนิค abstraction ของเอฟเฟกต์แบบเดิม

การใช้งานในฐานะชั้น abstraction

อัลจีบราอิกเอฟเฟกต์ไม่ได้มีประโยชน์แค่การขยายความสามารถแกนหลักของการเขียนโปรแกรมเท่านั้น แต่ยังใช้งานได้ดีมากในสถานการณ์ของแอปพลิเคชันธุรกิจที่หลากหลาย

การฉีดดีเพนเดนซี (Dependency Injection)

  • สามารถทำ abstraction ของออบเจ็กต์ที่ต้องพึ่งพา เช่น ฐานข้อมูลหรือการแสดงผล ให้อยู่ในรูปเอฟเฟกต์และ จัดการผ่านแฮนด์เลอร์ได้
  • จึงสามารถสลับเป็น mock สำหรับการทดสอบ หรือทำ output redirection ได้อย่างยืดหยุ่น

การจัดการล็อกหรือการแสดงผลแบบมีเงื่อนไข

  • สามารถควบคุมจากศูนย์กลางได้ว่าจะให้แสดงข้อความล็อกหรือไม่ตามระดับ logging

ทำให้การออกแบบ API เรียบง่ายขึ้นและส่งผ่านคอนเท็กซ์โดยอัตโนมัติ

การใช้ state effect

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

การแทนที่ออบเจ็กต์โกลบอล

  • แม้แต่สถานะที่เดิมจัดการผ่านออบเจ็กต์โกลบอล เช่น ตัวสร้างเลขสุ่มหรือการจัดสรรหน่วยความจำ ก็สามารถทำ abstraction เป็น effect ได้ ซึ่งได้เปรียบในด้านความชัดเจนของโค้ด ความสะดวกในการทดสอบ และการรองรับ concurrency
  • เพียงแค่สลับแฮนด์เลอร์ ก็สามารถเปลี่ยนแหล่งที่มาของเลขสุ่มจริงได้อย่างยืดหยุ่น

รองรับการเขียนแบบ direct style

  • เดิมทีต้องรับมือกับการซ้อนกันของ option type, การห่อข้อผิดพลาด และโครงสร้างอื่น ๆ อีกหลายชั้น
  • เอฟเฟกต์ช่วยให้สามารถแสดงเส้นทางของข้อผิดพลาดหรือผลข้างเคียงได้อย่างสะอาด โดยไม่ต้องพึ่งการห่อแบบดังกล่าว

การรับประกันความบริสุทธิ์และการตรวจสอบความปลอดภัย

การระบุผลข้างเคียงอย่างชัดเจน

  • ในภาษาที่รองรับ effect handlers ส่วนใหญ่ ฟังก์ชันที่ก่อให้เกิดผลข้างเคียงจะต้องระบุเอฟเฟกต์อย่าง can IO, can Print เป็นต้น ไว้ใน type signature
  • ในกรณีอย่างการสร้างเธรดหรือ software transactional memory (STM) จำเป็นต้องใช้ฟังก์ชันที่บริสุทธิ์เท่านั้น

การ replay ล็อกและเครือข่ายเชิงกำหนดได้

  • เมื่ออาศัยความบริสุทธิ์ ก็สามารถสร้างแฮนด์เลอร์อย่าง record, replay เพื่อทำให้ผลการรันสามารถทำซ้ำได้
  • จึงรองรับผลลัพธ์แบบกำหนดได้และการ rollback สำหรับ การดีบัก, ฐานข้อมูล, เครือข่ายเกม เป็นต้น

รองรับความปลอดภัยแบบ Capability-based

  • เอฟเฟกต์ทั้งหมดที่ยังไม่ได้ถูกจัดการจะปรากฏใน type signature ของฟังก์ชัน จึงมีประโยชน์มากเมื่อตรวจสอบความปลอดภัยของไลบรารีภายนอก
  • หากฟังก์ชันที่เดิมไม่มีผลข้างเคียงถูกอัปเดตให้มี can IO เพิ่มเข้ามา โค้ดที่เรียกใช้ก็จะตรวจพบได้ทันที

อย่างไรก็ตาม เนื่องจากเอฟเฟกต์ทั้งหมดสามารถแพร่กระจายต่อไปได้โดยอัตโนมัติ จึงมีโอกาสเกิดผลข้างเคียงจากการที่เอฟเฟกต์ถูกจัดการโดยไม่ตั้งใจได้เช่นกัน

มุมมองด้านประสิทธิภาพและบทสรุป

  • ในอดีต ปัญหาด้านประสิทธิภาพเคยเป็นจุดอ่อน แต่ปัจจุบันในหลายกรณี เช่น tail-resumptive effects ได้มี ความก้าวหน้าอย่างมากในด้านการปรับให้เหมาะสม
  • แต่ละภาษายังสามารถใช้กลยุทธ์การคอมไพล์ที่เหมาะสมของตนเองได้ เช่น closure call, evidence passing, การทำ handler specialization เป็นต้น

คาดกันว่าอัลจีบราอิกเอฟเฟกต์จะมีบทบาทที่สำคัญยิ่งขึ้นมากในภาษาโปรแกรมแห่งอนาคต

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

 
GN⁺ 2025-05-25
ความคิดเห็นใน Hacker News
  • ผมมองว่ามีข้อเสียอยู่สองอย่าง
    อย่างแรกคือเมื่อดูโค้ดชิ้นที่ให้มา ไม่มีอะไรบอกเลยว่า foo หรือ bar อาจล้มเหลวได้
    ถ้าจะรู้ว่าการเรียกแบบนี้อาจทำให้ตัวจัดการข้อผิดพลาดทำงาน ก็ต้องไปเปิดดู type signature เอง และบางกรณีก็ต้องทำด้วยมือหากไม่มี IDE ช่วย
    อย่างที่สองคือ หลังจากรู้แล้วว่า foo กับ bar อาจล้มเหลว ถ้าจะหาว่าตอนล้มเหลวจริง ๆ โค้ดส่วนไหนจะถูกรัน ก็ต้องไล่ขึ้นไปตาม call stack ไกลพอสมควรเพื่อหา with expression แล้วจากนั้นค่อยไล่ลงมาตาม handler นั้น
    มันตามพฤติกรรมนี้แบบสแตติกหรือกดกระโดดไปยังนิยามใน IDE ตรง ๆ ไม่ได้ เพราะ my_function อาจถูกเรียกจากหลายที่ด้วย handler ที่ต่างกัน
    ผมคิดว่าแนวคิดนี้สดใหม่มาก แต่สุดท้ายก็ยังมีความกังวลในแง่ความอ่านง่ายของโค้ดและการดีบัก

    • สำหรับประเด็นเรื่องการหาว่าโค้ดไหนทำงานเมื่อการรันล้มเหลว มีคำอธิบายว่านี่แหละคือหัวใจของ dynamic code injection
      มันเป็นโครงสร้างที่ผูก binding ตาม call stack เช่นเดียวกับความสามารถเชิงพลวัตหลายแบบอย่าง shallow-binding, deep-binding เป็นต้น
      ที่ทำ static analysis หรือกระโดดใน IDE ไม่ได้ก็เพราะคุณสมบัติความเป็น dynamic นี่เอง
      แต่ก็คิดว่าในกระบวนการนี้จริง ๆ แล้วไม่ได้มีอะไรที่ต้องกังวลมากนัก
      เพราะมันเป็นการเพิ่ม effect ให้กับ pure code ทำให้สามารถนำไปต่อเข้ากับบริบทต่าง ๆ ได้ ทั้ง pure effect, impure effect, mock สำหรับทดสอบ หรือ production environment
      หลักการคล้ายกับ dependency injection
      ใน monad แบบดั้งเดิมก็ทำคล้ายกันได้ แต่ถ้าจะหาว่า monad ถูก instantiate ที่ไหนจริง ๆ ก็ต้องย้อนดู call stack เหมือนกัน
      เทคโนโลยีแบบนี้มีข้อดีที่ให้มา แต่ก็มีต้นทุนที่ชัดเจนเช่นกัน
      เหมาะกับงานอย่างการทดสอบและ sandboxing แต่ก็มีคุณสมบัติที่ทำให้สิ่งที่เกิดขึ้นในโค้ดไม่ได้มองเห็นชัดนัก

    • มีคนแชร์ว่าตัวเองเคยเขียนวิทยานิพนธ์ระดับปริญญาตรีเกี่ยวกับการรองรับ lexical effects และ handlers ใน IDE
      และคิดว่าทุกประเด็นที่ชี้ไว้ข้างต้นสามารถทำให้เกิดขึ้นได้จริง
      ลิงก์วิทยานิพนธ์

    • มีการพูดถึงแนวโน้มการใช้อินเทอร์เฟซมากเกินไปบน .NET ซึ่งทำให้การกระโดดไปยัง implementation ของเมธอดต้องผ่านหลายขั้นตอนจนยุ่งยาก
      บ่อยครั้งถ้า implementation อยู่ใน assembly อื่น ความสามารถของ IDE ก็แทบใช้ไม่ได้
      ใน Dependency Injection ขั้นสูง (เช่น Autofac) ยังมีการสร้าง scope แบบลำดับชั้นคล้ายตัวแปร dynamic scope ใน LISP เพื่อกำหนดตอนรันไทม์ว่าบริการจะถูก bind กับ instance ไหน
      ในแง่นี้ effect ก็อาจแทนได้ด้วยการ inject instance ของอินเทอร์เฟซอย่าง ISomeEffectHandler และเมื่อเกิด effect ก็แทนด้วยการเรียกเมธอดนั้น
      พฤติกรรมจริงของ handler (เช่น throw exception, logging ฯลฯ) จะถูกกำหนดแบบ dynamic ตามการตั้งค่า DI
      เดิมทีเคยใช้แพตเทิร์นโยน exception แต่ก็สามารถเปลี่ยนมาเป็นดีไซน์ที่ประกาศ effect ผ่านอินเทอร์เฟซและปล่อยให้ DI ตัดสินวิธีจัดการทั้งหมดได้
      ส่วนเรื่องที่เกี่ยวกับ iterator อย่าง yield ยังไม่ได้เจาะลึก

    • คิดว่าจุดสำคัญคือส่วนที่ไม่ได้แสดงว่า foo กับ bar อาจล้มเหลวได้
      ทำให้สามารถเขียนโค้ดใน direct style ได้โดยไม่ต้องสนใจบริบทเชิง effect
      การตามหาโค้ดที่จะทำงานเมื่อเกิดความล้มเหลวก็เป็นธรรมชาติของ abstraction
      ว่าตอนรันจริง effect handler ตัวไหนจะถูกผูกเข้ามานั้นเป็นสิ่งที่ตัดสินทีหลัง
      หลักการคล้ายกับใน f : g:(A -> B) -> t(A) -> B ที่เราไม่อาจรู้ล่วงหน้าได้ว่าเมื่อ g ทำงานจริงจะมีโค้ดใดถูกรัน

    • ไม่เห็นด้วยกับข้ออ้างที่ว่าการไล่ขึ้นตาม call stack เพื่อหา handler นั้นทำ static analysis ไม่ได้
      ความเห็นนี้บอกว่าในทางปฏิบัติทำ static analysis ได้ และใช้ความสามารถอย่าง "ไปยัง caller" ใน IDE เพื่อเลือกดูได้ว่า handler ไหนถูกใช้งาน

  • "pseudocode" ของ Ante น่าประทับใจมาก
    ให้ความรู้สึกเหมือนเอาคุณสมบัติของ Haskell มาผสมกับพลังการแสดงออกและความใช้งานจริงของ Elixir ได้อย่างลงตัว
    เหมือน Haskell สำหรับนักพัฒนา
    หวังว่าคอมไพเลอร์จะพัฒนาเต็มที่
    และอยากลองพัฒนาแอปด้วย Ante

  • ต่อประเด็นที่ว่า AE (Algebraic Effects) เป็นการทำให้ control flow เป็นนามธรรมทั่วไปจนสามารถใช้สร้าง coroutine ได้
    มีคนมองว่าวิธีที่ง่ายที่สุดในการทำ AE ในภาษาใหม่จริง ๆ ก็คือใช้ coroutine แล้วใส่ไวยากรณ์ของ effect ครอบบนโครงพื้นฐาน yield/resume
    จึงถามว่าตัวเองกำลังพลาดอะไรไปหรือไม่

    • จุดต่างสำคัญของ AE เมื่อเทียบกับ coroutine คือ type safety
      ใน AE สามารถระบุได้ว่าฟังก์ชันหนึ่งใช้ effect อะไรได้บ้างจากในซอร์สโค้ด
      ตัวอย่างเช่น ถ้าเป็น query_db(): User can Database ก็แปลว่าสามารถเข้าถึงฐานข้อมูลได้ และเวลาถูกเรียกต้องมี Database handler มาให้เสมอ
      โครงสร้างแบบนี้ทำให้ข้อจำกัดว่าอะไรทำได้หรือทำไม่ได้ชัดเจนมาก
      คล้ายกับที่ใน NextJS server component ใช้ความสามารถฝั่ง client โดยตรงไม่ได้ ความปลอดภัยเชิงข้อจำกัดแบบนี้จึงเป็นที่นิยมในหลายด้าน

    • Effect-TS เข้าใกล้วิธีนี้บน JavaScript (การใช้ coroutine) แต่สุดท้ายก็ไม่แน่ใจว่านี่เป็นไอเดียที่ดีจริงหรือไม่
      มีความกังวลว่าเหมือนกับ DI ของ Spring ที่ AE อาจแพร่กระจายไปทั่วทั้งโค้ดเบสและสุดท้ายสร้างแต่ความซับซ้อน
      ถึงขั้นวิจารณ์ว่างานพูดที่ EffectDays ซึ่งแนะนำวิธีใช้ effect บนฝั่ง frontend ส่วนใหญ่เต็มไปด้วย boilerplate ที่แทบไม่มีความหมาย
      แม้ AE จะเป็นแนวคิดที่ชวนหลงใหล แต่ภาระที่ต้องห่อหลายอย่างให้เป็นฟังก์ชันอาจบั่นทอนความเขียนโค้ดง่ายแบบเฉพาะของ JS
      ในทางกลับกัน แนวทางอย่าง motioncanvas ที่ใช้แค่ coroutine ก็มีจุดแข็งมากในการอธิบายสถานการณ์กราฟิก 2D ที่ซับซ้อนได้อย่างง่ายดาย
      วิดีโอที่เกี่ยวข้อง EffectDays
      MotionCanvas

    • มีข้ออ้างว่าในเธรดเดียวกัน AE handler สามารถ resume โค้ดได้หลายครั้งเหมือน call/cc
      ในขณะที่ coroutine จะ resume ได้เพียงครั้งเดียวต่อการ yield หนึ่งครั้ง
      แต่เพราะการไหลของการทำงานที่ไม่แน่นอนแบบนี้กลับทำให้คาดเดายากขึ้น จึงมีคนบอกว่าตัวเองชอบวิธีคืนฟังก์ชันที่เรียกได้หลายครั้งแบบ explicit หรือแทนด้วยโครงสร้างอื่นอย่าง iterator มากกว่า

  • มีมุมมองว่าแนวคิดนี้น่าดึงดูดมากในฐานะ abstraction สำหรับการเขียนโค้ด
    ตอนทำ kernel programming ที่ Sun รู้สึกว่ามันมีข้อดีมากเพราะสามารถเขียนโค้ดได้กระชับเมื่อเรียกอะไรอย่าง sleep(foo) แล้วถูก foo ปลุกให้ตื่นกลับมาทำงานต่อ
    ทำให้ภาระในการจัดการ edge case ต่าง ๆ ด้วย control flow ทีละอย่างลดลง
    ถ้าระวังประเด็นเรื่อง memory locality ให้ดี ก็ดูเหมือนจะสนุกกับการ initialze หลายฟังก์ชันไว้ล่วงหน้าในสถานะรอ และแสดงอัลกอริทึมเป็นการเปลี่ยนแปลงของแต่ละยูนิตได้โดยตรง

  • ต่อคำกล่าวว่า "algebraic effects คือ exception ที่ resume ได้"
    มีคำถามว่ามันต่างจาก type class อย่าง ApplicativeError หรือ MonadError จริง ๆ ตรงไหน
    วิธีประกาศว่าในฟังก์ชันใช้ effect อะไรได้บ้างก็ดูคล้าย checked exceptions และการจัดการ effect ผ่าน handle expression ก็แทบเหมือน try/catch
    type class พวกนี้ก็รองรับการจับ exception ผ่าน handleError/handleErrorWith อยู่แล้ว
    แม้จะบอกว่า algebraic effects มีข้อดีที่จะนำไปใช้กับภาษาของ "อนาคต" แต่ในความจริงมันก็ดูเป็นแนวคิดที่ใช้งานกันได้อยู่แล้วในปัจจุบัน
    ลิงก์อธิบาย cats

    • ถ้าจัดการแค่ effect เดียว ความแตกต่างอาจไม่มากนัก แต่เมื่อจำเป็นต้องใช้หลาย effect พร้อมกัน การรองรับ effect โดยตรงจะสะอาดและเข้าใจง่ายกว่าการซ้อน monad แบบ explicit มาก
      เมื่อรวม monad เข้าด้วยกัน มักมีปัญหาปวดหัวเรื่องการกำหนดลำดับ หรือการสลับลำดับเมื่อผลลัพธ์ของบางฟังก์ชันไม่ตรงกับชุด monad ที่คาดไว้

    • โดยส่วนตัวคิดว่า monad กับ effect ไม่ได้เป็นคู่แข่งกัน แต่ควรมองเป็นกรอบการตีความที่เสริมกันมากกว่า
      ดูบทความที่เกี่ยวข้องได้ เช่น งานเขียนเกี่ยวกับ Koka

    • algebraic effects ทำงานบน program stack คล้าย delimited continuation
      ด้วย monad trick ธรรมดาเพียงอย่างเดียว จะทำสิ่งอย่างกระโดดขึ้นไปยัง effect handler ที่อยู่สูงกว่า 5 stack frame ทันที แล้วแก้เฉพาะ local variable ใน frame นั้น ก่อนย้อนกลับลงมา 5 ชั้นไม่ได้

    • ความต่างคือพฤติกรรมแบบ static เทียบกับ dynamic
      เวลาทำโปรแกรมด้วย monad ต้อง implement เมธอดที่เกี่ยวข้องทั้งหมดด้วยตัวเอง แต่ใน effect system เราสามารถติดตั้ง effect handler แบบ dynamic ได้ทุกเมื่อ และ override handler เดิมได้อย่างยืดหยุ่น
      ตัวอย่างเช่น อาจใช้ monad เฉพาะที่มีลักษณะ IO สำหรับการทดสอบในระดับล่าง และติดตั้ง effect handler เพิ่มเติมเฉพาะข้างใต้จุดนั้นก็ได้

    • แม้จะคล้ายกันมาก แต่ต่างกันที่ความใช้งานสะดวก
      algebraic effects มีโครงสร้างคล้าย free monad แต่เป็นสิ่งที่มีมาให้ในภาษา จึงเขียนไวยากรณ์ได้ง่ายกว่าและทำ composability ได้ดีกว่า
      ในภาษาแนว monad อย่าง Haskell ก็สามารถให้ผลคล้ายกันได้บ้างผ่านการอนุมาน type class (mtl style) และ bind ที่ฝังมาในไวยากรณ์

  • เดิมทีเข้าใจผิดว่า algebraic effects มีเฉพาะใน static type system เท่านั้น แต่ไม่นานมานี้เพิ่งรู้ว่ายังมีโครงสร้างแบบ dynamic ด้วย
    บทความสองชิ้นเกี่ยวกับเวอร์ชัน dynamic ของ Eff ที่เคยอ่าน (ชิ้นแรก, ชิ้นที่สอง) สร้างความประทับใจมาก
    แนวคิดอย่าง "parameterized operations with generalized arity" ก็รู้สึกว่าน่าสนใจมากเมื่อเชื่อม abstraction เข้ากับการเขียนโปรแกรม

    • อยากรู้ว่าคุณไม่ชอบอะไรใน static type system
  • มีการพูดถึงว่าแนวคิดเก่าแก่กำลังกลับมาอีกครั้งในชื่อและกรอบใหม่
    แนะนำ LISP Condition System
    ลองสัมผัส Algebraic Effects

  • เคยลองทำ protohackers ด้วย effects บน OCaml 5 alpha
    สนุกดี แต่ตอนนั้น toolchain ค่อนข้างไม่สะดวก
    Ante ให้ความรู้สึกคล้ายกัน จึงคาดหวังกับพัฒนาการต่อจากนี้

    • ตั้งแต่ OCaml 5.3 เป็นต้นมา effects ถูกปรับปรุงดีขึ้นมากเมื่อเทียบกับก่อนหน้า
      แม้ยังไม่มี type system มารองรับ แต่ตอนนี้ดูสะอาดลงอย่างชัดเจน
  • ใช้เวลาอยู่กับ Prolog มามาก และกำลังมองหาภาษาที่ช่วยให้ compose ฟังก์ชันแบบ non-deterministic และทำ compile-time type check ได้ง่าย
    Ante จึงเป็นหนึ่งในตัวเลือกที่สนใจ
    อีกคอมเมนต์ยังเสริมว่าอย่าลืมเครื่องมือสำหรับนักพัฒนาและปลั๊กอินเอดิเตอร์อย่าง LSP หรือ tree-sitter ด้วย

    • ในฐานะผู้เขียน Ante ขอตอบว่าตอนนี้มีการรองรับ LSP แล้ว (แม้จะยังพื้นฐานมาก)
      และคิดว่าเครื่องมือเป็นสิ่งจำเป็นตั้งแต่ช่วงต้นของภาษาใหม่
      เนื่องจากให้ความสำคัญกับประสบการณ์ดีบักด้วย จึงกำลังพิจารณาว่าจะใส่ความสามารถ replayability เป็นค่าเริ่มต้นอย่างน้อยใน debug mode ได้หรือไม่
  • ต่อคำกล่าวว่า "algebraic effects คือ exception ที่ resume ได้"
    มีคนถามว่ามันคล้ายกับ Common Lisp conditions ไหม
    และรู้สึกว่าน่าสนใจที่แนวคิดเก่ากลับมาปรากฏใหม่เพียงแค่เปลี่ยนชื่อ

    • algebraic effects ครอบคลุมกว่าระบบ condition ของ LISP มาก
      continuations มีลักษณะ multi-shot ได้ ซึ่งคล้าย call/cc ของ Scheme
      และยังมีการพูดด้วยว่าความขนานในลักษณะนี้อาจทำให้เกิดผลลัพธ์ที่แย่กว่าการไม่มีมันเสียอีก

    • ใน Smalltalk มีสิ่งที่เรียกว่า "resumable exceptions"

    • ถ้ามอง effect แค่ว่าเป็นการเปลี่ยนชื่อจาก condition system แบบเก่า ก็จะคุยกันต่อได้ยาก
      algebraic effects ที่กำลังพูดถึงกันอยู่ตอนนี้มีความแตกต่างมากกว่าระดับแนวคิดพื้นฐานอย่างชัดเจน

    • Dependency Injection เองก็อาจถูกพูดถึงได้ในบริบทคล้ายกัน