Understanding Functors, Applicatives and Monads in Swift


นี่คือตัวเลขธรรมดาๆ อาจหมายถึงค่าใดๆ (value) หรืออาจถูกชี้ด้วยสิ่งที่เราเรียกว่า "ตัวแปร"
และพวกเรารู้วิธีการเรียกใช้ฟังก์ชั่นกับค่าดังกล่าว
จากนั้น ลองคิดว่าค่าดังกล่าวอยู่ใน Context สักที่หนึ่ง (เราอาจจะจินตนาการเป็นกล่องที่มีค่าใดๆ อยู่ด้านในก็ได้)
คราวนี้เมื่อเราพยายามจะเรียกใช้ฟังก์ชั่นกับค่าดังกล่าว เราอาจจะได้ผลลัพธ์ที่แตกต่างออกไปขึ้นอยู่กับ Context ที่มันอยู่ นี่คือพื้นฐานแนวคิดของ Functors, Applicatives และ Monads ซึ่ง Optional Type ใน Swift ประกาศ Context ไว้ 2 แบบนั่นคือ


Coding ....

อีกไม่นาน เราจะได้เห็นกันว่า .Some(T) กับ .None แตกต่างกันอย่างไร แต่ก่อนอื่นเรามาพูดถึง Functors กันก่อน

Functors

เมื่อค่านั้นๆ ถูกห่อหุ้มด้วย Context ใดๆ เราไม่สามารถที่จะนำค่านั้นมาใช้กับฟังก์ชั่นตรงๆ ได้

ดังนั้น map จึงเข้ามา (fmap ใน Haskell) map รู้วิธีว่าจะสามารถใช้ฟังก์ชั่นกับค่าที่อยู่ด้านในกล่องได้อย่างไร (ผมอาจจะพิมพ์ "กล่อง" กับ "Context" สลับไปมา ขอให้เข้าใจว่ามันคือสิ่งเดียวกัน) ตัวอย่างเช่น ถ้าเราต้องการใช้ฟังก์ชั่น "เพิ่มค่า 3" กับ .Some(2) (ค่า 2 ถูกอยู่ในกล่อง)

Coding ....

ซึ่งสามารถเขียนได้สั้นลงอีกแบบนี้โดยใช้ Swift's autoclosure

Coding ....

จะเห็นได้ว่า map จัดการให้เราได้ แต่... map รู้ได้ยังไงล่ะ ว่าจะต้องใช้ฟังก์ชั่นนั้นๆ อย่างไร ?
เพื่อค้นหาคำตอบ เรามาตอบคำถามนี้กันก่อน... จริงๆ แล้ว Functors คืออะไรล่ะ ?

Functor เป็นอะไรก็ได้ที่จะรู้ว่า map จะใช้ฟังก์ชั่นกับค่าที่อยู่ในกล่องได้อย่างไร นี่คือตัวอย่างการทำงานของ map (fmap ใน Haskell)

ซึ่งเราสามารถทำแบบนี้ได้

Coding ....

จะเห็นว่า map สามารถใช้ฟังก์ชั่นดังกล่าวได้ เพราะว่า Optional คือ Functor นั่นเอง หมายความว่า Optional จะรู้ว่าฟังก์ชั่น map ต้องทำงานกับ Some และ None ได้อย่างไร

Coding ....

คราวนี้มาดูกันว่าเกิดอะไรขึ้นเมื่อเราเขียนโค้ดดังนี้
Optional.Some(2).map { $0 + 3 }

แล้วถ้าใช้กับ None ล่ะ ? อะไรจะเกิดขึ้น

Coding ....


ฟังก์ชั่น map รู้แค่ว่าตัวเองต้องทำอะไร คราวนี้เราเข้าใจมากขึ้นไหมว่า Optional มีมาเพื่ออะไร ตัวอย่างเช่น ถ้าเราต้องทำงานกับ Database โดยไม่มีแนวคิด Optional แบบที่ทำกันอยู่ในภาษา Ruby แบบนี้

Coding ....

แต่ใน Swift เราจะใช้ Optional แบบนี้

Coding ....

อ่านได้ง่ายๆ ว่า ถ้า findPost(1) ส่ง post กลับมา เราจะได้รับ title ของ post ผ่านฟังก์ชั่น getPostTitle ถ้าหาแล้วไม่พบ post นั้นอยู่ ก็ไม่ต้องอะไรกลับมาเมื่อพยายามจะเรียกใช้ฟังก์ชั่น getPostTitle
ซึ่งจริงๆ เราสามารถเขียนในรูปแบบของ Custom Infix Operator สำหรับ map เป็น <^> ก็ได้ (<$> ใน Haskell) และเขียนแบบนี้แทน (ต้องใช้ <^> ไม่สามารถสร้าง <$> บน Swift ได้)

