และแล้วเราก็อยากเป็น Functional Programmer (ตอนที่ 2: Immutability)

imKrish Developer
imKrish
Published in
4 min readNov 10, 2016

สวัสดีครับเพื่อนๆ บทความนี้ก็เป็นบทความที่ 2 ของซีรี่ Functional Programming ที่ผมแปลมาจากคุณ Charles Scalfani นะครับ

สำหรับตอนแรกเกี่ยวกับ Purity ใครยังไม่อ่านแนะนำให้ไปอ่านก่อนเลยครับผม (สั้นๆครับประมาณ 3–5 นาทีก็จบแว้วววว)

เอาหละครับเรามาต่อกันเลยกับตอนที่ 2 Immutability …

Immutability (การไม่เปลี่ยนแปลง, ไม่กลายพันธุ์)

เพื่อนๆยังจำครั้งแรกที่เห็น Code นี้ได้รึเปล่าครับ

var x = 1;
x = x + 1; // ห๊ะ x = x + 1 ?

สำหรับคนที่เรียนวิชาคณิตศาสตร์มา เห็นครั้งแรกคงตกใจ “x มันจะไปเท่ากับ x + 1 ได้ยังไง” คนเขียนสมการนี้ขึ้นมา มันต้องเพี้ยนแน่ๆ!!!

หลังจากที่เราคุ้นเคยกับการเขียนโปรแกรมมาพักนึงแล้ว เราก็จะเข้าใจว่า Code ข้างต้น ก็คือการเอาค่า x ค่าปัจจุบัน(1) มาบวกกับ 1 แล้วเก็บผลลัพธ์ใหม่(2) ไว้ในตัวแปร x เดิม

ใครที่ Code มาใน Imperative Programming การกำหนดตัวแปร แล้วเปลี่ยนค่า เปลี่ยนสถานะของมัน ถือเป็นสิ่งที่เราทำกันจนคุ้นชินมาก

อย่างไรก็ตามใน Functional Programming การทำแบบนี้ถือว่าบาปครับ!!! เพราะฉะนั้นความรู้ที่เราได้รับมาในวิชาคณิตศาสตร์นั้นอะ ถูกต้องแล้ว !!!

Functional Programming ไม่มีตัวแปร (No Variable)

เอาจริงๆแล้วตัวแปรมันยังมีอยู่ แต่จะอยู่ในรูปของค่าคงที่ คือถ้าเรากำหนดให้ x เท่ากับ 1 แล้ว มันจะเท่ากับ 1 ไปตลอดชีวิตของมันเลย ถ้ามีการไปเปลี่ยนแปลงค่า x ถือว่าผิดกฎครับ!

ไม่ต้องกังวลไปครับ ถึงจะเป็นค่าคงที่ก็จริง แต่ x มันอยู่ได้ไม่นานครับ “อายุสั้น” เพราะค่าเหล่านี้ส่วนใหญ่จะเป็นค่าใน local scope พอ scope หรือสิ่งแวดล้อมที่มันอาศัยอยู่ ทำภารกิจเสร็จสิ้น ค่าเหล่านี้ก็จะตายไปด้วย แต่หลักสำคัญก็คือ

ตราบใดที่ x ยังมีลมหายใจอยู่ มันจะไม่เปลี่ยนแปลงค่า หรือสถานะของมันอย่างเด็ดขาด (ถ้าเป็นคนนี้ก็ประมาณรักเดียวใจเดียวอะครับ เกิดมาแล้วรักได้แค่คนเดียว 555)

ต่อไปนี้คือตัวอย่าง code สำหรับการประกาศค่าคงที่ในภาษา Elm นะครับ, Elm เป็นหนึ่งในภาษา Pure Functional Programming ใช้สำหรับพัฒนาเวปไซต์ ข้อมูลเพิ่มเติมดูได้ที่เวปหลักนี้เลยจ้า http://elm-lang.org/

addOneToSum y z =
let
x = 1
in
x + y + z

สำหรับเพื่อนๆที่ยังไม่คุ้นเคยกับ syntax ผมจะอธิบายให้ฟังครับ จาก code ข้างบนนี้ มีการประกาศฟังก์ชันชื่อ addOneToSum โดยมีการรับตัวแปรเข้ามามาสองตัวคือ y และ z ในส่วนของ let มีการประกาศค่าคงที่ x เท่ากับ 1 และในส่วนของ in คือ เอาค่าคงที่ x(1) มาบวกกับ input สองตัวที่รับเข้ามา y และ z แล้ว return ผลลัพธ์นี้ออกไป

คราวนี้ผมลองใส่ x = 2 ในส่วนของ let แล้ว run ดูได้ผลดังนี้ครับ

addOneToSum y z =
let
x = 1
x = 2
in
x + y + z
There are multiple values named `x` in this let-expression.5| x = 2Search through this let-expression, find all the values named `x`, and give each of them a unique name.

