มารู้จักกับ Function Composition กัน

Function Composition in JavaScript

Pallop Chaoputhipuchong
Jitta Engineering
4 min readSep 18, 2017

--

เกริ่นนำ

ในปัจจุบัน FP (Functional Programming) เข้ามามีบทบาทมากขึ้นเรื่อย ๆ ในโลกของ JavaScript

สิ่งที่ผมจะมานำเสนอในวันนี้คืออีก 1 พื้นฐานสำคัญ ในโลกของ FP ที่เรียกว่า Function Composition

Function Composition

คือกระบวนการรวมกันของ functions มากกว่า 1 ขึ้นไป และก่อให้เกิด function ใหม่ขึ้นมา

หรือรูปแบบหนึ่งที่ใช้ในการอธิบายที่ง่ายที่สุดก็คือ f(g(x)) หรือการรวมกันของ function f และ g มากระทำกับ x

Compose Function

วิธีที่เราจะใช้ในการรวม function เข้าด้วยกัน นั้นเรียกว่า compose function

ก่อนอื่นเรามาดูโค้ดตัวอย่างการเขียน compose function กันก่อน

หรือเขียนใน syntax es6 ได้โดย

ภายในบทความนี้ผมจะใช้ arrow function แทนการเขียน function เพื่อความกระชับของโค้ดนะครับ หากใครไม่ทราบการทำงานของ arrow function สามารถอ่านเพิ่มเติมได้ ที่นี่ ครับ

จากโค้ดข้างต้น การทำงานของ compose function คือการรับ parameters ที่เป็น function เข้ามา 2 ตัว (ตัวแปร f และ g) และทำการคืน function ใหม่ออกไป

หาก function ใหม่มีการเรียกใช้

สิ่งที่เกิดขึ้นคือ มันจะทำการ apply functions ก่อนหน้านี้ที่ใส่เข้ามา (f และ g) กับ input (ตัวแปร x)

โดยค่อย ๆ apply functions ไล่ไปจาก ขวา ไป ซ้าย

x ถูก apply กับ function g ก่อน และผลลัพธ์ทั้งหมดค่อยถูกนำไป apply กับ function f ในภายหลัง

ตัวอย่าง

จินตนาการว่า เราจะสร้าง function ทำความสะอาด String ก่อนนำไปใช้งาน (sanitize function)

ขั้นตอนการทำงาน

  1. ตัดช่องว่างที่ไม่จำเป็นหน้าหลังของคำ (trim)
  2. แปลงคำทั้งหมดเป็นตัวพิมพ์เล็ก (trim)

สามารถเขียนเป็นโค้ดแบบปกติได้ดังนี้

ทีนี้ เราลองเขียนแบบ FP กันดู

อธิบายเพิ่มเติมนิดนึง ในการเขียนแบบ FP จากตัวอย่างด้านบน ผมได้มีการใช้งาน function ที่สร้างขึ้นมาเอง 2 ตัว คือ trim และ toLowerCase

เหตุผลที่ผมทำการสร้าง 2 ตัวนี้ขึ้นผม เนื่องจากการเขียนแบบ FP นั้น เราจะเขียนโดยให้ function ที่ใช้งานนั้นมีคุณสมบัติที่สามารถ compose ได้ (composable function)

วิเคราะห์

ทีนี้ลองสังเกตดูจากโค้ดแบบ FP ด้านบนนะครับ การทำงานของมัน มีความคล้ายคลึงกับอะไรบางอย่างที่ผมได้อธิบายไปก่อนหน้านี้ไหมครับ?

.

.

.

คำตอบนั่นก็คือ มันเหมือนกันกับ compose function ที่ผมได้อธิบายไปก่อนหน้านี้นั่นเองครับ

ลองแทนค่า functions ต่าง ๆ ดังต่อไปนี้นะครับ

  • toLowerCase = f
  • trim = g
  • str = x

เมื่อทั้งหมดมารวมกัน
จากบรรทัด toLowerCase(trim(str))
ก็จะมีค่าเหมือนกันกับ f(g(x))

