ไวยากรณ์การเขียนโปรแกรมที่ผมชอบที่สุด: “Pipelining”
(herecomesthemoon.net)- 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 ความคิดเห็น
https://github.com/tc39/proposal-pipeline-operator
ไม่ว่าจะเป็นข้อความแบบใดก็ตาม
การขึ้นบรรทัดใหม่และการเยื้องย่อหน้าก็สำคัญต่อความอ่านง่าย
น่าจะอยู่ในบริบทที่คล้ายกันครับ
LINQ เจ๋งที่สุด!
Gleam ก็รองรับสิ่งนี้เหมือนกัน เลยเขียนโค้ดได้ค่อนข้างสะอาดดีครับ
ว่าแต่ สงสัยเพราะมีโค้ดบล็อกอยู่ในเนื้อหา บนมือถือเลยแสดงผลเป็นเลย์เอาต์แบบเดสก์ท็อปด้วยนะครับ
คิดไปคิดมา elm ก็ทำได้เหมือนกันนะ
สำหรับข้อมูลปริมาณน้อยและโค้ดระดับง่าย ๆ แบบตัวอย่างข้างบน ผมคิดว่ามันก็ดูดีและไม่ได้มีข้อเสียอะไรครับ
แต่พอเริ่มมีโค้ดค่อย ๆ เข้าไปอยู่ใน
map()... ก็มีแนวโน้มจะทำให้โค้ดค่อย ๆ อ้วนขึ้นแม้จะขึ้นอยู่กับภาษาและไลบรารีที่ใช้ แต่ถ้าปริมาณข้อมูลมากขึ้น มันก็อาจช้าลงได้ง่าย ๆ ถึงระดับหลายพันเท่าเมื่อเทียบกับการประมวลผลด้วยการสะสมหรือจัดการข้อมูลตรง ๆ ในโครงสร้างข้อมูล
แล้วก็ยังมีอีกเหตุผลใหม่หนึ่งที่ทำให้ผมไม่ค่อยชอบมัน คือพอเปิดบทความนี้บนมือถือแล้ว ความกว้างแบบระดับ PC ยังถูกคงไว้เหมือนเดิม ทำให้ตัวหนังสือเล็กจิ๋วมากจนอ่านบทความยากมากจริง ๆ ครับ T.T
โดยพื้นฐานแล้วผมไม่ได้ชอบมัน และก็ไม่ได้พยายามจะเขียนให้เป็นแบบนั้นโดยตั้งใจครับ
ขอ
jsด้วยนะ |> ก้มกราบงามๆ|>สวยมากจริงๆเป็นไวยากรณ์ที่ฉันเกลียดที่สุด
ต่อให้
stacktraceพันกันนิดเดียวก็ดีบักได้แย่มากจริงมาก
ความคิดเห็นจาก Hacker News
ผู้เขียนเรียกสิ่งนี้ว่า "pipelining" แต่คิดว่าคำที่ถูกต้องคือ "method chaining"
โดยส่วนตัวสนับสนุนให้คงชุดความสามารถของภาษาให้เล็ก และทำให้ชุดความสามารถนั้นสมบูรณ์ได้อย่างรวดเร็ว
|>ของ Elixir ไปใช้มาโครของ Lisp มอบวิธีแก้ปัญหาทั่วไปที่สามารถกำหนดลำดับของ call chain ได้ ไม่ใช่แค่ตัวดำเนินการคอลเลกชันแบบต่อสาย
เคยเรียนรู้ว่าคำนี้คือ fluent interface ส่วน pipelining เป็นอีกอย่างหนึ่ง
ตัวดำเนินการ pipeline เป็นรูปแบบหนึ่งของ partial application ซึ่งสามารถ bind อาร์กิวเมนต์หลายตัวเพื่อสร้างฟังก์ชันใหม่ แล้วส่งผลลัพธ์นั้นต่อไปยังฟังก์ชันอื่นได้
ผู้ใช้ tidyverse ของ R ใช้สิ่งนี้กันอยู่แล้ว
pipelining ดีบักยาก จัดการข้อยกเว้นก็ยาก จึงต้องเพิ่มการแตกแขนงเข้าไปใน pipeline
ไวยากรณ์ของ SQL ซับซ้อนเกินจำเป็น
|>ไม่ค่อยมีพลังในการแสดงออกและเพิ่มสิ่งรบกวนทางสายตาผู้เขียนอ้างว่า "ความหมายชนะไวยากรณ์" แต่กลับโฟกัสที่ความชอบด้านไวยากรณ์
effect-ts ทำให้เขียนได้ทั้ง pipeline และโค้ดเชิงคำสั่ง