จะเห็นได้ว่ามันไม่ยอม Run ครับ ได้ Error มากินแทน 555 จากตรงนี้แล้วมั่นใจได้เลยครับว่าตัวแปร x ของฟังก์ชันนี้ จะมีค่าเท่ากับ 1 ไปตลอดกาลล

มาถึงตรงนี้เพื่อนๆ คงคิดในใจ บ้าไปแล้ววว!!! เขียนโปรแกรมยังไง โดยไม่ใช้ตัวแปร???

ใจเย็นๆครับ เราลองลองนึกถึงสถาณการณ์ที่เราต้องเปลี่ยนค่าตัวแปร ส่วนใหญ่แล้วจะมีอยู่ 2 เคส คือ Multi-Valued Changes (eg. การเปลี่ยนค่าสถานะของ Record หรือ Object เช่น programmer.status = ‘happy”) กับ Single-Valued Changes (eg. การเปลี่ยนค่าของตัว Counter ใน Loop)

ใน Functional Programming เมื่อมีการเปลี่ยนแปลงค่าพวกนี้ ยกตัวอย่างถ้าเราต้องการเปลี่ยนค่าสถานะใน Object มันจะทำการก๊อปปี้ Object ตัวนี้แล้วเก็บไว้ใน Memory อีกตำแหน่งนึง จากนั้นมันก็จะทำการเปลี่ยนแปลงค่าของ Object ตัวใหม่ตัวนี้ สรุปได้ว่า

Object ตัวเก่าก็ยังคงสถานะเดิมทุกอย่าง อยู่ใน “หน่วยความจำตำแหน่งเดิม”, Object ตัวใหม่ถูกสร้างขึ้นมาใน “หน่วยความจำตำแหน่งใหม่” แล้ว ถูกทำการเปลี่ยนค่าบางสถานะ

หลายคนคงคิดว่าถ้าทำแบบนี้แล้ว Performance ก็แย่ดิ คือ Functional Programming เนี่ยมันทำอย่างมีประสิทธิภาพครับ โดยใช้คุณสมบัติของ Data Structure ไม่ได้ Copy ทั้งหมด

สำหรับ Single-Valued Changes มันก็ทำในลักษณะเดียวกันนี้แหละครับ

ซึ่งใน Loop ปกติที่เราใช้กัน จะมีการเปลี่ยนค่าตัว counter เหล่านั้น (eg. count++) ดังนั้นแล้ว…

ใน Functional Programming ไม่มี Loop !!!

อ่านมาถึงตรงนี้ปุ๊ป Paradigm บ้าไรเนี่ย ตัวแปรก็ไม่ให้ใช้ นี้จะไม่ให้ใช้ Loop อีกหรอ จะมากไปแล้วววว !!!

เดี๋ยวครับ… ใจเย็นๆ ไม่มี loop ในที่นี้ไม่ได้หมายความว่า เราจะเขียนโปรแกรมให้ มัน Loop ไม่ได้นะครับ แต่หมายถึง มันไม่มี for, while, do, repeat ให้ใช้เท่านั้นเอง

Functional Programming ใช้ Recursion สำหรับ loop

ใน Javascript เราสามารถ loop ได้ 2 วิธีด้วยกัน ดังนี้

// อันนี้คือใช้ for loop ทั่วไปครับ
var acc = 0;
for (var i = 1; i <= 10; ++i)
acc += i;
console.log(acc); // prints 55
// ใช้ recursion
function sumRange(start, end, acc) {
if (start > end)
return acc;
return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // prints 55

สังเกตุว่าวิธีแบบ Functional Programming (Recursion) ได้ผลลัพธ์เดียวกันกับแบบ for loop โดยมันจะเรียกตัวเองซ้ำด้วยค่าใหม่ (start + 1) และ (acc + start) โดยไม่ได้ไปเปลี่ยนแปลงค่าเก่าเลย ในทางกลับกันคือใช้ค่าใหม่ที่ได้จากการคำนวณของค่าเก่า ไปเรื่อยๆ

ซึ่งก็จากที่เห็นแหละครับ แบบ recursion ใน javascript นี้อ่านแล้วงง ต้องใช้เวลากว่าจะเข้าใจ เหตุผลก็มี 2 อย่างคือ 1) syntax ใน javascript ไม่ได้ทำออกมาเพื่อรองรับ Functional Programming โดยตรง และ 2) เพื่อนๆอาจจะยังไม่คุ้นเคยกับการคิดแบบ recursion

คราวนี้เราลองมาดูในภาษา Elm กันมั่ง (Pure Functional Programming Language)

sumRange start end acc =
if start > end then
acc
else
sumRange (start + 1) end (acc + start)

ลองมาดู step ของ recursion กันมั่ง (สังเกตุดู เวลาที่มันเรียกตัวมันเองนะครับ มันจะเรียกด้วยค่าใหม่)