ซึ่ง หมายความว่าเราสามารถนำเอา compose function มาช่วยในกรณีนี้ได้ดังนี้

สำหรับ composable function ทั้งหลายแหล่จริง ๆ แล้ว เราไม่จำเป็นต้องเขียนเองก็ได้นะครับ เราสามารถทำได้โดยการ import จาก library ดัง ๆ อย่าง lodash/fp หรือ ramda เอาก็ได้นะครับ

แล้วทำไมเราต้อง compose ล่ะ?

ข้อดีของการสร้าง function ย่อย ๆ และ compose เข้าด้วยกัน คือมันจะทำให้เราสามารถตรวจสอบ หรือควบคุม data pipeline

กล่าวคือ flow หรือความเปลี่ยนแปลงที่จะเกิดขึ้นกับ data ได้

รวมไปถึงยังเป็นส่วนช่วยให้ function ต่าง ๆ ของเรา สามารถนำกลับมาใช้ใหม่ (reusuable) ได้โดยง่ายอีกด้วย

Compose กับการใช้งานจริง

ในการใช้งานจริง เราอาจจะไม่จบแค่การ compose functions เพียง 2 functions

แต่อาจจะ compose functions ไป 3–4 functions หรือมากกว่านั้นก็ได้

เพราะฉะนั้น เราลองมาเขียน compose function ที่เหมาะแก่การใช้งานจริง โดยรองรับการ compose functions ได้อย่างไม่จำกัด ตามแต่ตัวแปรที่ใส่เข้ามาดูครับ

ทำได้โดย

เท่านี้เอง เราก็จะสามารถ compose functions กี่ตัวเข้าไปแล้วก็ได้ครับ

ตัวอย่าง

ทีนี้เรามาดูตัวอย่างการใช้งานจริงที่ยากขึ้นกันบ้าง

จินตนาการว่าเราต้องการสร้าง function ที่รับ String เข้ามา และเปลี่ยนมันออกมาให้เป็น slug

function ที่ผมจะทำนั้นจะเป็น slug แบบง่าย ๆ ประกอบไปด้วยขั้นตอนดังต่อไปนี้

  1. ตัด ช่องว่าง (space) หน้าหลัง
  2. แยกคำแต่ละคำออกมาจากกัน
  3. เชื่อมคำกลับเข้าไปด้วยกัน ขีด (-)
  4. แปลงคำทั้งหมดเป็นตัวพิมพ์เล็ก (lower case)

ซึ่งสามารถเขียนเป็นโค้ดปกติง่าย ๆ ได้ดังนี้

ปกติ

Functional Programming

งั้นเรามาลองเขียนกันแบบ FP ดูดีกว่า

โอ้ว~ เลวร้ายกว่าเดิมอีก nesting กันเป็นแถบ เขียนโค้ดแบบนี้ไม่ดีแน่ อ่านยากสุด ๆ

Functional Programming with Compose

งั้นเราลองนำเอา compose มาช่วยดู

อืมม์ ดูดีขึ้นเป็นกอง…. แต่ช้าก่อน!!!

เราลองมาอ่านโค้ดกันดูดี ๆ ก่อนนะครับ

ในมุมมองของคณิตศาสตร์ compose นั้นก็เป็นอะไรที่ดีงาม แต่ในเรื่องของการอ่านโค้ดนั้น compose กลับไม่ใช่ทางเลือกที่ดีที่สุด

เพราะอะไร?

หากเรามองย้อนกลับขึ้นไปที่โค้ดข้างบน เพื่อน ๆ ลองพยายามอ่านดูกันนะครับว่า function toSlug นั้นมีขั้นตอนการทำงานยังไงบ้าง

1…. 2…. 3…..

การที่เราจะรู้ขั้นตอนการทำงานของมัน step 1, 2, 3 ไล่ไปได้นั้น

อย่างที่ผมกล่าวไปก่อนหน้า จากการ compose เราต้องเริ่มอ่านไล่จาก ขวา ไป ซ้าย (หรือในที่นี้ผมขึ้นบรรทัดใหม่ก็คือจาก ล่าง ขึ้น บน)

