Higher Order Function

Thotsaphon Lertkaew
Lotus’s IT
Published in
4 min readFeb 21, 2023

จุดเริ่มต้นของ Medium หัวข้อนี้มาจาก ภายในทีมที่มีน้องจบใหม่ที่ยังไม่รู้จัก Higher Order Function ทำให้น้อง ๆ เขียนโค้ดที่ค่อนข้างซับซ้อน เวลาต้องมาช่วยไล่ปัญหา ใช้เวลามาก และลำบากสุด ๆ ในท้ายสุดก็ต้องสอนให้รู้จักสิ่งนี้ และไหน ๆ ก็จะต้องสอนคนในทีมแล้ว ก็เขียนไว้ด้วยซะเลย เผื่อมีเพื่อนร่วมทีมใหม่ที่เข้ามา ก็โยนเจ้าสิ่งนี้ให้อ่านกันไปเลย ^_^

Photo by Luca Bravo on Unsplash

Higher Order Function คืออะไร???
Function ที่เข้าข่าย 2 ข้อต่อไปนี้ อย่างน้อย 1 ข้อ ก็ถือว่า Function นั้นเป็น Higer Order Function แล้ว

  • Input มี Function เป็น Parameter อย่างน้อย 1 Parameter
  • Output เป็น Function ออกไป

What *** ****!? หลายคนคงอ่านแล้วก็ อุทานอะไรมาประมาณนี้ นั่นสิ มันคืออะไร ขอภาพหน่อย ไม่เห็นภาพ!!

จัดไปครับ แต่ยังไม่ให้ดูหรอก :p ให้ดูตอนนี้ก็อาจจะยังไม่เข้าใจ เรามาเริ่มจากดู Function เดิม ๆ ที่เราคุ้นเคยกันก่อนดีกว่า (จากตรงนี้ไปจะเป็น Code ในภาษา Kotlin แต่ภาษาอื่น ๆ หลักการไม่ต่างจากตรงนี้มากครับ ลองดูกันก่อนครับ)

// Input Type (Int, Int)
// Output Type Int
fun add(number1: Int, number2: Int): Int {
return number1 + number2
} // Function Type (Int, Int) -> Int
// Function Type อ่านจาก Input -> Output

Function add นี้ รับ Parameter เป็น Int Type 2 ตัว (number1, number2) และ Return Int Type ออกไป แปลว่า ถ้าเราจะใช้ Function นี้ต้อง Input ด้วย Type ที่ Function ต้องการ

จากจุดนี้ เราจะเห็นว่า Parameter มี Type แล้ว Function ละมี Type ไหม?

คำตอบคือ มี!

Function add มี Type เป็น รับ Int, Int แล้ว Return Int หรือ (Int, Int) -> Int

แปลว่า ถ้ามี Function ใด ๆ ที่ Parameter Type เป็น (Int, Int) -> Int ก็สามารถ รับ Function add ได้

พอลองสร้างตามนั้น เราจะได้ Function หน้าตาประมาณนี้ (Focus ที่ Input)

// Input Type (Int, Int) -> Int
// Output Type Int
fun calAddFive(calculator: (Int, Int) -> Int): Int {
return calculator(1, 1) + 5
} // Function Type ((Int, Int) -> Int) -> Int

fun add(number1: Int, number2: Int): Int {
return number1 + number2
}

calAddOne(::add) // :: คือ Syntax ของ Kotlin เมื่อเราจะโยน Function เป็น Parameter

จะเห็นว่า Input ของ calAddFive มี Parameter ที่เป็น Type (Int, Int) -> Int ซึ่งตรงกับ Function Type ของ add ดังนั้นเราจึงสามารถโยน add เข้าไปเป็น Parameter ของ Function calAddFive ได้เลย

ถ้าย้อนกลับไปที่นิยามของ Higher Order Function ด้านบนจะเห็นว่า calAddFive ก็เป็น Higher Order Function เนื่องจากมีการรับ Function เป็น Parameter

ส่วนอันนี้คือตัวอย่างที่ Return เป็น Function ออกไป

// Input Type ()
// Output Type (Int, Int) -> Int
fun getRandomFunction(): (Int, Int) -> Int {
return ::add
} // Function Type () -> ((Int, Int) -> Int)

fun add(number1: Int, number2: Int): Int {
return number1 + number2
}

แค่นี้หรอ!? มันก็แค่เขียนโค้ดแล้วส่งต่อ Function ไม่ใช่หรอ