Coding ....

นี่เป็นอีกตัวอย่างนึง เมื่อเราพยายามจะเรียกใช้ฟังก์ชั่นให้กับ Array ใดๆ
จะเห็นว่าในกรณีนี้ Array ก็เป็น Functor เช่นกัน
ตัวอย่างสุดท้ายแล้วนะ จะเกิดอะไรขึ้น
"เมื่อเราพยายามจะเรียกใช้ฟังก์ชั่น กับฟังก์ชั่น" ดังโค้ดตัวอย่างด้านล่าง

Coding ....

นี่คือฟังก์ชั่น (รับค่าใดๆ และส่งค่าใดๆ กลับ)

และเมื่อเราพยายามทำแบบที่เรากล่าวถึงด้านบน
ผลลัพธ์ของมันก็คือ "ฟังก์ชั่น" อีกฟังก์ชั่นนึงนั่นเอง

Coding ....

ดังนั้น Function ใดๆ ก็เป็น Functor เช่นกัน (เนื่องจากฟังก์ชั่นทั้งสอง สามารถทำให้ฟังก์ชั่น map รู้ได้ว่าฟังก์ชั่น map ควรจะทำอะไรกับฟังก์ชั่นทั้งสอง) ภาพด้านบนที่เรากล่าวถึงเมื่อเราพยายามใช้ฟังก์ชั่น map กับพารามิเตอร์ที่เป็นฟังก์ชั่นทั้งคู่ มันคือการทำ Function Composition นั่นเอง

Applicatives

อีกระดับของ Functor และเช่นเดียวกันกับ Functor เราจะกล่าวถึงค่าใดๆ ของเราถูกใส่ไว้ในกล่อง

ที่ต่างออกไปคือ Function ของเราก็ถูกใส่เอาไว้ในกล่องเช่นเดียวกัน

Swift ไม่มี Feature ที่มารองรับแนวคิด Applicative ตรงๆ เหมือนภาษาอย่าง Haskell แต่มีวิธีง่ายๆ ที่จะเพิ่มเติมลงไปผ่าน Extension โดยเราสามารถเพิ่มฟังก์ชั่นชื่อ apply ให้กับทุกๆ type เพื่อให้รองรับแนวคิด Applicative ได้

Coding ....

ถ้า self (จากโค้ดด้านบน) และฟังก์ชั่นเป็น .Some เราจะสามารถใช้ฟังก์ชั่นนั้นกับค่าที่ได้จาก self และให้ค่า .None (nil) ในทางกลับกัน และอีกอย่าง เนื่องจาก Array และ Optional ถูกกำหนดในรูปแบบ Generic เช่น Optional<T> ดังนั้นจึงต้องใส่ Generic Type (<U> ในที่นี้) บนฟังก์ชั่น apply ด้วย

และเราจะทำ Custom Operator <*> สำหรับฟังก์ชั่น apply เพื่อทำกระบวนการนี้ด้วย

Coding ....


ตัวอย่างเช่น

Coding ....

และเราสามารถใช้ <*> ทำอะไรแบบนี้ผ่านแนวคิดของ Applicatives ได้

Coding ....

ในบทความต้นฉบับนั้นแสดงให้เราเห็นว่า Applicatives มีความสามารถมากกว่า Functors อย่างไร โดยกล่าวถึงความสามารถในการอนุญาติให้เรียกใช้ฟังก์ชั่นผลิตฟังก์ชั่นเพื่อเรียกใช้อีกทีหนึ่ง แน่นอนว่าบน Swift ก็ทำแบบนั้นไม่ได้ตรงๆ แต่สามารถทำได้ด้วยวิธี Function Currying
 นี่เป็นอีกตัวอย่างที่เราสามารถใช้ Applicatives ทำได้ แต่ใช้แนวคิดแต่ Functors ทำไม่ได้

Coding ....

แต่สามารถใช้แนวคิด Applicatives หรือ <*> (ฟังก์ชั่น apply) ที่เราเขียนเมื่อสักครู่แก้ปัญหาได้

Coding ....

ดูแล้ว Applicatives ชนะ Functors แบบขาดๆ เลย เพราะสามารถใช้ฟังก์ชั่นกับพารามิเตอร์กี่ตัวก็ได้ โดยจะใช้ <^> และ <*>  ในการกระทำการกับของที่ถูกห้อหุ่มอยู่ (ตัวแปร หรือฟังก์ชั่น)

Coding ....

จากที่เล่ามาทั้งหมด ก็ถึงเวลาของพระเอกแล้วล่ะนั่นคือ...

