2 คะแนน โดย GN⁺ 2023-08-04 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ระหว่างเตรียมโปรเจ็กต์เว็บเล็กๆ ด้วย Phoenix ก็พบว่า Elixir ไม่ใช่แค่ Erlang สไตล์ Ruby แบบเรียบง่าย แต่เมื่อมองว่าเป็น ภาษาสาย Lisp บน BEAM การออกแบบหลายอย่างกลับลงตัวมาก
  • บนพื้นฐานของการส่งข้อความ สถานะไม่เปลี่ยนแปลง การกู้คืนจากความล้มเหลว และการตรวจสอบขณะรันไทม์ของ Erlang/OTP นั้น Elixir เพิ่ม โมดูลซ้อนกัน สตริงที่ดีกว่า standard library ที่ใหญ่กว่า และระบบมาโครที่ทรงพลังเข้าไป
  • โครงสร้างอย่าง scope, pipeline, def, defmodule, if ดูเหมือนเป็นไวยากรณ์พิเศษ แต่จริงๆ แล้วเป็นผลจากการซ้อนกันของรูปแบบการเรียกใช้ association list, syntactic sugar แบบ do...end และการขยายมาโคร
  • Router DSL ของ Phoenix ไม่ใช่ข้อยกเว้นที่ฝังมากับภาษา แต่ประกอบขึ้นจาก การสร้างโค้ดด้วยมาโคร อย่าง scope, pipeline, plug, pipe_through
  • “เวทมนตร์” ของ Elixir ใกล้เคียงกับ metaprogramming ที่เปิดเผยมากกว่าพฤติกรรมที่ซ่อนไว้ โดย use, sigil, dbg!, pry, |> ถูกผสานเข้ากับเครื่องมือด้านการตรวจสอบ การกู้คืน และ concurrency ของ BEAM

ทำไมต้องอ่าน Elixir ก่อน Phoenix

  • เป้าหมายคือใช้ Phoenix web framework กับโปรเจ็กต์เล็กๆ แต่เพราะ Phoenix เขียนด้วย Elixir จึงต้องเข้าใจตัวภาษาเสียก่อน
  • บทความนี้ไม่ใช่ tutorial ของ Elixir หรือ Phoenix แต่ทำหน้าที่เสริม เอกสารทางการ เพื่อช่วยตั้งกรอบความคิดว่า “ควรคิดกับมันอย่างไร”
  • สภาพแวดล้อมอ้างอิงคือ Elixir 1.14 ณ เดือนพฤษภาคม 2024, Erlang/OTP 25, และ Phoenix 1.7.7
  • จุดตั้งต้นของผู้เขียนคือมีประสบการณ์กับ Erlang และเว็บโปรแกรมมิงไม่มากนัก ไม่ค่อยชอบเว็บโปรแกรมมิง และมีท่าทีมองโลกในแง่ร้ายแบบคนที่คอยซ่อมระบบพังมานานและอยากควบคุมทุกอย่าง

เสน่ห์พื้นฐานของ Erlang/OTP และ Elixir

  • แพลตฟอร์ม Erlang/OTP มอบโมเดลการประมวลผลแบบหลายโปรเซสที่ โปรเซสอิสระ สื่อสารกันด้วยข้อความ
    • สถานะส่วนใหญ่เป็น immutable ดังนั้นโปรเซสจึงโต้ตอบกันผ่านข้อความเท่านั้น
    • มีเครื่องมือสำหรับจัดการความล้มเหลวของโปรเซส การรีสตาร์ต การตรวจสอบขณะรันไทม์ และการเปลี่ยนโค้ดระหว่างรัน
  • พื้นฐานการทำงานคือ BEAM VM และมีมุมมองว่าหากใช้แค่โมเดล Unix process ทั่วไป ก็ยากจะได้ความแข็งแกร่งและความสามารถในการสังเกตภายในระดับเดียวกัน
  • Erlang เป็นภาษาที่มีเอกลักษณ์ เกิดขึ้นช่วงปลายทศวรรษ 1980 จาก Prolog ที่ผสมองค์ประกอบของ Lisp ส่วน Elixir เป็นภาษาที่ใหม่กว่า เริ่มต้นในปี 2012 ดูร่วมสมัยกว่า คอมไพล์ลง BEAM และเข้ากันได้ดีกับโค้ด Erlang
  • ตอนแรก Elixir อาจดูเหมือนภาษาเอา Erlang มาแต่งหน้าใหม่ให้คล้าย Ruby แต่จริงๆ แล้วเพิ่มสิ่งที่มีความหมายหลายอย่างเข้าไป

