2 คะแนน โดย GN⁺ 2024-05-27 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Hurl เป็นภาษาโปรแกรมเชิงทดลองที่สร้าง โฟลว์การควบคุมด้วยการจัดการข้อยกเว้นเท่านั้น แทนการใช้การแตกแขนงหรือการวนซ้ำแบบทั่วไป
  • แนวคิดนี้เริ่มจากบทสนทนาระหว่าง Nicole Tietz-Sokolskaya กับเพื่อน ๆ ที่ Recurse Center และเว็บไซต์มีทั้งเอกสารการใช้งาน ตัวอย่าง คำแนะนำการดีบัก และคำถามคำตอบ
  • ข้อความแนะนำบนหน้าแนะนำใช้โทน ล้อเล่นอย่างจงใจ อย่างเช่น “monstrosity is beautiful” และ “Certified unhinged™”
  • แม้ Hurl และซอร์สโค้ดของเว็บไซต์จะเปิดเผยสู่สาธารณะ แต่หากจะส่งแพตช์ทางอีเมลจะต้องมีการ โอนสิทธิ์ในแพตช์
  • ไลเซนส์มีให้เลือกแบบ AGPL-3.0, GAL-1.0 และไลเซนส์เชิงพาณิชย์

การทดลองภาษาที่เหลือไว้เพียงการจัดการข้อยกเว้น

  • Hurl ถูกสร้างขึ้นเพื่อเป้าหมายเดียวคือสำรวจว่า ภาษาที่ประกอบขึ้นจาก โฟลว์การควบคุมแบบอิงการจัดการข้อยกเว้น เพียงอย่างเดียวจะเป็นไปได้หรือไม่
  • ไอเดียนี้มาจากบทสนทนาระหว่าง Nicole Tietz-Sokolskaya กับเพื่อน ๆ ที่ Recurse Center โดยไม่ได้เปิดเผยตัวตนของเพื่อนเหล่านั้น “เพื่อรักษาเกียรติ”
  • เว็บไซต์มีเอกสารการใช้งาน Hurl ตัวอย่าง และ คำแนะนำการดีบัก รวมถึงคำถามคำตอบ

ดูเหมือนเรื่องล้อเล่น แต่เป็นโปรเจ็กต์ที่เผยแพร่จริง

  • หน้าแนะนำมีคำรับรองที่สะท้อนคาแรกเตอร์ของ Hurl
    • “This monstrosity is beautiful, and I must never touch it”
    • “Certified unhinged™!”
    • “is "🤮" an available quote?”
  • หากต้องการส่งคำรับรองเพิ่มเติม ต้องส่งอีเมลถึง Nicole และต้องยินยอมอย่างชัดแจ้งให้นำคำพูดนั้นไปอ้างอิงได้