Monads
วิธีการในการเรียนรู้เกี่ยวกับ Monads

  1. จบปริญญาเอกสาขา Computer Science
  2. จากนั้นโยนมันทิ้งเพราะคุณอาจจะไม่ต้องใช้มันอีก
Monads เป็นอีกแนวคิดหนึ่ง
จากที่อ่านมา เรารู้ว่า Functors คือการใช้ฟังก์ชั่นกับค่าใดๆ ที่ถูกห่อหุ้มอยู่


ส่วน Applicatives นั้นเป็นการใช้ฟังก์ชั่นที่ถูกห่อหุ้มอยู่กับค่าใดๆ ที่ถูกห่อหุ้มอยู่

Monads นั้นจะทำหน้าที่ในการเรียกใช้ฟังก์ชั่นที่ส่งค่ากลับเป็นค่าที่ถูกห่อหุ้ม กับค่าใดๆ ที่ถูกห่อหุ้มอยู่ (เดี๋ยวดูภาพนะ แล้วจะเข้าใจมากขึ้น) โดยจะใช้ฟังก์ชั่นที่ชื่อว่า flatMap (liftM ใน Haskell) และเราจะสร้าง Custom Operator >>- (>>= ใน Haskell) แทนการเรียกใช้ flatMap

Coding ....

มาดูภาพตัวอย่างของ Monads กัน

ฟังก์ชั่นตัวอย่างที่เราจะนำมาใช้กับ Monads ก็คือ half (ให้คิดว่าเป็นฟังก์ชั่นที่จะกระทำการกับตัวเลขที่เป็นเลขคู่เท่านั้น ถ้าค่าที่ส่งเข้ามาไม่ใช่ แทนที่เราจะปล่อยให้มัน Error เราก็แค่ส่งค่าว่างปล่าวกลับไป)

Coding ....




แล้วถ้าเราพยายามจะใส่ค่าใดๆ ที่ถูกห่อหุ้มโดยไม่ผ่าน Monads ล่ะ
เราจำเป้นต้องใช้ >>- ที่เราได้เขียนขึ้น (>>= ใน Haskell) เป็นตัวช่วยในการดันกล่อง (ค่าใดๆ ที่ถูกห่อหุ้ม) เข้าไปในฟังก์ชั่น และนี่อาจจะเป็นหน้าตาที่แท้จริงของ >>-

ตัวอย่างการใช้งาน >>-

Coding ....

มาดูฟังก์ชั่นที่เราประกาศอีกที และดูกันว่ามันทำงานอย่างไร

Coding ....


ดังนั้น ที่เราร่ายยาวกันมาทั้งหมด 3 หัวข้อ สรุปได้ว่า Swift Optional ก็คือ Monads นั่นเอง และที่เป็นสิ่งที่เกิดขึ้นเมื่อเขียนโค้ด .Some(3)!
แล้วนึกภาพออกไหมว่าอะไรจะเกิดขึ้นเมื่อส่งค่า .None เข้าไป
ซึ่งสามารถนำมาต่อกันได้แบบนี้ด้วย

Coding ....

Conclusion

Swift Optional นั้นได้ถูกออกแบบให้มีทั้ง map และ flatMap เพื่อกระทำการบางอย่างกับค่าที่รับเข้ามา ดังนั้น Swift Optional มีแนวคิดทั้ง Functors, Applicatives และ Monads อยู่ในตัวมันเอง

เรามาดูความแตกต่างของทั้ง 3 อย่างที่เราได้ร่ายมายาวๆ อีกครั้งผ่านภาพนี้
  • Functor คือการใช้ฟังก์ชั่นกับค่าใดๆ ที่ถูกห่อหุ้มอยู่ โดยใช้ฟังก์ชั่น map
  • Applicative คือการใช้ฟังก์ชั่นที่ถูกห่อหุ้มอยู่ กับค่าใดๆ ที่ถูกห่อหุ้มอยู่ โดยใช้ฟังก์ชั่น apply
  • Monad คือการใช้ฟังก์ชั่นที่ส่งค่ากลับเป็นค่าที่ถูกห่อหุ้มอยู่ กับค่าใดๆ ที่ถูกห่อหุ้มอยู่ โดยใช้ฟังก์ชั่น flatMap

credit: http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures/

Popular posts from this blog

12 วิธี การบริการและดูแลลูกค้าในร้าน Starbucks

"อีสุกอีใส" ประสบการณ์เมื่อต้องมาเป็นตอนอายุ 22

[Android Dev] การติดตั้ง Eclipse+AndroidSDK เพื่อพัฒนาโปรแกรมบน Android