18 คะแนน โดย GN⁺ 2025-04-22 | 11 ความคิดเห็น | แชร์ทาง WhatsApp
  • Pipelining เป็นความสามารถสำคัญของภาษาโปรแกรมที่ช่วยเพิ่มทั้งความอ่านง่ายและความสามารถในการบำรุงรักษาโค้ด
  • เป็นแนวทางที่ช่วยให้สามารถแสดง การไหลของข้อมูลจากซ้ายไปขวา และจากบนลงล่างได้อย่างเป็นธรรมชาติ
  • ในภาษาอย่าง Rust การทำ pipelining ช่วยให้ลำดับการไหลของโค้ดชัดเจนขึ้น และเพิ่มประสิทธิภาพการพัฒนาด้วยฟีเจอร์ autocomplete ของ IDE
  • ใช้ได้ในหลายภาษา เช่น Haskell, Elm, SQL และ builder pattern หรือ method chaining ก็ถือเป็นรูปแบบหนึ่งของ pipelining
  • ส่งผลดีต่อทั้ง ความอ่านง่าย, ความสะดวกในการแก้ไข, การรองรับจาก IDE, และ เครื่องมือจัดการเวอร์ชัน (diff, blame)
  • สามารถเขียนโค้ดให้กระชับและชัดเจนกว่าการซ้อนฟังก์ชัน จึง ได้เปรียบในด้านการทำงานร่วมกันและการบำรุงรักษา

ไวยากรณ์การเขียนโปรแกรมที่ผมชอบที่สุด, Pipelining

Pipelining คืออะไร?

  • เป็นความสามารถที่ส่งต่อค่าก่อนหน้า ทำให้สามารถละอาร์กิวเมนต์หนึ่งตัวออกจากรายการพารามิเตอร์ได้
  • ช่วยเพิ่มความอ่านง่ายของโค้ด และทำให้เพิ่มคอมเมนต์ได้ง่ายขึ้น
  • เป็นสไตล์ไวยากรณ์ที่ใช้ข้อมูลเป็นศูนย์กลาง โดย นำขั้นตอนการประมวลผลที่ต่อเนื่องมาใช้ตามลำดับ
  • มักใช้ในโค้ดสไตล์ functional ในรูปแบบ method chaining เช่น .map().filter().collect()
  • ตัวอย่างที่พบได้บ่อยใน Rust:
    data.iter()  
        .filter(|w| w.alive)  
        .map(|w| w.id)  
        .collect()  
    
  • ในทางกลับกัน ถ้าซ้อนทุกฟังก์ชันทั้งหมด ก็จะกลายเป็นโครงสร้างที่ ต้องอ่านจากด้านในออกมาด้านนอก แบบนี้:
    collect(map(filter(iter(data), |w| w.alive), |w| w.id))  
    

ทำไม pipelining ถึงดี?

  • 1. ความอ่านง่ายและการบำรุงรักษา

    • อ่านจากบนลงล่างได้ง่าย → ลำดับการไหลของข้อมูลตรงกับลำดับที่มนุษย์อ่าน
    • ใส่คอมเมนต์ในแต่ละบรรทัดได้ง่าย
    • บรรทัดยาวก็ยัง กระชับและชัดเจน โดยไม่ต้องซ้อนวงเล็บ
  • 2. ความสะดวกในการแก้ไข

    • สามารถ เพิ่มฟังก์ชันใหม่อย่าง .map() กลางทางได้ง่ายในหนึ่งบรรทัด
    • ใน git diff หรือ git blame ก็เห็นการเปลี่ยนแปลงได้อย่างเป็นระเบียบ
  • 3. การรองรับของ IDE / LSP

    • เข้ากันได้ดีกับโครงสร้างที่เมื่อกดปุ่ม . แล้วจะมี รายการ autocomplete แสดงขึ้นมา
    • เหมาะกับการวิเคราะห์แบบ static analysis ที่ต้องรู้ชนิดข้อมูลอย่างชัดเจน
    • เพื่อให้ฟีเจอร์นี้ทำงานได้ดี ภาษาเองควรเป็น ภาษาที่อิง static type (เช่น Rust, TypeScript)

SQL ก็มี pipelining ได้เหมือนกัน?

  • มีข้อเสนอว่าสามารถเปลี่ยน nested SELECT query ของ SQL ให้เป็นสไตล์ pipeline ได้
  • ตัวอย่าง:
    FROM customer  
    |> LEFT OUTER JOIN orders ON ...  
    |> AGGREGATE COUNT(...) GROUP BY ...  
    |> ORDER BY ...  
    
  • ให้ ลำดับการไหลที่ชัดเจนกว่า และ ช่วยเพิ่มความอ่านง่าย เมื่อเทียบกับ SQL แบบเดิม
  • ข้อเสีย: เมื่อคำสั่ง SELECT ถูกย้ายขึ้นไปด้านบน อาจทำให้เข้าใจชนิดของค่าที่คืนกลับมายากขึ้น → แต่แก้ไขได้

