เรียนรู้ Functional Python ภายใน 10 นาที

28-ก.ย.-18

คัมภีร์เทพ IT

ในบทความนี้คุณจะได้เรียนรู้ว่า Functional Paradigm เป็นอย่างไร รวมถึงวิธีการใช้ Functional Programming ใน Python รวมทั้ง List Comprehensions และรูปแบบอื่นๆ ของ Comprehensions อีกด้วย เรามาเริ่มเรียนรู้ Functional Python ภายใน 10 นาที กันเลย

Functional Paradigm

ใน Imperative Paradigm คุณจะทำสิ่งต่างๆ ได้ โดยการบอกลำดับของงานไปยังคอมพิวเตอร์ จากนั้นถึงทำการ Execute ในขณะที่ Execute มันสามารถเปลี่ยน State ได้ เช่น คุณ Set ค่า A เป็น 5 จากนั้นก็เปลี่ยนค่าของ A ไป แต่ใน Functional Paradigm, คุณไม่ต้องบอกคอมพิวเตอร์ว่าต้องทำอะไร ด้วยเหตุนี้ ตัวแปรจึงไม่สามารถเปลี่ยนแปลงได้ เมื่อคุณตั้งค่าตัวแปร มันจะเป็นอย่างนั้นไปตลอด (ใน Purely Functional Languages มันจะไม่ถูกเรียกว่า ตัวแปร) และด้วยเหตุนี้ Function จะไม่มี Side Effect ต่อ Functional Paradigm ซึ่ง Side Effect ในที่นี้คือ การที่ Function ได้ไปเปลี่ยนแปลงค่าบางอย่างที่อยู่นอก Function ลองดูตัวอย่างของ Code Python ด้านล่าง:

Output ของ Code นี้คือ 5 ใน Functional Paradigm การเปลี่ยนค่าตัวแปรเป็นสิ่งที่ไม่ควรทำและการที่ Function ส่งผลกระทบต่อสิ่งที่อยู่ภายนอกก็ยิ่งเป็นสิ่งที่ไม่ควรจะเป็นอีกด้วย สิ่งเดียวที่ Function สามารถทำได้ คือ การคำนวณบางสิ่งบางอย่างและ Return ค่ากลับมา คุณอาจกำลังคิดว่า “เมื่อ Function ไม่มีตัวแปรและไม่มี Side Effects หลังจากการเรียก function นั้นแล้วทำไมถึงเป็นเรื่องที่ดี นี่เป็นคำถามที่ดี ลองอ่านด้านล่างดูสิ

ถ้า Function ถูกเรียก 2 ครั้งด้วย Parameter เดียวกัน ก็รับประกันได้ว่า จะให้ผลลัพธ์เหมือนกัน ถ้าคุณได้เรียนรู้เกี่ยวกับ Function ทางคณิตศาสตร์ คุณก็จะรู้ถึงประโยชน์ของข้อดีนี้ สิ่งนี้เรียกว่า Referential Transparency เนื่องจาก Function ไม่มี Side Effect หากคุณกำลังสร้าง Program คำนวณค่าต่างๆ คุณสามารถเพิ่มความเร็วของ Program ได้ ถ้า Program ทราบว่า func(2) ได้เท่ากับ 3 เราสามารถเก็บข้อมูลนี้ไว้ใน Table ได้ ซึ่งจะช่วยป้องกัน Program ไม่ให้ใช้งาน Function เดียวกันซ้ำๆ 

โดยทั่วไปใน Functional Programming เราไม่ใช้ Loops แต่เราจะใช้ Recursion ซึ่ง Recursion เป็น Mathematical Concept ที่ปกติแล้วจะหมายถึง การป้อนข้อมูลและเรียกตัวเองซ้ำ และจาก Recursive Function, Function นี้จะเรียกตัวเองว่าเป็น Sub-Function และนี่เป็นตัวอย่างของ Recursive Function ใน Python:

Map