ซอร์สโค้ดและเงื่อนไขไลเซนส์

  • ซอร์สโค้ดของภาษา Hurl และเว็บไซต์นี้เผยแพร่อยู่ที่ Hurl's repo
  • หากพบบั๊กหรือข้อผิดพลาด สามารถส่งแพตช์ทางอีเมลได้ แต่ต้องโอนสิทธิ์ทั้งหมดในแพตช์นั้น
    • เป็นเงื่อนไขเพื่อคงความสามารถในการรีไลเซนส์และการออก ไลเซนส์เชิงพาณิชย์
  • โปรเจ็กต์นี้สามารถใช้งานได้ภายใต้หนึ่งในสามไลเซนส์ต่อไปนี้
    • AGPL-3.0

      • GAL-1.0: Gay Agenda License
      • ไลเซนส์เชิงพาณิชย์
      • ระหว่างกระบวนการพิจารณาไลเซนส์ มีการพิจารณาทั้ง joke licenses และ unfortunate licenses ด้วย แต่สุดท้ายเลือกใช้สามไลเซนส์ข้างต้น

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

 
GN⁺ 2024-05-27
ความคิดเห็นจาก Hacker News
  • ถ้าจะออกแบบภาษาการเขียนโปรแกรม ก็น่าจะบังคับ namespace กับ include/import และถ้าเป็นไปได้ก็ควรกัน side effect ระดับ top-level ด้วย
    รับมาแบบ let foo = include "lib/foo.hurl" แล้วค่อยเรียก foo.init() จะทำให้อนุมานได้ง่ายกว่ามาก
    ในทางกลับกัน ถ้าเจอ include "lib/foo.hurl" // side effects แล้วตามด้วย baz(buz) ก็จะเดายากว่าฟังก์ชันกับตัวแปรนั้นเป็นของ standard library หรือถูก include มาจากที่ไหนสักแห่ง

    • ถ้าไม่มี explicit name binding ก็ควรบังคับให้ คำสั่ง import กับ namespace ตรงกัน
      import "foo/bar" ควรทำให้ใช้ foo.* หรือ bar.* ได้ ไม่ใช่อยู่ ๆ มี bazz.* โผล่มา ทำให้นึกถึง Go เลย
    • ไม่ได้บอกว่าไม่เห็นด้วย แต่ IntelliJ ที่ใช้ทำงานอยู่ก็แสดงชัดมากว่า reference ไหน import มาจากที่ใด และก็กระโดดไปได้ด้วยคีย์ลัด
      VSCode ก็ทำคล้ายกันได้ผ่านปลั๊กอินกับ LSP แต่ด้อยกว่ามาก ผมทำงานด้วย VSCode ไม่ไหวเพราะการไล่ดูโค้ดช้าเกินไป
      เลยสงสัยว่าข้อเสนอแบบนี้จะมีประโยชน์แค่ตอนที่ไม่มีเครื่องมือพวกนั้นหรือเปล่า อย่างน้อยในงานระดับโปรดูเหมือนจะอยู่ไม่ได้ถ้าไม่มีมัน
    • แบบนี้จะส่ง พารามิเตอร์ ให้ foo.init() ได้ด้วย ซึ่ง import แบบเปลือย ๆ ทำไม่ได้
    • ถ้าภาษาใช้ข้อยกเว้นเป็นศูนย์กลางของการควบคุมลำดับการทำงาน ก็ดูเหมือนว่าเรือที่ชื่อว่า “อนุมานได้ง่าย” น่าจะออกจากฝั่งไปแล้ว
      ไม่ได้หมายความว่าโปรเจกต์นี้ไม่มีคุณค่า ตรงกันข้าม ผมมองว่ามันเป็นงานศิลปะด้วยซ้ำ
    • เห็นด้วย 100%
      เคย fork Ruby แล้วแก้ให้ require ไม่ไปเขียนทับ symbol table แต่สุดท้ายก็หมดความสนใจใน Ruby ไปเลย เพราะ ecosystem ของ Ruby ดูจะพึ่งพา สถานะโกลบอลที่แก้ไขได้ร่วมกัน มากเกินไป
  • ผมไม่ค่อยชอบข้อยกเว้นมาตลอด เพราะมันทำให้เข้าใจ สัญญา ระหว่างผู้เรียกกับสิ่งที่ถูกเรียกได้ยากขึ้น และยังเพิ่มความผูกพันของโค้ดด้วย
    ผมชอบแนวทางที่จัดการผ่านค่าที่คืนกลับมาแบบ Go หรือ Rust มากกว่า พอมองผ่าน ๆ ภาษานี้แล้วก็ยังไม่แน่ใจว่ามีอะไรที่แก้ปัญหานี้หรือไม่
    ถ้า IDE สามารถบอกแบบไดนามิกได้ว่าฟังก์ชันไหนมีข้อยกเว้นที่ยังไม่ถูกจับทั้งหมด และพาไปยังจุดที่อาจมีการโยนข้อยกเว้นได้ โมเดลแบบนี้ก็อาจโอเคได้ แต่ก็ยังไม่รู้ว่าจะจัดการเรื่อง coupling ยังไง และกราฟการควบคุมลำดับการทำงานก็น่าจะไม่เสถียรสุด ๆ

    • ใน Java IntelliJ ทำแบบนั้นได้เลย ถ้ามีฟังก์ชันที่โยนข้อยกเว้น แล้วมี caller ที่ไหนสักแห่งในโปรเจกต์ที่ไม่จับมันไว้ ก็จะแสดงเป็นปัญหาและสามารถกระโดดไปดู implementation หรือจุดเรียกใช้ได้ง่าย
      แต่ใน Java ถ้าไม่สืบทอดจาก RuntimeException ข้อยกเว้นที่ฟังก์ชันอาจโยนได้จะถือเป็นส่วนหนึ่งของ signature ของฟังก์ชัน ถ้าโยนโดยไม่เพิ่มไว้ใน signature ก็จะคอมไพล์ไม่ผ่าน
      ข้อกำหนดนี้ของ Java ทำให้ IDE รายงานข้อยกเว้นที่ไม่ถูกจับได้ง่ายขึ้นมาก แต่ถ้าไม่ใช่ runtime exception ก็เป็นปัญหาที่แก้ได้ด้วย static analysis
      ในทางกลับกัน วิธีคืนค่าแบบ wrapper มาตรฐานอย่าง Ok/Err ดูจะง่ายกว่าทั้งในแง่การรองรับของเครื่องมือและความสะดวกของนักพัฒนา
    • การโยนข้อยกเว้นกับการคืนข้อยกเว้นออกมาเป็นตัวแปร แทบจะไม่มีความต่างกันเลยตามตัวอักษร
      ความต่างมีแค่ว่าในแบบส่งต่อข้อยกเว้นนั้น compiler จะช่วยเขียน boilerplate ให้ แต่ถ้าคืนค่าเองคุณต้องเขียนมันด้วยมือ
      ผมไม่เข้าใจจริง ๆ ว่าในปี 2024 คนที่ยังมีสมองทำงานปกติจะอยากทำแบบนั้นด้วยมือตัวเองไปทำไม
    • คงจะชอบ Go มากกว่านี้ ถ้ามันไม่กินบริบทของข้อผิดพลาดไปอย่างเป็นธรรมชาติ
      ตอนที่ใช้ ผมหาสาเหตุรากเหง้า (root cause) ได้เจ็บปวดกว่ามากถ้าไม่มีดีบักเกอร์
  • ตัวอย่าง toss เขียนไว้ว่ามัน “ส่วนใหญ่ใช้ส่งหลายค่าออกนอกฟังก์ชัน ไม่ถึงกับจำเป็นแต่ก็น่ารักดี” แต่จริง ๆ แล้วมันไม่ได้ไร้ประโยชน์เลย มันคือการทำ generator ที่ resume ได้
    แน่นอน ถ้าจะทำให้มันไปทำอย่างอื่นนอกจาก resume ทันที ก็น่าสนใจมากทีเดียว แค่จัดโค้ดทั้ง codebase ให้เป็นสแตกด้านใน-ด้านนอกของ toss ก็พอ

    • มันไม่เหมือนกันเป๊ะ ๆ generator ที่ resume ได้สามารถกลับมาทำงานต่อจากจุดใดก็ได้ในภายหลังของโปรแกรม แต่ที่นี่ return ต้องถูก scope ตาม lexical อยู่ฝั่ง handler
      เช่นใน Python คุณจะเรียก next() จากที่ไหนก็ได้
      อันนี้ใกล้เคียงกับการส่ง callback ผ่านช่องทางข้างเคียงมากกว่า toss เป็นตัวเรียก callback นั้น และ return ก็คือคืนค่าจากตรงนั้นตามตัวอักษร
    • นั่นก็เป็นความคิดแรกของผมหลังจากอ่านเหมือนกัน
      ในภาษาอื่นมันจะเป็นฟีเจอร์ที่มีประโยชน์จริง ๆ แต่ที่นี่กลับถูกทำเหมือนเป็นฟีเจอร์น่ารัก ๆ ที่ทิ้งได้ เลยไม่แน่ใจว่านี่เป็นมุกหรือเปล่า
    • มันใกล้กับ การส่งต่อเหตุการณ์ แบบอิงสแตกมากกว่า
    • คล้าย yield ของ C# ไหม?
    • คิดเหมือนกันตั้งแต่แรก ภาษาจิ๋วเท่ ๆ ที่มี generator ก็ไม่เลวเลย
  • Hurl ดูใกล้เคียงกับ ระบบ conditions แบบ Smalltalk หรือ Common Lisp มากพอสมควร
    การคลายสแตกกับการ resume เป็นเพียงสองแบบของ restart ที่เป็นไปได้: https://gigamonkeys.com/book/beyond-exception-handling-condi...

  • แยกจากตัวโปรเจกต์ ผมเชื่ออย่างหนักแน่นว่าโลกจะดีขึ้นถ้ามีอะไรอีกมากมายใช้ นามสกุล .wtf ในโดเมนของมัน

  • ฟังดูเหมือนรูปแบบที่อ่อนกว่าของ algebraic effects แต่การได้เห็นภาษาลักษณะนี้และสิ่งที่ทำได้ด้วยมันก็ยังเจ๋งอยู่ดี

    • ผมไม่เคยเข้าใจ algebraic effects มาก่อน แต่เอกสารของ Hurl กลับเข้าใจได้
      ถ้า algebraic effects โดยพื้นฐานคืออะไรแบบคีย์เวิร์ด toss ของ Hurl ก็สงสัยว่ามันทรงพลังมากกว่า toss ตรงไหน
  • เป็นการทดลองทางความคิดที่น่าสนใจ
    ผมเกลียดข้อยกเว้นมาก และอยากได้ภาษาที่ไม่มีข้อยกเว้น
    ข้อยกเว้นคือ goto แห่งยุคเรา
    เมื่อมี Maybe/Option และ Effect/Result อยู่แล้ว ก็แทบไม่มีเหตุผลให้ต้องโยนข้อยกเว้นแล้วตามคิดในหัวว่ามันจะถูกจัดการที่ไหน
    ผมกังวลนิดหน่อยว่า algebraic effects จะยิ่งได้รับความนิยม และส่งผลไปถึง frontend JS ที่ซับซ้อนเกินพออยู่แล้ว เพราะมันส่งเสริมการโยน “ข้อยกเว้น” เพื่อควบคุมลำดับการทำงาน
    ถ้าทั้งหมดนี้มีเป้าหมายเพื่อเลี่ยง ปัญหาเรื่องสี ของ async/sync สำหรับผมแล้วมันไม่คุ้มเลย

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

    • ใช่ แต่ทางเลือกอีกอย่างคือ ถ้าเรียกฟังก์ชันในตำแหน่งที่คาดหวังค่าอยู่ ก็อาจทำให้ไวยากรณ์ catch ให้อัตโนมัติได้
  • ถ้าเข้าใจไม่ผิด สิ่งที่ถูก hurl สามารถจับได้ แต่สิ่งที่ถูก toss จับไม่ได้ ใช่ไหม? คงต้องใช้เวลาหน่อยกว่าจะชิน
    แล้วก็อดกังวลไม่ได้ว่าต้องเขียน Hurl มากแค่ไหนกว่าคนจะเริ่มเรียกผมว่า tosser

  • Toss ฟังดูเหมือนโครงสร้างภาษาที่น่าสนใจ มันไต่ขึ้นไปตามสแตกเพื่อหาตัวจัดการข้อยกเว้น แล้วก็กลับมายังตำแหน่งเดิมเพื่อทำงานต่อราวกับไม่มีอะไรเกิดขึ้น
    ดูเหมือนว่าจะใช้โครงสร้างนี้ ฉีดพฤติกรรมเพิ่มเติม เข้าไปตอนรันไทม์ได้
    ปกติในโค้ดเชิงวัตถุเราจะทำ dependency injection ผ่าน constructor ของ service แต่ toss ทำสิ่งนั้นได้ผ่าน “toss handler” ใช่ไหม?

    • ไปดู Koka ได้เลย เป็นภาษาจริง ๆ ที่มีระบบ algebraic effects
      https://koka-lang.github.io/koka/doc/book.html#why-handlers
    • คล้ายกับ ระบบ conditions ของ Common Lisp มากทีเดียว
      ผมไม่ได้รู้เยอะนัก แต่รู้ว่ามันช่วยให้ฉีดพฤติกรรมตอนรันไทม์ได้ในลักษณะนี้
    • คล้าย Resume Next ของ VB