3 คะแนน โดย GN⁺ 2025-11-01 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • John Carmack ได้แชร์มุมมองส่วนตัวเกี่ยวกับการใช้ mutable variable
  • เขากล่าวว่า การใช้ Python ทำให้ละเลยหลักการ ‘single assignment’ ไปบ้าง และควรระวังเรื่องนี้กับตัวเอง
  • เขาเน้นว่า ควรหลีกเลี่ยงการกำหนดค่าซ้ำหรืออัปเดตตัวแปร ยกเว้นการคำนวณซ้ำในลูป
  • หากเก็บขั้นตอนการคำนวณระหว่างทางไว้ทั้งหมด จะ ช่วยในการดีบัก และเมื่อย้ายบล็อกโค้ดก็ ช่วยป้องกันปัญหาที่มีการใช้ค่าก่อนหน้าโดยไม่ตั้งใจ
  • เขาอธิบายว่าใน C/C++ การประกาศตัวแปรเกือบทั้งหมดเป็น const ตั้งแต่ตอนกำหนดค่าเริ่มต้นเป็นนิสัยที่ดี
  • ท้ายที่สุด เขาย้ำว่า “อยากให้ mutable เป็นคีย์เวิร์ด” เพื่อให้ความไม่เปลี่ยนแปลงเป็นค่าเริ่มต้น

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

 
GN⁺ 2025-11-01
ความคิดเห็นจาก Hacker News
  • หลังจากใช้ Clojure มา 2 ปี ก็รู้สึกว่ามันยากมากที่จะอธิบายให้คนอื่นเข้าใจถึง ความชัดเจน ที่ความไม่เปลี่ยนแปลงมอบให้
    คนที่คุ้นกับการคิดแบบทำให้เกิดผลผ่านการเปลี่ยนสถานะ มักจะเข้าใจได้ยากหากยังไม่เคยลองด้วยตัวเอง

    • การเปลี่ยนค่าตัวแปรสร้าง การพึ่งพาลำดับ แบบแฝงขึ้นมา
      ตัวอย่างเช่น ถ้าเขียน x = 7; x = x + 3; x = x / 2 ต่อให้สลับลำดับแล้วไม่เกิด error ผลลัพธ์ก็จะเปลี่ยน
      แต่ถ้าใช้ตัวแปรใหม่อย่าง x1, x2 ความผิดพลาดจากลำดับที่ไม่ถูกต้องจะปรากฏออกมาชัดเจน
      สุดท้ายแล้ว single assignment คือวิธี แสดงการพึ่งพาเหล่านี้อย่างชัดแจ้ง
    • ฉันก็เคยมีประสบการณ์คล้ายกันกับ Scheme
      ต่อให้พยายามอธิบายให้เพื่อนร่วมงานที่ไม่เคยใช้ภาษาฟังก์ชันเข้าใจว่าแนวคิดแบบยึดฟังก์ชันเป็นศูนย์กลางนั้น ทดสอบง่ายและสะอาดแค่ไหน ก็ยังไม่ค่อยเห็นภาพกัน
      Python เขียนสไตล์ฟังก์ชันให้อ่านง่ายได้ยาก ส่วน JS กลับรู้สึกว่าทำได้ดีกว่า
      สุดท้ายมีแค่ นักพัฒนาที่ช่างสงสัย เท่านั้นที่มักจะลองภาษาอย่าง Clojure
    • ถ้าใชัข้อมูลแบบ immutable และ pure function เป็นพื้นฐาน ก็สามารถมองฟังก์ชันเป็น black box ได้อย่างสมบูรณ์
      ฟังก์ชันไม่จำเป็นต้องรู้สถานะภายนอก และภายนอกก็ไม่จำเป็นต้องรู้ว่าข้างในฟังก์ชันทำอะไร
      ต่อให้ไม่รู้สถานะทั้งหมดของโปรแกรม ก็ยังทดสอบหรือดีบักฟังก์ชันใดฟังก์ชันหนึ่งแบบอิสระได้
    • การเอา immutability กับ mutability ไปวางเป็นคู่ตรงข้ามแบบเรียบง่ายนั้นเป็นการ หลีกเลี่ยงความซับซ้อน
      มันน่าสนใจที่ชุมชน Haskell สุดท้ายก็พยายามประดิษฐ์ mutability ขึ้นมาใหม่ภายใน type system
      ประเด็นสำคัญคือการ ควบคุม side effect ด้วยต้นทุนที่ต่ำที่สุด
    • Clojure เป็นภาษา ที่ทรงอิทธิพลที่สุด ในบรรดาภาษาที่ฉันเคยเรียน
      Haskell มีอุปสรรคในการเริ่มต้นสูงเพราะ type system ส่วน F# ก็ ประนีประนอมเกินไป จนสุดท้ายกลายเป็นเขียนด้วยไวยากรณ์แบบ C#
      ด้วย homoiconicity และโครงสร้างข้อมูลอันทรงพลังของ Clojure แนวคิดเรื่อง “ทำงานกับค่า” จึงเป็นสิ่งที่ฉันเข้าใจได้ชัดเจนเป็นครั้งแรก
      ถึงจะไม่ใช้ในงานอาชีพ แต่ก็อยากแนะนำอย่างยิ่งสำหรับคนที่ยังไม่มีประสบการณ์กับภาษาฟังก์ชันหรือ Lisp
  • อยากให้ตัวแปรเป็น immutable โดยพื้นฐาน และทุกอย่างเป็น expression
    แต่ในความเป็นจริง ในฐานะนักพัฒนา Clojure ตอนนี้กำลังทุกข์กับ การบุกของ Python

    • ฉันก็เป็นนักพัฒนา Python แต่ใช้ Clojure แค่กับโปรเจกต์ส่วนตัว
      ตอนนี้เลยกำลังทุกข์กับ การบุกของ TypeScript เช่นกัน จึงเข้าใจดี
    • หลังจากเรียน Rust ก็ได้ตระหนักว่า ถึงภาษาไม่ใช่ฟังก์ชันล้วน ทุกอย่างก็ยังเป็น expression ได้
      วิธีนี้มีประโยชน์มากในการ จำกัดขอบเขตของการเปลี่ยนแปลง
    • Clojure เร็วกว่า Python เสมอ อย่างน้อยก็ปลอบใจได้บ้าง
    • คุณก็แค่เป็นคนที่ใช้ Clojure ไม่จำเป็นต้องนิยามตัวเองว่าเป็น “โปรแกรมเมอร์ Clojure”
      ไม่จำเป็นต้องเข้าไปพัวพันกับ สงครามชนเผ่า ระหว่างภาษา
      ในยุคที่เน้นการเพิ่มผลิตภาพ เส้นแบ่งเหล่านี้ไม่มีความหมายแล้ว
      ขอแนะนำบทความ Don’t Call Yourself a Programmer
  • แม้จะพยายามลดการ reassign ตัวแปรให้น้อยที่สุด แต่บางครั้งก็ใช้ variable shadowing
    ชอบแพตเทิร์นอย่าง result = result.process() เพราะกระชับดี

    • อาจเป็นเพราะตัวอย่างมันนามธรรม แต่ในกรณีส่วนใหญ่ก็ตั้ง ชื่อที่ชัดเจน ให้แต่ละขั้นได้
    • แพตเทิร์นแบบนี้อาจก่อให้เกิดบั๊กด้านความปลอดภัยได้
      ตัวอย่างเช่น ถ้า process() เป็น ฟังก์ชันตรวจสอบ ก็อาจทำให้ไม่ชัดเจนว่าค่านั้นผ่านการประมวลผลมา ณ จุดไหนแล้ว
      ดังนั้นควรแยกสถานะให้ชัดด้วยชื่อ
    • ในสไตล์ฟังก์ชัน เรื่องนี้แก้ได้ด้วย การเชนฟังก์ชัน โดยไม่ต้องมีตัวแปรกลาง
      ตัวอย่าง: result = x |> foo |> bar |> baz หรือ (-> x foo bar baz)
    • result.process() นี่มัน result อะไร แล้ว process อะไร?”
      คนที่มาอ่านโค้ดทีหลังย่อมสับสน
    • เป็น result อยู่แล้วแต่ยังจะ process อีก ฟังดู ประหลาดในเชิงตรรกะ
  • คำว่า “variable” เองก็ทำให้ติดใจมาตลอด
    ถ้ามัน immutable แล้วทำไมถึงยังเรียกว่า variable?

    • ตัวแปรอาจไม่เปลี่ยนระหว่างการรันหนึ่งครั้ง แต่ แต่ละครั้งที่เรียกใช้อาจมีค่าต่างกันได้
      ใน Rust จะเปลี่ยนค่าได้ก็ต่อเมื่อต้องระบุ mut อย่างชัดเจน
      ตรงกันข้าม ใน C ถ้าจะสร้างค่าคงที่กลับต้องพึ่ง preprocessor เลยทำให้สับสน
    • อาร์กิวเมนต์ของฟังก์ชันอย่าง x รับค่าต่างกันได้ทุกครั้งที่เรียก จึงเป็น ค่าที่แปรผันได้ ในตัวมันเอง
      ต่อให้ไม่มีการ reassign ก็ยังเรียกว่า variable ได้
    • ในคณิตศาสตร์ variable ก็เป็นสัญลักษณ์ที่ชี้ไปยัง ค่าตามอำเภอใจ ไม่ใช่วัตถุเฉพาะตัว
      การเขียนโปรแกรมเพียงนำแนวคิดนี้มาใช้ตรง ๆ
    • สุดท้าย variable ก็เป็น variable เพราะค่าของมันอาจต่างกันไปในแต่ละการรัน
      ส่วน constant มีค่าเดิมในการรันทุกครั้ง
      ดู Variable (mathematics)
    • ดังนั้น “ตัวแปร” ไม่ได้หมายถึงเปลี่ยนไปตามเวลา แต่หมายถึง ต่างกันไปตามบริบท มากกว่า
  • ถ้า IDE ช่วย แสดงให้เห็นทางสายตา ว่าตัวแปรไหนถูกเปลี่ยนค่าก็น่าจะดี
    เช่น ทำเครื่องหมายเบา ๆ กับตัวแปรที่ถูกแก้ไข

    • ใน IntelliJ ตัวแปรที่ถูก reassign จะมีเส้นใต้ และเมื่อเอาเมาส์ไปชี้จะมีคำใบ้ว่า ‘Reassigned local variable’
      ถ้าใช้ final ให้มากที่สุดเท่าที่ทำได้ โค้ดจะ อ่านง่ายและดูแลง่ายขึ้น
    • แต่คิดว่าการ opt-in แบบชัดเจน น่าจะดีกว่าการอนุมานอัตโนมัติ
      IDE ควรเตือน และอนุญาตให้เปลี่ยนค่าได้เฉพาะเมื่อจำเป็นจริง ๆ
      เหมือนแนวคิด set vs list ของ Rich Hickey ที่ควรเลือกโครงสร้างที่สื่อความหมายให้ชัด
    • Swift มีคอมไพเลอร์ที่ตรวจจับได้ว่าตัวแปรถูกเปลี่ยนค่าหรือไม่ และถ้าไม่จำเป็นก็จะแนะนำให้เปลี่ยนเป็นค่าคงที่
    • IDE ของ JetBrains มีฟีเจอร์ ค้นหาตำแหน่งอ่าน/เขียนตัวแปร อยู่แล้ว
    • ถ้าจะทำ linter สำหรับเรื่องนี้ ชื่อ “mutalator” ก็ดูเหมาะดี
  • เคยทำโปรเจกต์หนึ่งที่บังคับใช้ immutability อย่างเข้มงวดเพื่อ thread safety
    ผลคือโค้ดอ่านง่ายขึ้น และตามรอยได้ง่ายขึ้นว่าอะไรเปลี่ยนได้บ้าง
    ตั้งแต่นั้นมาก็กลายเป็น แฟนพันธุ์แท้ ของ immutability

    • ถ้าอย่างนั้นอยากแนะนำให้ลอง Rust ดูจริง ๆ
  • หลังจากทำงานกับโค้ดเบส Haskell ขนาดใหญ่มา แล้วต้องกลับไปเขียน C อีกครั้ง ก็ยิ่งรู้สึกว่าอยากให้ immutability เป็นค่าพื้นฐาน
    แค่ const ยังไม่พอ

    • ใน C จริง ๆ แล้วสิ่งที่ทำได้มีแค่ การกำหนดค่าใหม่ ไม่ใช่การแก้ไขโดยตรง
      ถ้าจะเปลี่ยนจริง ๆ ต้องใช้ pointer ส่วน C++ สามารถเปลี่ยนอาร์กิวเมนต์ได้แค่จากการเรียกฟังก์ชัน ทำให้ยิ่งไม่โปร่งใส
    • เลยสงสัยว่าใน Rust ค่าเริ่มต้นนั้นให้ immutability ที่ปลอดภัยเพียงพอ หรือไม่
  • เห็นด้วยกับคำพูดที่ว่า “ควรประกาศตัวแปรเกือบทั้งหมดเป็น const”
    Rust สมควรถูกพูดถึงในบริบทนี้

  • ลองจินตนาการถึงไวยากรณ์ที่ให้ immutable เป็นค่าพื้นฐาน และอนุญาต mutable ได้เฉพาะภายในบล็อกหนึ่ง
    เช่นเดียวกับบล็อก with ของ Python

    with mutable(x, items):
        x = 3
        items.append(4)
    

    เมื่อออกจากบล็อกแล้วก็กลับไปเป็น immutable อีกครั้ง

    • ที่จริงนี่ก็คือแนวคิด mutable borrow นั่นเอง
      แค่ดู borrow checker ของ Rust ก็พอเห็นแล้วว่าแนวคิดนี้ซับซ้อนแค่ไหน
    • ถ้าไม่มีการตรวจ borrow ก็อาจมี reference ที่ยังหลงเหลืออยู่ นอกบล็อกและทำให้เกิดการเปลี่ยนแปลงได้
  • มีคำพูดว่า “State is the enemy
    ยิ่งสถานะมากเท่าไร เงื่อนไขที่ต้องทดสอบก็เพิ่มขึ้นแบบทวีคูณ
    immutability คือวิธีหนึ่งในการป้องกัน การระเบิดของสถานะ แบบนี้

    • เพราะอย่างนี้โปรแกรมเมอร์สายฟังก์ชันจึงเชื่อใน การแยก Church ออกจาก state
      Separation of Church and State