เพื่อทำความเข้าใจกับ Map ต้องมาดูกันก่อนว่า Iterables เป็นอย่างไร Iterables คือการทำอะไรซ้ำๆ โดยปกติจะทำกับ List หรือ Array แต่ใน Python มี Iterables หลายประเภท คุณยังสามารถสร้าง Object ของคุณเองซึ่งสามารถใช้ในการ Iterate ได้ด้วยการใช้ Magic Method ซึ่ง Magic Method ก็เป็นเหมือน API ที่ช่วยให้ Object ของคุณกลายเป็น Pythonic มากขึ้น คุณจำเป็นต้องใช้ 2 Magic Methods ในการทำให้ Object สามารถทำ Iterables ได้:

ใน Magic Method ตัวแรก “__iter__” หรือ dunder iter (double underscore iter) จะ Return ตัว Iiterative Object ซึ่งมักใช้เมื่อเริ่ม Loop แล้ว Dunder ถัดไปจะ Return ว่า Object ถัดไปคืออะไร

ลองดูที่ตัวอย่างนี้

สิ่งที่ Print ออกมาคือ:

สำหรับ Python แล้ว Iterator คือ Object ที่มีเพียง __iter__ Magic Method ซึ่งหมายความว่า คุณสามารถเข้าถึง Position ใน Object แต่ไม่สามารถ Iterate ผ่าน Object ได้ Object บางอย่างจะมี Magic Method __next__ และ ไม่ใช่ __iter__ Magic Method เช่น Sets สำหรับบทความนี้เราจะสมมติว่าทุกอย่างที่เราเกี่ยวข้อง คือ Iterable Object 

ตอนนี้เรารู้แล้วว่า Iterable Object คืออะไร ตอนนี้เรากลับไปที่ Map Function กัน Map Function ช่วยให้เราสามารถใช้ Function กับทุก Item ใน Iterable โดยปกติเราต้องการใช้ Function กับทุก Item ใน List แต่ทราบว่ามันก็เป็นไปได้สำหรับ Iterables ส่วนใหญ่ ใน Map จะใช้ 2 Inputs คือ Function ที่จะนำไปใช้และ Iterable Object

สมมติว่าเรามี List ของตัวเลขดังนี้:

และเราต้องการยกกำลัง 2 ในทุกตัวเลข เราสามารถเขียน Code ได้ดังนี้:

Functional Functions ใน Python มีความ Lazy อยู่ ถ้าเราไม่ได้รวม "list ()" Function จะเก็บ Definition ของ Iterable ไม่ใช่List ของตัวเอง เราจำเป็นต้องบอก Python ให้ชัดแจ้ง ว่า "เปลี่ยนไปเป็น List" 

เรื่องน่าแปลกในการไปจาก Non-Lazy Evaluation เป็น Lazy Evaluation ทั้งหมดโดยทันทีใน Python ในที่สุดคุณจะเคยชินกับมัน ถ้าคุณคิดเพิ่มเติมเกี่ยวกับ Functional Mindset มากกว่า Imperative Mindset 

มันดีที่จะเขียน Function ทั่วไป อย่าง "square (num)" แต่ดูไม่ค่อยถูกต้อง เราต้องกำหนด Function ทั้งหมดเพียงเพื่อใช้ครั้งเดียวใน Map อย่างงั้นเหรอ? เราสามารถกำหนด Function ใน Map โดยใช้ Function Lambda (anonymous)

Lambda expressions

Lambda Expression เป็น “one line function” จากตัวอย่างด้านล่างนี้เป็น Lambda Expression ที่เป็นเลขยกกำลัง 2:

ลอง Run เพื่อดูผลลัพธ์นี้ดู:

คุณอาจจะงงว่าแล้วไหน Arguments ล่ะ เรื่องนี้สามารถอธิบายได้ ดังนั้น เราจึง Assign บางอย่างให้กับตัวแปร "square" ตามนี้:

เรากำลังบอก Python ว่านี่เป็น Lambda Function และมี Input เรียกว่า x ส่วนสิ่งที่อยู่หลัง : คือสิ่งที่คุณทำกับ Input ซึ่งจะ Return ค่าผลลัพธ์มาให้โดยอัตโนมัติ
เพื่อลดความซับซ้อนของ square program ของเราให้เหลือ หนึ่งบรรทัด เราสามารถทำได้โดย:

ดังนั้นใน Lambda Expression, Arguments ทั้งหมดจะอยู่ด้านซ้ายและสิ่งที่คุณต้องการจะทำกับพวกมันจะอยู่ทางด้านขวา ทำอาจดูยุ่งยากเล็กน้อย แต่ก็ไม่มีใครปฏิเสธได้ ความจริงก็คือ มีความน่ายินดีในการเขียน Code ที่ Functional Programmers คนอื่นๆ สามารถอ่านได้ นอกจากนี้มันก็ดีมากที่ใช้ Function และเปลี่ยนมันให้อยู่ในบรรทัดเดียว

Reduce

Reduce เป็น Function ที่เปลี่ยน Iterable ให้เป็นอีกสิ่งหนึ่ง โดยปกติคุณจะคำนวณใน List เพื่อลดจำนวนมันให้เหลือจำนวนเดียว ซึ่ง Reduce มีลักษณะแบบนี้:

เราสามารถ (และมักจะ) ใช้ Lambda Expression เป็น Function
จากตัวอย่างด้านล่าง Product ของ List คือทุกตัวเลขที่คูณกัน ซึ่งการจะทำแบบนี้ สามารถเขียน Program ได้เป็น

แต่ด้วย Reduce คุณสามารถเขียนได้เป็น:

เพื่อให้ได้ Product ที่เหมือนกัน Code สั้นลงและด้วยความรู้ของ Functional Programming มันมีความเป็นระเบียบมากขึ้น

Filter

Filter Function ใช้ Iterable และกรองเอาข้อมูลที่ไม่ต้องการใน Iterable โดยปกติ Filter จะใช้ Function และ List มันใช้ Function กับแต่ละ Item ใน List และถ้า Function นั้น Return ค่าเป็น True มันจะไม่มีผลอะไร ถ้ามันส่งกลับเป็น False ก็จะ Remove Item นั้นออกจาก List 
Syntax ของมัน มีลักษณะแบบนี้:

ลองดูตัวอย่างนี้ กรณีไม่มี Filter จะเขียนได้เป็นแบบนี้:

แต่พอมี Filter จะเขียนได้แบบนี้:

Higher order functions

Higher Order Functions สามารถใช้ Function เป็น Parameters และ Return Function กลับ ลองมาดูตัวอย่างดังนี้:

ส่วนนี่คือตัวอย่างของการ Return Function กลับ

คุณคงรู้มาก่อนหน้านี้แล้วว่า Pure Functional Programming Languages ไม่มีตัวแปรใช่ไหม? Higher Order Functions เป็นสิ่งที่ทำให้ง่ายขึ้น คุณไม่จำเป็นต้องเก็บตัวแปรไว้ที่ไหนเลย หากทุกสิ่งที่คุณทำ เป็นการส่งผ่านข้อมูลผ่าน Function 
Function ทั้งหมดใน Python เป็น First Class Objects ซึ่ง First Class Objects ถูกกำหนดให้มี อย่างน้อยหนึ่งใน Feature เหล่านี้:

  • สร้างขึ้นในขณะ Runtime
  • กำหนดค่าตัวแปร หรือ Element ใน Data Structure
  • ส่งผ่านเป็น Argument ไปยัง Function
  • Return เป็นผลลัพธ์ของ Function อื่น

ดังนั้น Function ทั้งหมดใน Python เป็น First Class และสามารถใช้เป็น Higher Order Function

Partial application