ใช่ครับ สำหรับ Higher Order Function เบื้องต้นมีแค่นี้ แต่พื้นฐานเล็ก ๆ เหล่านี้ก็สร้างความแตกต่างที่ยิ่งใหญ่ได้

Photo by Parshva Shah on Unsplash

ก่อนอื่น ลองนึกย้อนและสำรวจก่อนว่าที่ผ่านมาตั้งแต่เขียนโค้ดภาษา Computer ต่าง ๆ เคยใช้ Higher Order Function กันมาบ้างรึป่าว ผมเชื่อว่าหลายคนต้องเคยใช้มาบ้างแล้ว และเชื่อว่ามีทั้ง คนที่รู้ และ คนที่ไม่รู้

Higher Order Function ทั่วไปนั้น ส่วนมากมีให้ใช้กันในหลาย ๆ ภาษาเช่น

  • ForEach // ห๊ะเป็นด้วยหรอ? เป็นครับ ถ้าไปดูจะเห็นว่ารับ function เข้ามาครับ
  • Map
  • Filter
  • Etc. // อีกมากมาย ขึ้นอยู่กับภาษานั้น ๆ

หลายคนรู้จัก Function เหล่านี้ แต่บางคนอาจจะพึ่งเคยรู้จัก มาดูตัวอย่างการเขียนสำหรับคนที่ไม่รู้ว่ามี Higher Order Function เหล่านี้กันดีกว่าครับ

สำหรับคนที่ไม่รู้

val list = listOf(1, 2, 3, 4, 5, 6)

for(number in list) {
print(number)
}

ส่วนอันต่อไปนี้คือคนที่รู้

val list = listOf(1, 2, 3, 4, 5, 6)

list.forEach(::print)

จะเห็นได้ว่าเราสามารถอ่านมันได้ง่ายขึ้นด้วย มันจะอ่านได้ว่า จาก list สำหรับละตัว print หรือแปลเป็นภาษาไทย ก็ เอาแต่ละตัวใน list มา print

มาดูอีกหนึ่งตัวอย่างด้วย Filter ละกันครับ และลองเทียบความอ่านและตีความหมายดูนะครับ

// แบบ A
val list = listOf(1, 2, 3, 4, 5, 6)

val newList = mutableListOf()

for(number in list) {
if (isEven(number)) newList.add(number)
}

fun isEven(number: Int): Boolean {
return number % 2 == 0
}
// แบบ B
val list = listOf(1, 2, 3, 4, 5, 6)

val newList = list.filter { number ->
isEven(number)
}

fun isEven(number: Int): Boolean {
return number % 2 == 0
}
// แบบ C
val list = listOf(1, 2, 3, 4, 5, 6)

val newList = list.filter(::isEven)

fun isEven(number: Int): Boolean {
return number % 2 == 0
}

แบบ A จะเห็นได้ว่ามี Low Level ที่อยู่ภายใน Loop ทำให้เราต้องคอยพยายามคิดตามและตีความ ​ซึ่งยากที่คนอ่านจะรู้ว่ากำลัง Filter อยู่

แบบ B ดีขึ้นกว่าแบบ A มากเพราะไม่ต้องไปตีความอะไร อ่านง่าย แต่ยังมี {} ที่ทำให้ต้องกวาดตาเข้าไป ซึ่งอาจจะทำให้ดูยากขึ้น แต่ก็ดีกว่าแบบ A

แบบ C ดีที่สุดใน 3 แบบนี้ เพราะว่าแยกส่วน Logic ของ Filter ออกมา ทำให้เราสามารถอ่านได้ง่าย ๆ เลยว่าให้ Filter ของใน list เอาเลขคู่

ทำไมถึงไม่ควรใช้แค่การเปิด { } แบบนี้ ทำไมถึงต้องใช้ ::Function แบบนี้

นั่นก็เพราะว่า การเปิด { } มันคือ Lambda Expression หรืออีกชื่อหนึ่งคือ Anonymous Function ชื่อมันก็บอกอยู่ว่า Anonymous ทำให้เราไม่รู้ว่ามันคือ Function อะไร ถ้าอยากรู้ต้องไปแกะ Logic ข้างในและตีความเอาเอง

ถ้าเป็น 1 บรรทัดก็คงพอแกะได้ แต่ในการทำงานจริง มันจะไม่ 1 บรรทัดเสมอไป และยังไม่รวมถึงตอนที่ Function นั้นมีการขยายตัวอีก เพราะงั้นยกออกเป็น Function ตั้งแต่แรก ตั้งชื่อและโยนเข้ามาในฐานะ Parameter ดีกว่า ส่วนถ้าต้องการเพิ่ม Logic ก็ไปทำใน Function นั่น