สิ่งที่ Elixir เพิ่มเข้ามาบน Erlang

  • โมดูลซ้อนกัน

    • Erlang ใช้ namespace ของโมดูลแบบแบน จึงไม่สามารถใส่ bar ไว้ใน foo ให้เป็นโครงสร้างอย่าง foo.bar ได้
    • เพราะอย่างนั้นโค้ดจริงจึงมักตั้งชื่อแบบ foo_bar เพื่อเลียนแบบการซ้อนกัน
  • ค่าเริ่มต้นของสตริงที่ดีขึ้น

    • สตริงของ Erlang โดยพื้นฐานคือ linked list ของตัวอักษร ซึ่งอาจเป็นธรรมชาติกว่าสำหรับภาษาด้านโครงสร้างสื่อสารในยุค 1980
    • bit-string ของ Erlang คืออาร์เรย์ immutable ของไบต์ตามใจที่ใส่ข้อมูล ASCII หรือ UTF-8 ได้
    • Elixir ใช้อาร์เรย์ immutable เป็นสตริงค่าปริยาย และยังสามารถสร้างสตริงแบบ linked list ที่โค้ด Erlang คาดหวังได้แยกต่างหาก
    • สตริงของ Elixir ถือว่าเป็น UTF-8 โดยปริยาย ขณะที่ Erlang bit-string ไม่ได้สมมติ encoding ใดเป็นพิเศษ
  • ไวยากรณ์ของโครงสร้างข้อมูล

    • records ของ Erlang ยังใกล้เคียงกับมาโครรอบ tuple และ Record module ของ Elixir ช่วยเรื่องการทำงานร่วมกับสิ่งนี้
    • ทั้ง Erlang และ Elixir มี maps ซึ่งเป็น immutable key-value dictionary
    • structs ของ Elixir คือ maps ที่เพิ่มการตรวจสอบตอนคอมไพล์เข้าไป
  • standard library ที่ใหญ่กว่า

    • Elixir เพิ่มความสามารถต่อยอดบน standard library ของ Erlang
  • มาโครที่ทรงพลังยิ่งกว่า

    • มาโครของ Erlang ก็ทรงพลังได้ แต่คล้าย C preprocessor ที่เป็นระเบียบมากกว่า มักใช้กับการกำหนดค่าคงที่ include และ conditional compilation พื้นฐาน
    • Elixir วางมาโครไว้ตรงศูนย์กลางของการใช้งานภาษาอย่างจริงจังมากกว่า

จุดที่ Elixir ดูชวนอึดอัดในตอนแรก

  • Elixir ดูหลวมและยืดหยุ่นแบบ Ruby และตั้งแต่ช่วงต้นของ tutorial ก็มี syntactic sugar จำนวนมากที่ทำให้โค้ดเดียวกันเขียนได้หลายแบบ
  • ถ้า Erlang ดูเหมือนภาษาง่ายๆ ที่มีชิ้นส่วนไม่กี่อย่างแล้วประกอบกันลงตัว Elixir จะให้ความรู้สึกว่าเป็นภาษาขนาดใหญ่ที่มีองค์ประกอบมากกว่าในหลายชั้น
  • โปรเจ็กต์ Elixir จะทำเป็นสคริปต์เดี่ยวหรือไฟล์เล็กๆ ไม่กี่ไฟล์ก็ได้ แต่เอกสารมักพาเข้าสู่ build tool อย่าง mix, การออกแบบ OTP application และวิธีใช้ GenServer อย่างรวดเร็ว
  • การเริ่มต้นผ่าน mix ให้ความรู้สึกว่าทำความคุ้นเคยยากกว่าเครื่องมืออย่าง cargo หรือ zig
  • วิธีแนะนำ Elixir และ Phoenix มักเป็นมิตรและเต็มไปด้วยคำอธิบายฟีเจอร์ที่ดูแวววาว จนชวนให้สงสัยว่าคุ้มกับความซับซ้อนที่ต้องแบกรับหรือไม่
  • ความรู้สึกต่อต้านที่ลึกกว่านั้นมาจากประสบการณ์ว่า Magic สุดท้ายก็พัง
    • ระบบค้นหา service แบบกระจายตัวอัตโนมัติดูเหมือนเวทมนตร์ในการหา network node โดยไม่มีดัชนีกลาง แต่จริงๆ คือไล่สแกนจาก UDP port ที่รู้จักทีละพอร์ต
    • GUI สำหรับตรวจสอบระยะไกลจริงๆ แล้วใช้วิธีส่งข้อมูลที่โปรแกรมโอเพนซอร์สเดิมเก็บมาให้ผ่าน websocket
    • หน้าจอที่อัปเดตแบบเรียลไทม์โดยไม่ใช้ frontend framework แท้จริงคือ iframe ที่รีเฟรชทุก 5 วินาที
    • โปรโตคอลที่ทำให้ข้อความ RPC เชื่อมต่อกันอัตโนมัติมีสมมติฐานที่ไม่ได้เขียนเอกสารไว้มากมายเกี่ยวกับลำดับข้อความและค่าที่คาดหวัง