Partial Application (หรือที่เรียกว่า closures) แม้จะแปลกเล็กน้อยแต่มันก็เป็นสิ่งที่ยอดเยี่ยม คุณสามารถเรียกใช้ Function ได้โดยไม่ต้องมี Arguments ลองดูในตัวอย่างนี้ เราต้องการสร้าง Function ซึ่งใช้ 2 Arguments คือ ฐาน และ เลขยกกำลัง แล้ว Return ผลของการคำนวนเลขยกกำลังออกไป:

ตอนนี้เราต้องการที่จะมี Square Function เพื่อคำนวณการยกกำลังของตัวเลขโดยใช้ Power Function:

แต่ถ้าสิ่งที่เราต้องการคือ Cube Function? หรือ Function ที่ยกกำลัง 4? เราสามารถเขียนมันต่อไปได้ตลอดไปหรือไม่? คุณสามารถทำได้ แต่ Programmer มักจะขี้เกียจ ถ้าคุณทำซ้ำสิ่งเดียวกันซ้ำแล้วซ้ำอีก ถือเป็นสัญญาณว่ามีวิธีเร่งความเร็วให้มากขึ้นและจะช่วยให้คุณไม่ต้องทำมันซ้ำๆ เราสามารถใช้ Partial Applications ได้ ลองดูตัวอย่างของ Square Function โดยใช้ Partial Application ดังนี้:

มันเยี่ยมใช่ไหมล่ะ เราสามารถเรียกใช้ฟังก์ชันที่ต้องการ 2 Arguments, ใช้เพียง 1 Argument โดยบอก Python ว่า Argumentที่ 2 คืออะไร
นอกจากนี้เรายังสามารถใช้ Loop เพื่อสร้าง Power Function ที่คำนวนโดยยกกำลัง 3 จนถึง 1000

Functional programming isn’t Pythonic

คุณอาจสังเกตเห็นได้ แต่หลายสิ่งที่เราต้องการทำใน Functional Programming นั้นมุ่งโฟกัสไปที่ List นอกเหนือจาก Reduce Function และ Partial Application แล้ว Function ทั้งหมดที่คุณได้เห็นจะ Generate List ซึ่ง Guido (ผู้ประดิษฐ์ Python) เองก็ไม่ชอบ Functional Programming ที่เกี่ยวกับ List ใน Python เนื่องจาก Python มีวิธี Generate List อยู่แล้ว
ถ้าคุณเขียน “import this” ลงใน Python IDLE session คุณจะได้:

นี่คือ Zen of Python ซึ่งเป็นเรื่องที่เกี่ยวกับสิ่งที่ Pythonic เป็น ในส่วนที่เราต้องการจะเกี่ยวข้องกับมันคือ:
ควรมีวิธีเดียวที่ชัดเจน ในการทำเท่านั้น
ใน Python, Map และ Filter สามารถทำสิ่งเดียวกันกับที่ List Comprehension (ซึ่งจะกล่าวถึงในหัวข้อถัดไป) สามารถทำได้ ซึ่งเป็นการแหกกฏข้อหนึ่งของ Zen of Python ดังนั้น ส่วนของ Functional Programming เหล่านี้ จะไม่มองเป็น 'Pythonic' 
อีกประเด็นที่จะพูดถึงคือ Lambda สำหรับใน Python แล้ว Lambda Function ถือเป็น Function ธรรมดาทั่วไป Lambda เป็น Syntactic Sugar (Syntax ที่ถูกออกแบบมาเพื่อทำให้อ่านง่าย/เข้าใจง่ายขึ้น) ซึ่งจากตัวอย่างด้านล่าง 2 สิ่งนี้มีความหมายเหมือนกัน:

Regular Function ปกติทั่วไป สามารถทำทุกอย่างที่ Lambda Function สามารถทำได้ แต่ Lambda Function กลับไม่สามารถทำทุกอย่างที่ Regular Function สามารถทำได้
นี่เป็นข้อโต้แย้งสั้นๆ เกี่ยวกับเหตุผลที่ว่า ทำไม Functional Programming ถึงไม่ได้เหมาะไปซะทั้งหมดกับ Python Ecosystem 

List comprehensions