อ่ะ ๆ พอรู้แล้วว่า Higher Order Function คืออะไร แล้วมันดียังไง?

ต่อไปนี้จะพาไปดูตัวอย่างสุดท้ายของบทความนี้ ว่าเราสามารถเอาความรู้ของ Higher Order Function ไปใช้ยังไง

  • โจทย์ให้นับจำนวนเฉพาะจากชุดตัวเลขที่มี

แว๊บแรกในหัวของเรา ก็เริ่มจาก Logic การหาจำนวนเฉพาะ บลา ๆ เต็มไปหมด

ก่อนจะคิดเรื่องนั้น เราควรทำความเข้าใจโจทย์ที่เราจะแก้ให้เสร็จก่อน โจทย์บอกให้นับจำนวนเฉพาะ งั้นก็ตั้งโจทย์ให้ได้ประมาณนี้

val numbers = listOf(1, 2, 5, 15, 48, 63, 21, 31) // ชุดตัวเลข

count(numbers, ::isPrime) // โจทย์

ห๊ะ!? แค่นี้หรอ

ใช่ครับ แค่นี้แหละ หลังจากนั้นเราก็ไปแก้ปัญหาย่อยที่ต้องแก้
1. count คือการนับ
2. isPrime คือการหาจำนวนเฉพาะ
ดังนั้นจะมี Function ที่แก้แต่ละปัญหา (ส่วนนี้ผมจะเขียนแค่ส่วน Function count นะครับเพราะว่าอยากให้เห็นส่วนที่เป็นการใช้ประโยชน์จาก Higher Order Function)

fun count(numbers: List<Int>, shouldCount: (Int) -> Boolean): Int {
// จะแก้ปัญหาการนับยังไงก็ได้ อยากเปลี่ยนวิธีนับก็ได้ ตราบใดที่มันยังทำหน้าที่การนับได้ถูก
return numbers.filter(::shouldCount).size
}

fun isPrime(number: Int): Boolean {
// Implement การหา จำนวนเฉพาะ
}

จากด้านบน วิธีการคือ แก้ปัญหาใหญ่ด้วยการแตกเป็นปัญหาย่อย พอทำแบบนี้เรายังสามารถนำ Function เหล่านี้ไปแก้ปัญหาอื่น ๆ ในทำนองคล้ายกันได้ด้วย

  • ให้นับจำวนวนเลขคู่ของชุดตัวเลขต่อไปนี้ ก็ตั้งโจทย์แล้วจะเห็นปัญหาที่ต้องแก้คือ

1. count คือการนับ
2. isEven คือการหาเลขคู่

เอ๊ะ 1. เรามีแล้วนี่นา ส่วน 2. เราก็เคยทำไว้แล้ว เอามา Combo กัน ก็จะได้แบบนี้

val numbers = listOf(1, 2, 5, 15, 48, 63, 21, 31) // ชุดตัวเลข

count(numbers, ::isEven) // โจทย์

เสร็ลแล้ว โดยไม่ต้อง implement อะไรเพิ่ม

จะเห็นว่า เราสามารถนับอะไรก็ได้ โดยไม่ต้องไปแก้ข้างใน ขอแค่มี Function Type เป็น (Int) -> Boolean เราก็สามารถนับชุดตัวเลขด้วยเงื่อนไขใด ๆ ที่เกี่ยวกับ List ของ Int ได้แล้ว

และที่สำคัญทำให้โค้ดของเราอ่านง่ายขึ้นมาก ๆ แถมสามารถ Reuse และ Combo ได้ด้วย เท่ฝุด ๆ ไปเลย

สรุป Higher Order Function

  • คือ Function ที่รับ Function หรือ คืน Function ออกไป
  • Function เองก็มี Type นะ
  • สามารถแยก Logic ออกจากกันได้มากขึ้น ทำให้โค้ดอ่านได้ง่ายขึ้น และ Reuseable

สำหรับเรื่องนี้ก็ประมาณนี้ครับ หวังว่าจะจุดประกายให้ทุกคนที่อ่านมาถึงตรงนี้ สามารถเอาต่อยอด ให้เป็นประโยชน์ต่อตัวเองหรือทีม

ที่สำคัญอย่าลืมไปฝึกใช้บ่อย ๆ ไว้เจอกันใหม่ในบทความอื่นนะครับ

Photo by Jajang Permana on Unsplash

--

--