ความเชื่อมโยงกับ Builder pattern

  • รูปแบบอย่าง Builder::new().option().option().build() ใน Rust เป็นโครงสร้าง pipeline แบบคลาสสิก
  • การประกอบค่าตั้งค่าแบบเลือกได้ผ่านเมธอดทำให้ ติดตามโค้ดและจัดการการเปลี่ยนแปลงได้ง่าย

การปรับปรุง pipelining ใน Haskell

  • ตัวดำเนินการอย่าง $, &, |> ใน Haskell ช่วยให้ ใช้ pipeline แทนการประกอบฟังก์ชัน ได้
  • ตัวอย่างเปรียบเทียบก่อนและหลัง:
    -- 기존  
    checkPalindromes content = unlines $ map (show . isPalindrome) $ lines $ map toLower content  
    
    -- 개선  
    checkPalindromes content =  
      content  
        & map toLower  
        & lines  
        & map (show . isPalindrome)  
        & unlines  
    

ข้อดีของ pipelining ใน Rust

  • method chaining, type inference, และ ความสามารถในการขยายโครงสร้างผ่าน trait ล้วนเข้ากันได้ดีกับ pipelining
  • Rust มีโครงสร้างที่เหมือน คัดเอาแต่ข้อดีของไวยากรณ์แบบ functional และ object-oriented มาไว้ด้วยกัน จึงเป็นภาษาที่การใช้ pipelining ดูเป็นธรรมชาติที่สุด

สรุป

  • Pipelining ไม่ใช่แค่ไวยากรณ์ธรรมดา แต่เป็น ความสามารถหลักที่ส่งผลต่อทั้งลำดับการไหลของโค้ด ความสะดวกในการแก้ไข และการทำงานร่วมกัน
  • แทนที่จะใช้การซ้อนแบบ f(g(h(x))) โครงสร้างแบบ x |> h |> g |> f เป็นมิตรกับมนุษย์มากกว่า
  • ภายใต้กฎง่าย ๆ ว่า “หนึ่งบรรทัดต่อหนึ่งงาน” pipelining คือ วิธีที่ยอดเยี่ยมที่สุดในการแสดงลำดับการไหลอย่างเป็นธรรมชาติ

> “แต่ละชิ้นส่วนของท่อรับข้อมูลหลักเข้ามาแล้วทำงานเพียงอย่างเดียว สุดท้ายถ้าตั้งชื่อให้ชัดเจน นั่นแหละคือโครงสร้างโค้ดในอุดมคติที่สุด”

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

 
progdesigner 2025-04-23

ไม่ว่าจะเป็นข้อความแบบใดก็ตาม
การขึ้นบรรทัดใหม่และการเยื้องย่อหน้าก็สำคัญต่อความอ่านง่าย
น่าจะอยู่ในบริบทที่คล้ายกันครับ

 
forgotdonkey456 2025-04-23

LINQ เจ๋งที่สุด!

 
bus710 2025-04-23

Gleam ก็รองรับสิ่งนี้เหมือนกัน เลยเขียนโค้ดได้ค่อนข้างสะอาดดีครับ

ว่าแต่ สงสัยเพราะมีโค้ดบล็อกอยู่ในเนื้อหา บนมือถือเลยแสดงผลเป็นเลย์เอาต์แบบเดสก์ท็อปด้วยนะครับ

 
bus710 2025-04-23

คิดไปคิดมา elm ก็ทำได้เหมือนกันนะ

 
galadbran 2025-04-22

สำหรับข้อมูลปริมาณน้อยและโค้ดระดับง่าย ๆ แบบตัวอย่างข้างบน ผมคิดว่ามันก็ดูดีและไม่ได้มีข้อเสียอะไรครับ

แต่พอเริ่มมีโค้ดค่อย ๆ เข้าไปอยู่ใน map() ... ก็มีแนวโน้มจะทำให้โค้ดค่อย ๆ อ้วนขึ้น
แม้จะขึ้นอยู่กับภาษาและไลบรารีที่ใช้ แต่ถ้าปริมาณข้อมูลมากขึ้น มันก็อาจช้าลงได้ง่าย ๆ ถึงระดับหลายพันเท่าเมื่อเทียบกับการประมวลผลด้วยการสะสมหรือจัดการข้อมูลตรง ๆ ในโครงสร้างข้อมูล

แล้วก็ยังมีอีกเหตุผลใหม่หนึ่งที่ทำให้ผมไม่ค่อยชอบมัน คือพอเปิดบทความนี้บนมือถือแล้ว ความกว้างแบบระดับ PC ยังถูกคงไว้เหมือนเดิม ทำให้ตัวหนังสือเล็กจิ๋วมากจนอ่านบทความยากมากจริง ๆ ครับ T.T

โดยพื้นฐานแล้วผมไม่ได้ชอบมัน และก็ไม่ได้พยายามจะเขียนให้เป็นแบบนั้นโดยตั้งใจครับ

 
bichi 2025-04-22