ก่อนหน้านี้ เราพูดกันถึงสิ่งที่คุณสามารถทำกับ Map และ Filter คุณสามารถทำกับ List Comprehension นี่คือส่วนที่เราจะได้เรียนรู้เกี่ยวกับพวกมัน
List Comprehension เป็นวิธี Generate List ใน Python ซึ่งมี Syntax ดังนี้:

และนี่เป็นตัวอย่างของการ ยกกำลัง ทุกตัวเลขใน List:

เอาล่ะ ตอนนี้เราได้เห็นวิธีที่ เราสามารถใช้ Function กับทุกๆ Item ใน List ได้อย่างไรกันแล้ว แล้วเราจะไป Apply กับ Filter ได้อย่างไรบ้าง? ลองดู Code นี้ จากที่อ้างอิงมาก่อนหน้านี้:

เราสามารถแปลงมันเป็น List Comprehension ได้ดังนี้:

List Comprehension จะ Support กรณีมี Statement ลักษณะนี้ คุณไม่จำเป็นต้องใช้ Function มากมายอีกแล้ว ที่จริงแล้วคุณกำลังพยายามสร้าง List ที่จะทำให้ดู Clean ขึ้นและง่ายขึ้นโดยการใช้ List Comprehension
จะทำอย่างไร ถ้าเราต้องการยกกำลังของตัวเลขใน List ที่มีค่าต่ำกว่า 0? กับ Lambda, Map และ Filter คุณสามารถเขียนได้ดังนี้:

มันอาจดูค่อนข้างจะยาวและซับซ้อนสักเล็กน้อย แต่ด้วย List Comprehension มันจะเหลือเพียง:

List Comprehension มันจะใช้งานได้ดีกับ List ส่วน Map และ Filter สามารถใช้งานได้ดีกับ Iterable นั่นคือคุณสามารถใช้ Comprehension ใดก็ได้ สำหรับ Iterable Object ใดๆ ที่คุณต้องเจอ

Other comprehensions

คุณสามารถสร้าง Comprehensions ใดๆ สำหรับ Iterable ได้
Iterable ใดๆ สามารถถูก Generate โดยใช้ Comprehension และตั้งแต่ Python 2.7 เป็นต้นไป คุณสามารถสร้าง Dictionary (hashmap) ได้ 

ถ้าเป็น Iterable มันจะสามารถถูก Generate ได้ ลองดูตัวอย่างสุดท้ายของ Sets ลองดูที่สรุปสั้นมาให้ด้านล่าง:

  • Sets คือ Lists ของ Elements ไม่มี Element ใดซ้ำกันใน List นั้นๆ
  • ลำดับใน Sets ไม่มีความสำคัญใดๆ

คุณอาจสังเกตเห็นว่า Sets มีเครื่องหมาย { } ซึ่งเป็นแบบเดียวกับ Dictionary แต่ Python มีความฉลาดเพราะ มันรู้ว่าคุณกำลังเขียน Dictionary Comprehension หรือ Set Comprehension โดยขึ้นอยู่กับว่าคุณ Provide ตัว Extra Value สำหรับ Dictionary หรือไม่ 
สรุป
แม้ Functional Code จะสามารถถูกทำให้ Clean แต่ขณะเดียวกันก็สามารถทำให้ยุ่งยากได้เช่นกัน สำหรับ Hardcore Python Programmer บางคน อาจไม่ชอบ Functional Paradigm ใน Python คุณควรใช้ในสิ่งที่คุณต้องการใช้ และใช้ Tools ที่ดีที่สุดให้เหมาะกับแต่ละงานของคุณ

ที่มา:  https://hackernoon.com/

 

 

รับตำแหน่งงานไอทีใหม่ๆ ด้วยบริการ IT Job Alert

 

อัพเดทบทความจากคนวงในสายไอทีทาง LINE ก่อนใคร
อย่าลืมแอดไลน์ @techstarth เป็นเพื่อนนะคะ

เพิ่มเพื่อน

 

บทความที่เกี่ยวข้อง