เช่นในที่นี้ ขั้นตอนที่เกิดขึ้นจากการ compose

คือ trim -> split(' ') -> join('-') -> toLowerCase

ซึ่งแน่นอนว่าการอ่านโค้ดย้อนกลับแบบนี้นั้น ผมคิดว่ามันเป็นอะไรที่ไม่น่าอภิรมย์ซักเท่าไหร่เลยครับ

งั้นเราจะทำยังไง?

แทนที่ เราจะเขียนโค้ดและเรียกใช้งาน compose ให้ต้องมานั่งอ่านย้อนกลับ

เราจะใช้อีกสิ่งหนึ่งที่ทำให้เราสามารถอ่านโค้ดอย่างลื่นไหล ไล่ไปเรื่อย ๆ โดยง่าย ซึ่งเราจะเรียกกันว่า pipe นั่นเองครับ

Pipe คืออะไร?

หากใครเคยเขียน หรือใช้งาน shell script มาบ้าง น่าจะเคยผ่านตาในการใช้งาน pipe เรียกใช้งานโดย โดยใน shell script จะใช้งานโดยอักขระ |

ลักษณะการทำงานของ pipe คือ การส่งต่อ ผลลัพธ์ ที่ได้จากการ คำสั่งก่อนหน้า ไปให้แก่ คำสั่งด้านหลัง

ซึ่งอธิบายง่าย ๆ pipe นั้นมีการทำงาน เหมือนกัน กับ compose นั้นเอง เพียงแต่ว่าการนำเอา function เข้ามา apply นั้น สลับกันจาก

  • compose: apply function จาก ขวา ไป ซ้าย
  • pipe: apply function จาก ซ้าย ไป ขวา

เราลองมา implement pipe พร้อมเปรียบเทียบกับ compose ได้กันดูครับ โดย

เพราะฉะนั้นจากโค้ด function toSlug ก่อนหน้า

เราสามารถเปลี่ยนใหม่โดยใช้ pipe กลายเป็น

เป็นไงครับ? อ่านง่ายขึ้นเป็นกองเลยใช่ไหมล่า~

Data Pipeline

ข้อดีหนึ่งที่ผมได้กล่าวเป็นก่อนหน้าของการ compose function หลาย ๆ ตัวเข้าด้วยกันคือ

เราสามารถ ตรวจสอบ และควบคุม data pipeline หรือ flow การทำงานต่าง ๆ ที่เกิดขึ้นได้โดยง่าย

เช่นยังไง?

เพิ่มขั้นตอน

จินตนาการต่อว่า function toSlug ของเรานั้น เราต้องการ เพิ่มขั้นตอนการลบ อักขระพิเศษ เข้าไปใน flow การทำงานด้วย

เราสามารถเพิ่มโค้ดเข้าไปง่าย ๆ ได้โดย

ก็เป็นอันเสร็จครับ

เช่นเดียวกัน หากว่าต้องการลดการทำงานส่วนไหนชั่วคราว ก็แค่ไป comment ออกแค่นั้นเองครับ

ตรวจสอบ

อีกสิ่งหนึ่งที่ผมพูดถึงคือ การตรวจสอบ

เช่นผมต้องการทราบผลลัพธ์หลังการ map ผมสามารถทำได้โดย function หนึ่งเรียกว่า trace

และใช้งานโดยการเพิ่มเข้าไปใน pipeline ดังนี้

Conclusion

หลังจากอ่านบทความนี้จบ ผมคาดว่าเพื่อน ๆ หลายคน คงเริ่มเข้าใจ concept หรือการทำงานของ FP (Functional Programmign) มากขึ้นไม่มากก็น้อยแล้วนะครับ

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

หากใครมีคำถาม หรือติชมอะไร ก็มาคอมเม้นท์พูดคุยกันได้ข้างล่างนี้นะครับ

สุดท้ายนี้หวังว่าบทความนี้จะช่วยทำให้เพื่อน ๆ เข้าใจ และหลงใหลใน FP กันยิ่งขึ้นไปนะครับ 👏 ขอบคุณครับ 👏

--

--