sumRange 1 10 0 =      -- sumRange (1 + 1)  10 (0 + 1)
sumRange 2 10 1 = -- sumRange (2 + 1) 10 (1 + 2)
sumRange 3 10 3 = -- sumRange (3 + 1) 10 (3 + 3)
sumRange 4 10 6 = -- sumRange (4 + 1) 10 (6 + 4)
sumRange 5 10 10 = -- sumRange (5 + 1) 10 (10 + 5)
sumRange 6 10 15 = -- sumRange (6 + 1) 10 (15 + 6)
sumRange 7 10 21 = -- sumRange (7 + 1) 10 (21 + 7)
sumRange 8 10 28 = -- sumRange (8 + 1) 10 (28 + 8)
sumRange 9 10 36 = -- sumRange (9 + 1) 10 (36 + 9)
sumRange 10 10 45 = -- sumRange (10 + 1) 10 (45 + 10)
sumRange 11 10 55 = -- 11 > 10 => 55
55

ตอนนี้เพื่อนๆ ก็คงคิดว่ายังไงแล้วแบบ for loop ก็ยังอ่านง่ายกว่าอยู่ดี!!! ซึ่งก็เป็นที่ถกเถียงกันนั่นแหละครับว่า แบบไหนอ่านง่ายกว่ากัน ส่วนนึงอาจจะเป็นเพราะเราคุ้นชินกับแบบ for loop มากกว่า (ถ้าว่างๆ ลองกลับไปอ่านบทความตอนที่ 1 ดูนะครับ เปรียบเทียบการขับรถ และการกับยานอวกาศ) แต่สิ่งที่สำคัญก็คือ วิธีการแบบ For Loop นั้น มัน Mutability (การกลายพันธุ์ได้) ครับ ซึ่งถือว่ามันไม่ดี !!!

หลายๆคนสงสัยว่าแล้ว Immuatbility เนี่ยมันดียังไง ไม่เห็นอธิบายเลย ใครสงสัยลองอ่านใน หัวข้อ Global Mutable State ของบทความนี้ดูครับ (เป็นบทความภาษาอังกฤษ)

สำหรับคนที่ไม่อยากอ่านผมขอสรุปง่ายๆ ดังนี้

ลองเปรียบตัวแปรให้เป็นกล่องนะครับ แล้วในกล่องใบเนี่ยผมใส่ลูกอมไว้ครับ ประเด็นคือกล่องใบนี้ใช้กันหลายคน วันนึงเพื่อนผมมาเปิดกล่องแล้วเอาลูกอมที่ผมใส่ไว้ไปกิน จากนั้นมัน ก็ใส่หมากฝรั่งไว้ในกล่องใบนั้น ซึ่งผมเนี่ยก็ไม่ได้รู้เลย คราวนี้พอผมจะกลับมากินลูกอมที่ผมใส่ไว้ ปรากฎคือมันไม่อยู่แล้วววว!!!

คราวนี้ลองเทียบกับโปรแกรมที่เราเขียนขึ้นมาดูมั่งนะครับ สมมุติว่า Object ก ถูกสร้างขึ้นมาในระบบ โดยปกติถ้าไม่ใช่ใน Functional Programming, Object ก นี้มันก็จะถูกเปลี่ยนค่าสถานะไปเรื่อยๆครับ อาจจะถูกเปลี่ยนโดย Object ข ต่อมาเวลาผ่านไป… Object อีกตัว คือ Object ค อยากจะเรียกใช้สถานะบางค่าของ Object ก ซึ่งตอนนี้ถูกเปลี่ยนค่าไปแล้ว คราวนี้แหละครับปัญหามันก็เกิดดดด (eg. Cannot call method ‘xxx’ of undefined คุ้นๆไหมเอ่ย) ใครอ่านแล้วยังงงๆอยู่แนะนำให้ไปอ่านบทความภาษาอังกฤษข้างบนดูนะครับ

สุดท้ายนี้ก็ขอสรุปอีกทีนะครับ…

ใน Functional Programming ไม่มีใครสามารถเปลี่ยนค่าตัวแปรได้ แม้แต่ตัวคุณเอง !!! อ่านได้อย่างเดียวเท่านั้น (Read Only Access)

Part 2 ก็ขอจบไว้เท่านี้ก่อนนะครับผม

เครดิตทั้งหมดก็ให้กับเจ้าของบทความ คุณ Charles Scalfani ใครสนใจอยากเข้าร่วมกลุ่มใน Facebook ของเขากดได้ที่นี้เลย https://www.facebook.com/groups/learnelm/

ส่วนอันนี้เป็นแฟนเพจของผม ฝากด้วยนะครับผม https://www.facebook.com/imkrish.developer/

“ Happiness Only Real When Shared ”

ที่เริ่มเขียน Blog เพราะไปดูหนังเรื่องนึงแล้วเจอ Quote นี้ ผมก็เลยจะลองดูครับว่ามันจริงหรือเปล่า

--

--

imKrish Developer
imKrish

I’m going to be the best I could be, not someone tells me I should be. I am optimistic and I love freedom : )