จุดเปลี่ยน: Elixir เข้าใจได้แบบ Lisp

  • ยิ่งขุดลึกใน Elixir ก็ยิ่งรู้สึกว่าแก่นของมันใกล้เคียงกับ Elixir is actually a Lisp
  • แม้จะไม่มีวงเล็บ แต่มีองค์ประกอบจำนวนมากที่ทำให้ Common Lisp มีความยืดหยุ่น ทรงพลัง เป็นไดนามิก และเน้น metaprogramming
  • Elixir ยังรักษาข้อดีของ Erlang เอาไว้ด้วย
    • immutability ที่แท้จริง
    • pattern matching อย่างกว้างขวาง
    • symbolic programming
    • ความสามารถด้าน type แบบเลือกใช้และค่อยๆ เติมได้
  • BEAM มีลักษณะคล้าย Lisp runtime ในแง่ที่ตรวจสอบ แก้ไข และสำรวจภายในได้ง่าย
  • Erlang ไม่ได้ทำให้ใช้พลังในการตรวจสอบภายในแบบนี้อย่างกระตือรือร้นได้ง่ายนักจนกว่าจะลงลึกใน runtime library แต่ Elixir ใช้พลังนั้นตั้งแต่เริ่มต้น
  • Phoenix จึงเป็นกรณีศึกษาสำหรับดูว่าควรจัดโครงสร้างโปรแกรมและไลบรารี Elixir ขนาดใหญ่ที่ใช้งานจริงอย่างไร

ตัวตนที่แท้จริงของ Phoenix Router DSL

  • ตัวอย่าง Elixir พื้นฐานอย่าง defmodule, def, IO.puts ดูค่อนข้างธรรมดา
  • แต่ใน router ของ Phoenix project template จะมีรูปแบบอย่าง
    • use OtterWeb, :router
    • pipeline :browser do ... end
    • plug :accepts, ["html"]
    • scope "/", OtterWeb do ... end
    • if Application.compile_env(:otter, :dev_routes) do ... end
  • ตอนแรก scope, pipeline, plug, pipe_through ดูเหมือน DSL ที่ฝังมาเป็นพิเศษสำหรับ Phoenix มากกว่าจะเป็น Elixir
  • แต่หัวใจจริงคือไวยากรณ์การเรียกใช้ของ Elixir โดยพื้นฐานแล้วสรุปลงได้เป็น การเรียกฟังก์ชันหรือมาโครเพียงครั้งเดียว
    • รูปแบบพื้นฐานคือ foo(arg1, arg2, arg3)
    • ถ้าตัดวงเล็บออกก็เขียนเป็น foo arg1, arg2, arg3 ได้
    • อาร์กิวเมนต์สุดท้ายสามารถเป็นรายการ keyword arguments ได้
    • [key1: val1, key2: val2] เทียบเท่ากับ [{:key1, val1}, {:key2, val2}]
    • ถ้าอาร์กิวเมนต์สุดท้ายเป็น association list ก็ละวงเล็บเหลี่ยมชั้นนอกได้เช่นกัน
  • do ... end ก็ทำงานเป็น syntactic sugar สำหรับ association list
    • foo thing do 10 end เทียบได้กับ foo thing, do: 10
    • ถ้าคลี่ออกอีกก็อาจมองเป็น foo(thing, [{:do, 10}])
    • ถ้า foo ไม่ใช่ฟังก์ชันแต่เป็นมาโคร สิ่งที่อยู่ใน do ... end จะถูกส่งต่อเป็น syntax tree แทนค่าที่ประเมินแล้ว
  • ภายใต้โครงสร้างนี้ scope, pipeline, def, defmodule, if จึงเข้าใจได้ว่าเป็น มาโคร ทั้งหมด

วิธีที่มาโคร, sigil และ use ใช้ขยายภาษา

  • Elixir ไม่เพียงนำมาโครแบบตระกูล Lisp และการแทนโค้ดแบบ homomorphic มาใช้ แต่ยังหยิบองค์ประกอบอื่นที่ใกล้กับการใช้งานจริงมาด้วย
  • sigil

    • สตริงทั่วไปเขียนเป็น "foo"
    • สตริงแบบ list ของ Erlang สร้างได้ด้วย ~c"foo"
    • regular expression สร้างได้ในรูป ~r/foo/
    • sigil ของ Elixir สามารถนิยามเองได้ โดยเขียนฟังก์ชันที่รับสตริงแล้วคืนค่าข้อมูล
    • เอกสารที่เกี่ยวข้อง: Custom sigils
    • มันมีบทบาทคล้าย reader macros ของ Common Lisp แต่ในทางปฏิบัติ sigil ของ Elixir มักถูกใช้เป็นทางลัดที่ดีกว่าสำหรับงานส่วนใหญ่ที่ต้องการ
  • ชั้นของเครื่องมือและ syntactic sugar

    • Elixir ยังรับฟีเจอร์ดีๆ จากภาษาอื่นด้วย
    • มีความสามารถพิมพ์โค้ดของ expression พร้อมผลลัพธ์ คล้าย dbg!() ของ Rust
    • สามารถเข้าสู่ interactive debugger ผ่าน pry ซึ่งดูเหมือนเสริมความสามารถมากกว่า debugger ของ Erlang
    • ใช้ไวยากรณ์ pipeline |> แบบสาย Haskell/ML
    • ยังมีความสามารถด้านเอกสารเชิงโต้ตอบซึ่งเป็นหนึ่งในเหตุผลที่ Python สำรวจใน REPL ได้ง่าย
    • syntactic sugar หลายชั้นทำงานเป็น โครงสร้างแบบลำดับชั้น ที่แต่ละชั้นวางอยู่ภายในชั้นก่อนหน้าอย่างเรียบร้อย
    • จาก foo([{:do, 3}]) ไปจนถึง foo do 3 end จะมีหลายขั้น แต่โครงสร้างพื้นฐานยังคงเป็นการเรียกฟังก์ชันหรือมาโครที่รับ association list เป็นอาร์กิวเมนต์สุดท้าย
    • ข้อยกเว้นคือมีจุดคมอยู่ระหว่าง do\n ...\n end กับ do: ...\n