ขอ js ด้วยนะ |> ก้มกราบงามๆ

 
secret3056 2025-04-22

|> สวยมากจริงๆ

 
howudoin 2025-04-22

เป็นไวยากรณ์ที่ฉันเกลียดที่สุด
ต่อให้ stacktrace พันกันนิดเดียวก็ดีบักได้แย่มาก

 
cosine20 2025-04-25

จริงมาก

 
GN⁺ 2025-04-22
ความคิดเห็นจาก Hacker News
  • ผู้เขียนเรียกสิ่งนี้ว่า "pipelining" แต่คิดว่าคำที่ถูกต้องคือ "method chaining"

    • เทียบกับ pipeline แบบง่ายของ Bash: แต่ละองค์ประกอบทำงานแบบขนานและสตรีมผลลัพธ์ระหว่างทาง
    • ใน Ruby แต่ละบรรทัดจะถูกประมวลผลตามลำดับ และมีการสร้างอาร์เรย์เต็มขึ้นมาระหว่างแต่ละขั้น
    • ทำให้ดีบักยาก จึงทุกวันนี้เขียนโค้ดที่ชัดเจนตรงไปตรงมามากขึ้น
    • โค้ดที่ชัดเจนอาจดูไม่เรียบร้อยนัก แต่ตรวจสอบสถานะระหว่างทางได้ง่าย
  • โดยส่วนตัวสนับสนุนให้คงชุดความสามารถของภาษาให้เล็ก และทำให้ชุดความสามารถนั้นสมบูรณ์ได้อย่างรวดเร็ว

    • แต่ก็หวังให้ทุกภาษารับไวยากรณ์ |> ของ Elixir ไปใช้
  • มาโครของ Lisp มอบวิธีแก้ปัญหาทั่วไปที่สามารถกำหนดลำดับของ call chain ได้ ไม่ใช่แค่ตัวดำเนินการคอลเลกชันแบบต่อสาย

    • ตัวอย่างเช่น สามารถเขียน (foo (bar (baz x))) เป็น (-> x baz bar foo) ได้
    • รองรับกรณีที่มีอาร์กิวเมนต์เพิ่มเติมด้วย
    • รายละเอียดเพิ่มเติมดูคู่มือ threading macro ของ Clojure
  • เคยเรียนรู้ว่าคำนี้คือ fluent interface ส่วน pipelining เป็นอีกอย่างหนึ่ง

  • ตัวดำเนินการ pipeline เป็นรูปแบบหนึ่งของ partial application ซึ่งสามารถ bind อาร์กิวเมนต์หลายตัวเพื่อสร้างฟังก์ชันใหม่ แล้วส่งผลลัพธ์นั้นต่อไปยังฟังก์ชันอื่นได้

    • partial application มีประโยชน์มากในการเขียนโปรแกรม และสักวันหนึ่งภาษา (ที่ไม่ใช่ Haskell) ก็น่าจะใช้สิ่งนี้เป็นพื้นฐานของการประกอบโปรแกรม
  • ผู้ใช้ tidyverse ของ R ใช้สิ่งนี้กันอยู่แล้ว

  • pipelining ดีบักยาก จัดการข้อยกเว้นก็ยาก จึงต้องเพิ่มการแตกแขนงเข้าไปใน pipeline

    • pipeline มีประโยชน์เฉพาะเวลาที่เขียนโปรแกรมตาม happy path เท่านั้น
  • ไวยากรณ์ของ SQL ซับซ้อนเกินจำเป็น

    • SQL เป็นภาษาเชิงตัวดำเนินการอยู่แล้ว แต่มีข้อจำกัดมากจากเหตุผลทางประวัติศาสตร์
    • ถ้าจะยอมให้มีไวยากรณ์ใหม่ ก็สามารถเขียนให้เรียบง่ายกว่านี้ได้
    • ไวยากรณ์ |> ไม่ค่อยมีพลังในการแสดงออกและเพิ่มสิ่งรบกวนทางสายตา
  • ผู้เขียนอ้างว่า "ความหมายชนะไวยากรณ์" แต่กลับโฟกัสที่ความชอบด้านไวยากรณ์

    • pipelining ยิ่งเชนยาวก็ยิ่งดีบักยาก
    • วิจารณ์ Python แต่ไม่ได้ให้เหตุผลที่เป็นรูปธรรม
    • คำจำกัดความของ "pipelining" ไม่ชัดเจน
  • effect-ts ทำให้เขียนได้ทั้ง pipeline และโค้ดเชิงคำสั่ง

    • มีเอกสารเกี่ยวกับการเขียน pipeline และการใช้ generator
    • ชุมชนส่วนใหญ่ลงเอยด้วยการชอบ generator สไตล์เชิงคำสั่งมากกว่า
    • ดูเหมือนว่าจะดีบักและบำรุงรักษาได้ง่ายกว่า