August 01, 2015

Swift Type inference กับการลดรูป Closure

Closures คล้ายกับ functions และ method มันคือ block ของโค้ดที่เราเรียกใช้มัน แต่ไม่ได้เหมือนกันซะทีเดียว เพราะ closure ไม่มีชื่อ และมีความสามารถบางอย่างต่างออกไป เช่นการ capture value ที่เกิดใน scope เดียวกัน (บทความนี้ไม่ได้พูดถึง)

สำหรับหลายคนเมื่อเห็น syntax ประมาณนี้จะเกิดความสงสัยว่า โค้ดแบบนี้มาจากไหน

myArray.sorted{$0 > $1}

ซึ่งบทความนี้จะเล่าถึงที่มาที่ไปของมัน เริ่มจากลองดูตัวอย่างจาก method sorted ของ Array กันครับ

func sorted(isOrderedBefore: (T, T) -> Bool) -> [T]

method นี้มี parameter  1 ตัวชื่อ isOrderedBefore ซึ่งเป็น function ที่รับ parameter 2 ตัวชนิด T (ชนิดของข้อมูลที่ array บรรจุอยู่)  และ return Bool กลับออกมา ซึ่ง method sorted จะ return Array ที่เรียงเสร็จแล้วกลับออกมาอีกที

จากนิยามของ method sorted เมื่อเราลองลองสร้าง function เพื่อใช้เป็น parameter ตามที่นิยาม (รับ parameter 2 ตัว และ return เป็น Bool) ดู ก็จะได้โค้ดออกมาแบบนี้


ลองรันโค้ดดูจะพบว่า ได้ array ที่เรียงกันตามตัวอักษรถูกต้อง โดยอิงตามกฎที่ว่า ใครมาทีหลัง ให้มาก่อน

ในกรณีนี้ การประกาศ function isBefore เพื่อใช้เป็น parameter ให้กับ method sorted เพียงอย่างเดียวอาจจะดูไม่สวยงามนัก เพราะโค้ดก็ไม่ได้ยืดยาวอะไร ถ้าอย่างนั้น เราก็เอาโค้ดของ function isBefore ใส่ลงไปตรงๆ ใน method sorted เลยแล้วกัน ก็จะได้โค้ดแบบนี้


ผลลัพธ์ยังเป็นเหมือนเดิม
จากโค้ดแทนที่เราจะใส่ function reference เป็น parameter ให้กับ method sorted ก็ใส่เป็น closure ไปเลย ซึ่งใน context นี้ เราสามารถคิดได้ว่าจริงๆ แล้ว closure ก็คือ anonymouse function นั่นเอง ซึ่ง closure ที่ใส่ลงไปหน้าตาจะคล้ายกับ function isBefore แทบทุกอย่าง ยกเว้นแต่กรณีนี้มีคำสั่ง in เพิ่มเข้ามาด้วย โดยตามหลักแล้ว expression ที่ต่อจาก in ก็จะเป็น closure implementation

Swift compiler มีความสามารถในการระบุได้ว่าตัวแปรตรงนี้ สมควรมี type เป็นอย่างไรโดยพิจารณาจาก context เรียกเท่ๆ ว่า  Type Inference ทำให้ในบางจุดเราไม่จำเป็นต้องระบุ type แต่ compiler ก็สามารถเข้าใจได้ และนี่คือตัวอย่างที่เรานำความสามารถนี้ทำให้โค้ดสั้นลงได้


จากโค้ดด้านบน compiler สามารถ infer (อนุมาน) ได้ว่า type ของตัวแปร one และ two คืออะไร ในที่นี้คือ String โดยอิงจาก signature ของ method sorted ดังนั้นเราสามารถตัด type ออกไปได้เลย

closure นี้มี statement เดียวเป็นผลลัพธ์ที่ return ออกมา ดังนั้นเราสามารถตัด return ออกไปได้ จะเห็นว่าโค้ดสั้นลงอีกนิดนึงแล้วดังนี้


และถ้าดูจาก signature ของ method sorted แล้ว return type ของ closure นี้ compiler ก็ยังสามารถ infer ต่อได้อีกว่า มันต้อง return ออกมาเป็น Bool แน่ๆ ดังนั้น เราสามารถละโค้ดตรงนี้ไว้ในฐานที่เข้าใจได้  รวมทั้ง วงเล็บรอบๆ paramter one, two ก็ยังสามารถตัดออกไปได้อีก ก็จะเหลือโค้ดแบบนี้


คราวนี้จะตัดอะไรได้อีก? สามารถตัดการประกาศ closure parameter ออกไปได้ โดยเปลี่ยนไปใช้ local shorthand constant แทน ก็จะเหลือแบบนี้


จากโค้ด ในกรณีที่เราไม่ประกาศชื่อของ parameter แล้ว swift จะเตรียม local constant ให้ $0 ก็คือ parameter ตัวแรก $1 ก็คือตัวถัดไปตามลำดับ และเท่าที่ดูนี่คือตัดออกไปจนเหลือสั้นมากแล้ว ซึ่งจริงๆ แล้วเรายังตัดได้อีก คือ ในกรณีที่ closure ถูก pass เข้าไปเป็น argument ตัวสุดท้ายของ function หรือ method แล้ว เราสามารถเอา closure วางไว้ข้างนอกวงเล็บได้ วิธีนี้เรียกว่า trailing closure ก็จะได้โค้ดออกมาแบบนี้


และสุดท้าย เราสามารถตัดวงเล็บว่างๆ ทิ้งไปได้ก็จะได้โค้ดที่สั้นที่สุดออกมาแบบนี้


ถึงตรงนี้ ลองย้อนกลับไปดูแล้วพบว่า function isBefore ถูกตัดออกไปเยอะมากเลยทีเดียว :)
นอกจากที่กล่าวมานี้ Closure ยังมีสิ่งที่น่าสนใจอื่นๆ อีก เช่น Capturing Values สามารถอ่านต่อได้จากที่นี่เลย The Swift Programming Language: Closure