use ไม่ใช่แค่ import แต่เป็นการอนุญาตให้สร้างโค้ด

  • use Thing, option: something จะดึงโมดูล Thing เข้ามาเหมือน require แล้วเรียก Thing.__using__(option: something)
  • จุดสำคัญคือ __using__() โดยทั่วไปไม่ใช่ฟังก์ชันที่ถูกเรียกเพื่อให้เกิด side effect
  • __using__() มักเป็น มาโคร และจะสร้างโค้ดไปแทรกในโมดูลตรงตำแหน่งที่ใช้ use
  • ดังนั้น use Thing อาจขยายออกเป็นโค้ดอย่าง
    • require Thing1
    • require Thing2
    • def some_cool_method() ...
    • use SomeOtherThing
  • use จึงไม่ใช่การนำคำจำกัดความของโมดูลหนึ่งเข้ามาใน namespace ปัจจุบันแบบตรงๆ แต่เป็นการอนุญาตให้โมดูลนั้นสร้างโค้ดใดๆ ก็ได้ภายในโมดูลปัจจุบัน
  • เวลาอ่านหรือเขียนโค้ด Elixir แล้วเห็น use ควรมองมันเป็น turbo macro ที่อาจสร้างคำจำกัดความใหม่ได้มากมาย

ทำไมมันถึงให้ความรู้สึกเหมือน “Lisp ที่ดีกว่า”

  • Common Lisp หรือ Scheme มักต้องแก้ปัญหาเรื่องไลบรารี package manager, FFI, build system และการ deploy แอปพลิเคชันตั้งแต่ช่วงเริ่มต้น
  • Elixir ทำงานบน BEAM จึงได้ทั้ง ความแข็งแกร่ง และความสามารถด้านการประมวลผลหลายโปรเซสที่ไม่ค่อยพบใน Lisp ทั่วไป
  • pattern matching ของ Erlang อยู่ในระดับสูง และ Elixir ก็รับคุณสมบัตินี้มาเต็มๆ
  • Erlang มีเครื่องมือและไลบรารีสำหรับเฝ้าดูแอปที่กำลังรันและตรวจดูภายใน ส่วน Elixir ก็ใช้งานสิ่งเหล่านี้พร้อมต่อยอดเพิ่ม
  • เวทมนตร์ของ Elixir ไม่ได้ซ่อนอยู่ แต่เปิดให้เห็น
    • มันอธิบายวิธีทำงานไว้ตั้งแต่ต้นของ tutorial
    • โครงสร้างหลักของ runtime ประกอบด้วย list, tuple และ atom จึงตรวจดูและแก้ไขเองได้
    • สามารถกู้คืนกลับสู่สถานะสะอาดได้จาก declarative project specification
  • Elixir ไม่ได้ซ่อนภายใน แต่เปิดให้ตรวจสอบและลองทำให้พังได้ พร้อมตั้งอยู่บนสมมติฐานว่าคุณจะใช้เครื่องมือที่ช่วยไม่ให้มันพังใน production

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

 
GN⁺ 2023-08-04
ความคิดเห็นจาก Hacker News
  • บทความยอดเยี่ยมจริง ๆ และคุ้มค่าแก่การอ่าน
    ตอนที่ตระหนักว่า จริง ๆ แล้ว Elixir คือ Lisp ภาษานี้ก็เปลี่ยนจาก “น่าสนใจนิดหน่อย” เป็น “นี่อาจกลายเป็นภาษาโปรดได้เลย”
    พอรวมกับเฟรมเวิร์กที่ทั้ง productive และใช้งานได้จริงอย่าง Phoenix, Nerves และ Nx ที่กำลังเติบโตขึ้นเรื่อย ๆ ก็กลายเป็นกล่องเครื่องมือที่ทรงพลัง
    ข้อบ่นใหญ่ที่สุดคือการเขียนสคริปต์เร็ว ๆ ทำได้ยาก แต่ในช่วงไม่กี่ปีที่ผ่านมาก็ดีขึ้นมาก และในแง่ที่สามารถใส่ dependency ไว้ในสคริปต์ได้ ก็ดูเหมือนอาจดีกว่า Ruby ด้วยซ้ำ

    • ยังสงสัยอยู่ว่าคำว่า “Elixir คือ Lisp” นั้นถูกจริงหรือไม่
      เท่าที่เคยดูเมื่อก่อน ไวยากรณ์สำหรับเขียน macro ต่างจากไวยากรณ์โค้ดทั่วไป จนดูเหมือนไม่ได้รองรับ metaprogramming แบบ homogeneous อย่างแท้จริง
      แต่ก็เป็นความทรงจำเมื่อนานมาแล้ว อาจจำผิดก็ได้ และถ้ามีตัวอย่างแย้งก็อยากเห็น
    • เรื่องการจัดการ dependency ใน Gemfile สำหรับสคริปต์ไฟล์เดียวของ Ruby ก็ดีขึ้นกว่าสมัยก่อนเล็กน้อย
      ถึงอย่างไรก็ยังต้องติดตั้ง gem อยู่ดี และโดยอุดมคติก็คงอยากติดตั้งไว้ในตำแหน่ง local แทนตำแหน่งเริ่มต้นของระบบ ซึ่งปกติทำได้ใน bundler แต่ใน inline bundler ดูเหมือนผู้ดูแลจะมองว่านั่นเกือบเป็น use case ที่ไม่ควรใช้: https://github.com/rubygems/bundler/issues/7131
      ไม่ได้พยายามจะโน้มน้าว แค่เป็นข้อมูลเพิ่มเติมสำหรับผู้อ่านคนอื่น ๆ
    • ปัญหาของการเขียนสคริปต์บน Erlang VM คือ เวลาเริ่มต้นที่ช้า
      ไม่ค่อยเหมาะกับงานสคริปต์และไฟล์ executable แต่เรื่องอื่น ๆ แทบทั้งหมดชอบมาก
    • Livebook ของ Elixir สำหรับผมกลายเป็นเครื่องมือ “สคริปต์” ที่ทรงพลัง
    • มี repository ดี ๆ ที่แสดงวิธีใช้ Elixir เหมือนภาษาเขียนสคริปต์เร็ว ๆ: https://github.com/wojtekmach/mix_install_examples
  • เห็นด้วยอย่างลึกซึ้งกับคำว่า “เวทมนตร์ไม่เคยคุ้มค่า”
    ฟีเจอร์อำนวยความสะดวกที่เหมือนเวทมนตร์ ส่วนใหญ่สักวันหนึ่งก็มักจะพัง และถ้าจะซ่อมมันก็ต้องตัดชั้นความสะดวกที่แวววาวออกให้หมด เพื่อดูว่าจริง ๆ แล้วเกิดอะไรขึ้น
    พอขุดลงไปมักพบว่ามันพังตั้งแต่การออกแบบแบบโง่ ๆ หรือไม่ก็ไม่ได้เป็นเวทมนตร์อะไรนักตั้งแต่แรก จนกลายเป็นว่า “แล้วทำไมไม่พูดแบบนี้ตั้งแต่แรก?”
    ที่แย่กว่านั้นคือสถานการณ์ที่ทุกคนใช้เวทมนตร์กันคนละแบบ หรือหลังจากคนสร้างเวทมนตร์นั้นลาออกไปเมื่อ 2 ปีก่อน ทุกอย่างก็เดินต่อแบบ cargo cult

  • สิ่งที่ชอบใน Elixir คือ concurrency เข้าใจได้เป็นธรรมชาติ และสามารถใช้ทั้ง standard library ของ Elixir และ Erlang ได้ง่าย ทำให้ได้เครื่องมืออย่าง gen_tcp, gb_tree มาฟรี ๆ
    สามารถ remote เข้าไปยัง virtual machine หรือ pod ที่กำลังรันอยู่ ตรวจดูสถานะผ่าน REPL จัดการ “process” และรันคำสั่งโดยตรงได้ อีกทั้ง observer และ terminal UI ก็มีประโยชน์
    ข้อดีอีกอย่างคือ library ต่าง ๆ ใช้วิธี telemetry แบบมาตรฐาน, compile ได้เร็ว, BDFL ที่ทำงานอย่างขยันขันแข็ง และชุมชนที่ให้เกียรติกัน
    ในทางกลับกัน เพราะเป็นภาษา dynamic pattern matching หรือ type mismatch ที่ควรจับได้ตอน compile time อาจไปแตกตอน runtime และ Phoenix มีชุมชนกับเนื้อหาล่าสุดไม่มากนัก หากไม่มีประสบการณ์ frontend เฟรมเวิร์ก SPA สมัยใหม่อย่าง React อาจรู้สึกง่ายกว่า
    อุปทานบุคลากร Elixir มีมากกว่าอุปสงค์ และการเติบโตของ ecosystem ก็ค่อนข้างช้ากว่า

    • เอกสารของ Elixir และ library หลักอย่าง Ecto กับ Phoenix แม้ปริมาณจะไม่มาก แต่คุณภาพดีมาก
      ไม่จำเป็นต้องมีบล็อกคุณภาพต่ำแนว “วิธีทำ xyz” จำนวนมาก เพราะเอกสารทางการจัดระเบียบสิ่งที่ต้องการไว้ดี อ่านง่ายและค้นหาได้ง่าย
    • ยังอยู่ในระยะเริ่มต้น แต่มีแผนจะนำ type system เข้ามาใน Elixir: https://elixir-lang.org/blog/2023/06/22/type-system-updates-...
    • ถ้าชอบ ecosystem แต่ไม่ชอบ dynamic typing ก็ยังมี Gleam ซึ่งเป็นภาษา static typing บน BEAM: https://gleam.run/
    • วิธีเปลี่ยนโค้ดที่กำลังรันอยู่ผ่าน REPL ทำให้นึกถึง Pharo
      Pharo ถึงขั้นมี IDE ให้ด้วย
      แต่ก็รู้สึกว่าชุมชน Pharo เล็กเกินไปเหมือนกัน และใกล้จะมีงานประชุม ESUG แล้ว จึงดีที่จะได้เห็นคนมารวมตัวกัน
      แต่สงสัยว่าทำไม concurrency ถึงเข้าใจได้เป็นธรรมชาติ
    • สงสัยว่าจริงหรือไม่ที่อุปทานบุคลากร Elixir มีมากกว่าอุปสงค์
  • นอกจากตัวภาษาเองแล้ว Elixir ยังมี เครื่องมือสำหรับนักพัฒนา ที่ยอดเยี่ยม และรู้สึกสบายใจที่ทั้งหมดเขียนและตั้งค่าด้วยภาษานั้นเอง
    ไม่ต้องใช้ภาษา scripting ภายนอก และบางงานถึงขั้นไม่ต้องใช้ library ภายนอกด้วยซ้ำ
    โดยรวมให้ความรู้สึกเหมือนระบบปฏิบัติการ และ BEAM เองก็มีลักษณะคล้ายระบบปฏิบัติการด้วย
    ถ้ากำลังลังเลระหว่าง Erlang กับ Elixir ก็น่าดู The Soul of Erlang and Elixir ของ Sasa Juric: https://youtu.be/JvBT4XBdoUE
    น่าทึ่งที่ทีม Erlang ของ Ericsson ล้ำยุคแค่ไหน และจนถึงตอนนี้ก็ยังเป็นอย่างนั้น

    • ชอบ Erlang มากจริง ๆ
      เป็นระบบที่กระชับ ทรงพลัง และออกแบบมาดีอย่างน่าทึ่ง VM ก็เข้ากันได้ดีมาก
      แต่ความ verbose ที่ Elixir เพิ่มเข้ามายังเป็นสิ่งที่ก้าวข้ามได้ยาก ถึงอย่างนั้นพออ่านบทความนี้ก็เริ่มมีความหวังว่าสักวันอาจทำได้
  • น่าสนใจที่ if, def, defmodule ล้วนเป็น macro
    นิยามของ defmacro ซึ่งเป็น macro สำหรับนิยาม macro ก็น่าดูเช่นกัน และตัวมันเองก็นิยามด้วย defmacro: https://github.com/elixir-lang/elixir/blob/v1.15/lib/elixir/...

  • เป็นเรื่องน่ายินดีเสมอที่มีคนเข้ามาสนใจ Elixir มากขึ้น
    ผมมองว่าจุดที่ผู้คนสะดุดบ่อยที่สุดคือ ระดับความอิสระ
    เขาบอกว่า “โครงสร้างโปรเจกต์ขนาดใหญ่และซับซ้อน” เป็นอุปสรรค แต่จริง ๆ แล้วในระดับภาษาไม่มีโครงสร้างที่ถูกบังคับ และสามารถทำได้ตามต้องการ
    เพียงแต่ Mix มีขนบที่แนะนำไว้ และคนส่วนใหญ่ก็ทำตามแนวทางนั้นเท่านั้น
    แทบทุกอย่างถือว่าใกล้เคียงกับขนบปฏิบัติ รวมถึงโหมดรันสำหรับทดสอบ/พัฒนา/โปรดักชันด้วย และ Erlang เองก็มีแนวทางมาตรฐาน แต่ไม่ได้บังคับว่าต้องทำตาม
    struct ก็เป็นตัวอย่างที่ดี เพราะไม่ใช่ฟีเจอร์ของภาษา แต่เป็น map ที่มีคีย์พิเศษและมีการสนับสนุนจากไลบรารี Elixir เพิ่มเข้ามา
    ข้อดีและข้อเสียคือ หากมีเวลาและฝีมือ ก็สามารถสร้างแทบทุกอย่างขึ้นมาได้

  • ถ้าอยากสัมผัสความรู้สึกว่า “ข้างล่างลงไปทั้งหมดคือ macro” อย่างชัดเจน มีวิดีโอความยาว 3 นาที 39 วินาทีนี้: https://www.youtube.com/watch?v=x5W3CmZJMLs

  • ผมมาจาก Python/C++ แล้วกำลังดู Elixir กับ Phoenix อยู่ ซึ่งดูเท่มาก
    ถ้าจะทำฟีเจอร์เรียลไทม์ใน Python ก็ต้องใช้ของอย่าง Tornado หรือ asyncio ซึ่งผมมองว่าไม่ใช่แนวทางที่เหมาะที่สุด
    ในโครงสร้างขนาดใหญ่ ผมชอบสถาปัตยกรรมแบบ immutability และการส่งข้อความ แต่ภายในโปรเซสก็อยากให้เขียนโค้ดสไตล์ Python ได้ตรง ๆ
    ยังไง VM ก็ทำสำเนาโครงสร้างข้อมูลทั้งหมดที่ขอบเขตระหว่างโปรเซสอยู่แล้ว
    ผมเคยใช้ Lisp เมื่อ 20 ปีก่อน ก่อนใช้ Python ด้วยซ้ำ แต่ตอนนี้การต้องเขียนโค้ดทั้งหมดแบบ “กลับด้าน” ในรูปแบบที่ดูไม่ได้ให้ประโยชน์มากนักทำให้รำคาญอยู่บ้าง
    เช่น แทนที่จะเขียนผลรวมของลิสต์ด้วย recursion ผมอยากเขียนด้วยลูป for และตัวแปรสะสมแบบ Python พร้อม ๆ กับให้ Erlang VM จัดการการ scheduling ของโปรเซสและการส่งข้อความแบบ immutable ให้

    • สงสัยว่าได้ดู list comprehension ของ Elixir แล้วหรือยัง
      เขียนได้แบบ for n <- 1..4, do: n*n และผลรวมในตัวอย่างก็จัดการได้ด้วย Enum.reduce หรือ Enum.sum
      https://elixir-lang.org/getting-started/comprehensions.html
      https://hexdocs.pm/elixir/1.12/Enum.html#reduce/2
      https://hexdocs.pm/elixir/1.12/Enum.html#sum/1
      แต่ก็จริงที่มันเป็นวิธีคิดที่ต่างจาก Python/Go/JavaScript
    • ใน Elixir ก็สามารถทำ reduce ด้วย list comprehension ได้เหมือนกัน แต่ไม่ค่อยพบเห็นบ่อย
      โดยทั่วไป Enum.reduce/2 เป็นตัวเลือกที่ดีกว่า และถ้าต้องการสถานะที่ซับซ้อนขึ้น ก็ใส่ค่ามากขึ้นใน accumulator ได้
      แต่ไม่ว่าทางไหนก็ไม่ได้ให้ความยืดหยุ่นของ mutability อย่างที่อีกคอมเมนต์พูดถึง
    • ถ้าไม่นับฟังก์ชันในตัว ผมคิดว่าการใช้ recursion ส่วนใหญ่เหมาะกับ Enum.reduce มากกว่า
    • Ray น่าลองดู
      เป็นไลบรารี Python ที่ช่วย abstract เรื่องการ deploy คลาสที่เป็น actor และฟังก์ชันที่เป็น task รวมถึงการจัดการทรัพยากร การส่งข้อความ และการกู้คืนอัตโนมัติ
      ผมไม่เคยใช้ Erlang หรือ Elixir ดังนั้นแนวทางฝั่งนั้นอาจใช้งานง่ายกว่าก็ได้ แต่ Ray แม้จะมุ่งไปที่งานแมชชีนเลิร์นนิงเป็นหลัก ก็ยังค่อนข้างใช้งานได้ทั่วไป
    • สงสัยว่า Enum.reduce ยังไม่พอหรือ หรือดีกว่านั้น Enum.sum ก็ใช้ได้ไม่ใช่หรือ: https://hexdocs.pm/elixir/1.12/Enum.html#reduce/3
  • ปัญหาเดียวของแมโครใน Elixir คือ ผู้คนเห็นแมโครของ ExUnit, Plug, Phoenix, Ecto ซึ่งเทียบได้กับ core++ แล้วพยายามทำทุกอย่างให้เป็น ภาษาเฉพาะโดเมน
    โดยเฉพาะ commanded, Tesla, Absinthe, exmachina และช่วงหลัง ๆ ก็คือ ash ผมมองว่าเป็นหนัก
    ไลบรารีแกนหลักจะปรับให้เข้ากับไวยากรณ์หรือคีย์เวิร์ดที่ต้องรู้อยู่แล้วแม้อยู่นอก Elixir เช่น get ของ Plug หรือ SQL ของ Ecto แต่ไลบรารีที่เป็นปัญหากลับสร้างชื่อใหม่ที่ดูฉลาดขึ้นมา แล้วเพิ่มภาระทางความคิดให้กับทุกงาน
    บางทียังทำให้เส้นทางการคอมไพล์ซับซ้อนเกินไป หรือสร้างชื่อฟังก์ชันที่ทำให้การรีแฟกเตอร์และการค้นหาโค้ดกลายเป็นฝันร้าย

    • โดยส่วนตัวแล้ว ผมไม่มีปัญหากับการที่ ใช้แมโครเยอะ ภายในไลบรารีเอง
      ถ้าเอกสารดี ผมก็ไม่ค่อยสนใจวิธี implement มากนัก และหลายกรณี API ที่สะอาดกว่าก็ดีกว่า
      แต่ก็อาจมากเกินไปได้ และแม้ใน Phoenix router ก็มีเรื่องแปลก ๆ เกิดขึ้น แต่ส่วนใหญ่เป็นไปเพื่อลด dependency การคอมไพล์แบบวนกลับ
      ในทางกลับกัน แมโครในโค้ดธุรกิจคือความชั่วร้ายล้วน ๆ และถ้าใครผ่าน Rails มาในช่วง 10 ปีที่ผ่านมา ก็น่าจะได้เรียนรู้บทเรียนนั้นแล้ว
    • นี่เป็นปัญหาแน่นอน แต่โชคดีที่วัฒนธรรมและคอมมูนิตี้ของ Elixir โดยทั่วไปไม่ได้มองแมโครที่ไม่จำเป็นในแง่ดี
      ผมเคยได้ยินหลายครั้งว่า “ถ้ามันทำเป็นฟังก์ชันธรรมดาได้ ก็ควรเป็นฟังก์ชันธรรมดา” และผมชอบจุดนั้น
      โดยรวมแล้ว ขณะใช้ Elixir นี่ไม่ใช่ปัญหาที่ผมชนบ่อยหรือหนักนัก
    • Tesla มีโหมดที่ใช้งานได้ครบถ้วนโดยไม่ต้องใช้แมโคร และกำลังแนะนำให้ใช้แบบนั้นมากขึ้นเรื่อย ๆ
      ผู้เขียนก็พูดไปในทิศทางเดียวกันเมื่อปี 2020: https://github.com/elixir-tesla/tesla/issues/367#issuecommen...
      อีกทางเลือกหนึ่งคือ req ที่ถูกพูดถึงในบทความล่าสุด และดูน่าสนใจ แต่ผมยังทดลองอยู่ว่าจะใช้แทน Tesla ได้ในทุกกรณีหรือไม่
      Absinthe ต้องนิยามอย่างเข้มงวดให้ตรงกับสเปก GraphQL จึงใกล้เคียงกับคอมไพเลอร์ชนิดหนึ่ง และตอนนี้แม้จะนำเข้าไฟล์ SDL ได้แล้ว แต่ก็ยังต้องเชื่อม resolver กับ middleware อยู่ดี
      เพราะอิงแมโคร จึงมีขอบคม ๆ เวลาพยายามนำโค้ดกลับมาใช้ซ้ำด้วยแมโครเอง แต่ถ้าไม่นับการนิยาม schema ก็ใช้งานได้โดยไม่ต้องมีแมโคร
      Exmachina ไม่ดึงดูดใจผม เพราะเห็นการใช้ factory_bot แบบเกินขอบเขตมามากเกินไป ส่วน Ash ดูเหมือนการทดลองที่น่าสนใจ แต่ไม่รู้ว่าถูกใช้แพร่หลายเมื่อเทียบกับ Phoenix แค่ไหน
      commanded ไม่เคยมีเหตุผลให้ลอง เพราะไม่มีงานที่ต้องใช้ CQRS/ES และผมไม่มองสามตัวนี้ว่าเป็นผู้เล่นหลักของ Elixir
    • ผมใช้ Ash ใน side project อยู่ และยิ่งใช้ก็ยิ่งชอบ
      ถ้าจะเข้าใจมันให้ครบต้องบิดสมองอยู่บ้าง แต่เมื่อเทียบกับ Ecto แล้วมี boilerplate น้อยกว่า และสามารถรวม query, การจัดการการเปลี่ยนแปลง, การเข้าถึง API, งานเชื่อมโยงต่าง ๆ ไว้ใน resource เดียวกับแมโครไม่กี่ตัวได้
      มันยังมีทางออกไปเขียน Elixir ปกติค่อนข้างเยอะ ดังนั้นถ้าไม่ชอบเวทมนตร์หลัง action ก็สามารถแทรกเข้าไปหรือ implement เองได้หลายวิธี และโค้ดฝั่งเรียกใช้ยังดูเหมือนเดิม
      แม้จะมีแมโครเยอะ แต่ error โดยทั่วไปค่อนข้างดีและชี้ตำแหน่งปัญหาได้ดี
      ข้อเสียคือมีคนที่เข้าใจ Ash จริง ๆ น้อยเกินไป เอกสารมักสั้นและบีบอัดเกินไป และ error ก็ไม่ได้ดีเสมอไป
      ผมเห็นด้วยว่าบางส่วนของ ecosystem พึ่งพาแมโครมากเกินไป
    • แม้ในฐานะคนที่ชอบและรู้จัก Elixir ดี จุดนี้ก็ทำให้การ เรียน Phoenix เป็นเรื่องยากสำหรับผมเป็นการส่วนตัว
      มีแมโครซ้อนบนแมโครมากเกินไป จนรู้สึกแทบเหมือนกำลังเรียนภาษาอื่น
  • ช่วงเวลาที่เข้าใจว่า “Elixir แท้จริงแล้วคือ Lisp” นั้นตื่นเต้นมากจริง ๆ
    เป็นภาษาที่งดงามซึ่งทุกองค์ประกอบเข้ากันได้อย่างลงตัว