FP(02): Lambda Function and Closure ฟังก์ชันทันใจและพื้นที่ปิดล้อม!
Tags: beginners, programming, functional
Published: 2020-04-06 17:42:07

> บทความจาก [https://www.tamemo.com/post/158/fp-02-lambda-and-closure/](https://www.tamemo.com/post/158/fp-02-lambda-and-closure/)

ในบทที่แล้ว เราพูดถึง **Pure Function** ว่ามันคือการที่เซ็ตฟังก์ชันเป็นตัวแปรได้ ในบทนี้เราจะเราพูดเสริมในหัวข้อนั้นต่อ

> เนื้อหาในบทความนี้จะมีบางหัวข้อซ้ำซ้อนกับบทความที่เราเคยเขียนไปก่อนหน้านี้ในซีรีส์ **JavaScript ฉบับมือใหม่** [ตอนที่ 3 Js มองในมุมของ Functional Programming](https://www.tamemo.com/post/129/javascript-fundamental-3-functional/) อยู่นิดหน่อยนะ ... แต่จะเอามาขยายความให้ลึกขึ้นหน่อย และไม่ได้โฟัสกับภาษา JavaScript นะ

ก่อนอื่นลองดูโค้ดนี้

```js
function whenClick(){
  ...
}

button.onClick(whenClick)
```
หากใครเคยเขียนโปรแกรมฝั่ง front-end หรือโปรแกรมที่ต้องมี ui ด้วย น่าจะคุ้นกับการเขียนโปรแกรมแบบนี้คือ Event-Driving 

นั่นคือเราจะกำหนดว่าเมื่อ**ปุ่ม**ถูกกด จะให้ทำงานแบบ**ฟังก์ชัน**ที่กำหนดไว้

แต่ถ้า event ของเรามีเยอะมาก เช่นมีปุ่มมากกว่าหนึ่งปุ่ม เราอาจจะต้องเขียนโค้ดแบบนี้

```js
function whenClick1(){
  ...
}

function whenClick2(){
  ...
}

function whenClick3(){
  ...
}

button1.onClick(whenClick1)
button2.onClick(whenClick2)
button3.onClick(whenClick3)
```
เราจะพบว่า เราจะต้องสร้างฟังก์ชันเยอะมาก อาจจะทำให้อ่านยาก และที่สำคัญคือ**เราต้องตั้งชื่อฟังก์ชันทุกตัวด้วย** (การตั้งชื่อตัวแปรหรือฟังก์ชัน สำหรับ
โปรแกรมเมอร์น่าจะรู้ๆ กันว่าเป็นงานที่เราไม่ค่อยอยากทำกัน ฮา)

## Lambda สร้างฟังก์ชันทันใจ
แลมด้าหรือที่เรียกว่า **"Anonymous Function"** (ฟังก์ชันนิรนาม)

ก่อนจะอธิบายเรื่องแลมด้า ลองดูตัวอย่างโค้ดนี้ก่อน
```js
var x = 10
print(x)
```
สำหรับตัวแปร `x` ที่เราสร้างขึ้นมาเพื่อเก็บค่า `10` แล้วนำมาปริ๊นค่าต่อ

จากโค้ดข้างบนนี่ เราสามารถลดรูปให้เหลือแค่นี้ได้
```js
print(10)
```
นั่นแปลว่าเราสามารถลดการสร้างตัวแปร แล้วหันมาใช้ literal แทนได้ถ้าตัวแปรค่านั้นใช้งานแค่ครั้งเดียว

> **"literal"** คือค่า value ที่ไม่ใช่ตัวแปร เช่น `10`, `"A"` ซึ่งต่างจากตัวแปรหรือ variable เพราะค่าแบบ literal สามารถนำไปโปรเซสค่าได้เลย แต่สำหรับค่าแบบตัวแปรจะต้องมีการเข้าไปดึงค่ามาจาก memory ก่อนนำมาใช้งานได้

ดังนั้นถ้าเราใช้หลักการเดียวกันกับโค้ดฟังก์ชันตอนแรก
```js
function whenClick(){
  ...
}

button.onClick(whenClick)
```
แทนที่เราจะสร้างฟังก์ชันเตรียมไว้ก่อน (เหมือนประกาศตัวแปร) เราก็เอา **function literal** ไปใช้เป็น value แทนเลยก็ได้ ... แบบนี้
```js
button.onClick(function whenClick(){
  ...
})
```
ในเคสนี้ ถ้าเราไม่อยากตั้งชื่อให้ฟังก์ชัน ภาษาส่วนใหญ่ก็มักจะให้ละส่วนนี้ไว้ได้ เป็นแบบนี้
```js
button.onClick(function(){
  ...
})
```
การใช้ฟังก์ชันแบบนี้ เราเรียกว่าแลมด้า ซึ่งเป็นอักษรกรีกตัว **λ** โดยเป็นชื่อที่มาจากวิชา **Lambda Calculus** ในจักรวาล FP ของ Alonzo Church

สังเกตว่าเวลาใช้แลมด้านั้น เราสร้างเวลาใช้งานเลย สร้างทีเดียวทิ้ง ไม่จำเป็นต้องประกาศชื่อให้มัน เพราะไม่มีการอ้างอิงที่ใดอีก


> **Note:** ในแต่ละภาษามีวิธีสร้าง lambda ที่ต่างกัน
> ```js
> // แบบมาตราฐาน
> button.onClick(function(){
>   ...
> })
> 
> // แบบยาวสุดๆ ในภาษา OOP เช่น Java
> button.onClick(new OnClickListener(){
>   public void click(Event e){
>     ...
>   }
> })
> 
> // แบบย่อหรือ "arrow function" เช่นในภาษา JavaScript
> button.onClick(() => {
>   ...
> })
> 
> // แบบย่อกว่าแบบที่แล้ว เช่นในภาษา Dart
> button.onClick((){
>   ...
> })
> 
> // แบบย่อสุดๆ จนไม่เหลืออะไรแล้ว ในภาษา Kotlin
> button.onClick {
>   ...
> }
> 
> // แบบใช้คำว่า lambda เลยในภาษา Python
> button.on_click(lambda _: ...)
> ```












## Closure

หรือแปลว่า **"การปิดล้อม"** หรือ **"พื้นที่ปิดล้อม"** เป็นคุณสมบัติพิเศษอีกอย่างใน FP ซึ่งใช้ได้กับฟังก์ชันธรรมดาหรือแลมด้าก็ตาม



### Scope

ภาษาโปรแกรมส่วนใหญ่ สามารถสร้างฟังก์ชันซ้อนๆ กันได้ (nested) ในแต่ละชั้นของฟังก์ชันที่สร้างซ้อนๆ กันอาจจะมีการสร้างตัวแปรเอาไว้ แต่การจะเรียกใช้ตัวแปรพวกนั้น ไม่ใช่ว่าฟังก์ชันทุกชั้นจะเรียกใช้ได้ เราเรียกว่า Scope ในการเรียกใช้ตัวแปร

ลองดูโค้ดข้างล่างประกอบ

<pre><code>function a(){
    var <span style="color: #6aa84fff"><strong>x = 10</strong></span>
    function b(){
        var <span style="color: #6aa84fff"><strong>y = 20</strong></span>
        function c(){
            var z = 30
            <span style="color: #6aa84fff">x, y, z // <strong>Ok!</strong> ฟังก์ชั c สามารถเรียกใช้ตัวแปรได้หมดเลย</span>
        }
        <span style="color: #6aa84fff">x, y // <strong>Ok!</strong> ฟังก์ชั c สามารถเรียกใช้ตัวแปรได้หมดเลย</span>
        <span style="color: #ee401cff">z // ในฐานะ b เราไม่รู้จักตัวแปร z เพราะมันถูกสร้างใน c</span>
    }
    <span style="color: #6aa84fff"><strong>x // Ok! </strong>ฟังก์ชัน b สามารถเรียกใช้ x ได้</span>
    <span style="color: #ee401cff">y, z // ในฐานะ a เราไม่รู้จักทั้งตัวแปร y และ z เพราะมันถูกสร้างภายในฟังก์ชันข้างในทั้งคู่เลย</span>
}</code></pre>

![](https://1.bp.blogspot.com/-sf1P4gFx7Mc/Xor1ACeGVqI/AAAAAAAASOo/ymSiXomDFoYhLT65nvTGyDO73OX7dhdEACNcBGAsYHQ/s1600/fp-02-9.png)

ให้จำไว้ว่า**ฟังก์ชันจะมีคุณสมบัติการปิดล้อม** หรือก็คือ **"Closure"** ในการปกปิดตัวแปรภายในเอาไว้ ถ้าสโคปที่เราอยู่ อยู่ข้างนอกฟังก์ชัน เราจะไม่มีสิทธิในการใช้ตัวแปรในฟังก์ชันนั้นเลย

เช่น...

ถ้าเราอยู่ในสโคปของฟังก์ชัน `b()` เราสามารถใช้งานตัวแปร `y` ซึ่งเป็นสโคปของตัวมันเองได้อยู่แล้ว ... แต่เพิ่มเติมคือมันใช้ตัวแปร `x` ซึ่งเป็นสโคปของ `a()` แต่เพราะฟังก์ชัน `b` อยู่ใน `a` ก็เลยใช้งานได้ ไม่มีปัญหา

แต่สำหรับตัวแปร `z` ที่อยู่ในฟังก์ชัน `c()` จะไม่สามารถเรียกได้เลย เพราะ**โดน Closure ของฟังก์ชัน `c()` ปิดล้อมเอาไว้นั่นเอง**

─────

แต่นอกจากคุณสมบัติ scope แล้ว การใช้งาน closure ยังมีเรื่องที่น่าสนใจ

```js
function outer() {
  var x = 10
  function inner() {
      return x
  }
  return inner
}
```
ตอนนี้เรามีฟังก์ชันอยู่ 2 ตัวคือ `inner` ที่ถูกสร้างอยู่ใน `outer` อีกทีนึง

โครงสร้างแบบนี้เราเรียกว่า `outer` ล้อมตัวแปร `x` และฟังก์ชัน `inner` เอาไว้ และรีเทิร์นค่ากลับเป็น **high order function**

โดยที่ฟังก์ชัน `inner` นั้นสามารถใช้ตัวแปร `x` ซึ่งเป็นสโคปของ `outer` ได้ 

### Quiz?

ทีนี้ เราลองมาเดากันหน่อย ว่าถ้ารันโค้ดข้างล่างนี้ จะได้คำตอบเป็นอะไร

```js
var func = outer()
print(func()) // ได้คำตอบเป็นอะไร?
```
ถ้าใครอยากลองก็หยุดคิดดูก่อน เมื่อได้แล้วก็ดูคำตอบต่อไปได้เลย
```js
var func = outer()

// ตอนนี้ func ก็คือฟังก์ชัน inner ที่ outer รีเทิร์นกลับมาให้นั่นแหละ

print(func()) // คำตอบคือ 10 นั่นเอง!
```
อันนี้ไม่ยาก เมื่อเราเรียกใช้ `outer` มันก็จะรีเทิร์นค่ากลับมาเป็น **ฟังก์ชัน inner** ซึ่งถ้าเราเรียกใช้มันต่อ มันก็จะรีเทิร์นค่า **10** กลับมานั่นเอง

การทำงานก็ตรงไปตรงมานี่นา? แล้วมีอะไรน่าแปลกเหรอ?

แต่ตามหลักการทำงานของฟังก์ชัน การที่ `inner` ยังเรียกใช้งานตัวแปร `x` ได้อยู่นั่นแหละ คือเรื่องแปลก!!

> ถ้าใครยังไม่รู้ว่า เมื่อเราเรียกใช้ฟังก์ชัน มันทำงานในเชิงเมโมรี่ยังไง อ่านเพิ่มได้ก่อนที่ [Function ทำงานยังไง?, ในมุมมองของโปรแกรมเมอร์สาย Imperative](https://www.tamemo.com/post/152/how-function-work/)

ลองดูรูปข้างล่างนี่เพื่ออธิบายว่าทำไมเหตุการณ์แบบนี้ถึงแปลก

![](https://1.bp.blogspot.com/-Tad8aKFbGmk/Xor-aC8G_yI/AAAAAAAASO0/Pt8jInTWKVoO6dQTSf8y2iCfTS7HYvJQwCNcBGAsYHQ/s1600/fp-02-10.png)

1. เรียกใช้ฟังก์ชัน `outer` เกิดสโคปของตัวแปร `x` และ `inner` ขึ้น
2. ฟังก์ชัน `outer` รีเทิร์นค่ากลับเป็นฟังก์ชัน `inner` --> ตามหลักการของฟังก์ชัน **ถ้ามันรีเทิร์นค่ากลับแล้ว มันจะหยุดการทำงานทันที** นั่นแปลว่าตัวแปร `x` ก็จะหายไปด้วยในจังหวะนี้!
3. พอเราเรียกใช้ `inner` ซึ่งต้องรีเทิร์นค่า `x` กลับ ... นั่นแหละ แล้วจะไปหยิบค่า x มาจากไหนในเมื่อฟังก์ชันคืนเมโมรี่กลับไปแล้ว?

**สาเหตุที่ `x` ยังสามารถเรียกใช้ได้อยู่ เพราะคุณสมบัติของ Closure นั่นเอง**

ในเชิงการจัดการเมโมรี่เมื่อเราสร้างฟังก์ชันซ้อนๆ กัน เมื่อฟังก์ชันทำงานเสร็จแล้วจะมีการเช็กว่าตัวแปรในสโคปของมันมีการเรียกใช้งานจาก**ฟังก์ชันที่อยู่ใน closure ส่วนผิดล้อมของมันหรือไม่** ถ้ายังมีฟังก์ชันจะถูกย้ายไปเก็บไว้ใน [[memory]] พิเศษอีกส่วนหนึ่ง (แยกเป็นคนละส่วนกับ stackframe)

### สำหรับภาษา OOP
ถึงภาษา OOP จะไม่มีการสร้างฟังก์ชัน แต่ก็มีเมธอดแทน เพียงแต่กฎการเรียกใช้ตัวแปรจะต่างจาก FP นิดหน่อย
```java
class Outer {
  int x;

  public void outerMethod(){
    final int y;
    int z;
    new Inner(){
      public void innerMethod(){
        // x, y สามารถเรียกใช้ได้
        // z ใช้ไม่ได้ เพราะถือว่าเป็นสโคปของเมธอด
      }
    };
  }
}
```
สำหรับ inner method จะสามารถเรียกใช้ตัวแปร x ที่เป็น properties ของคลาสได้ แต่ถ้าตัวแปรนั้นเป็น local ในเมธอดละก็ จะเรียกใช้งานไม่ได้เลย ตามเหตุผลที่อธิบายไปข้างบน (OOP ไม่มีคุณสมบัติของ Closure) ถ้าจะใช้จริงๆ จะต้องประกาศให้ตัวแปรนั้นเป็น `final` ซะก่อน ระบบของOOPถึงจะอนุญาตให้เรียกใช้ได้

## การประยุกต์ใช้งาน Closure

เราสามารถใช้ประโยชน์จาก Closure ได้หลักๆ คือการ**ปิดล้อมตัวแปร** (ก็ตามชื่อมันนั่นแหละ)

เช่น
```js
var count = 0
function countIt() {
  count++
  return count
}
 
print(countIt()) // 1
print(countIt()) // 2
print(countIt()) // 3
```
เราทำฟังก์ชันสำหรับนับขึ้นมา แต่ข้อเสียของฟังก์ชันนี้คือมีการเรียกใช้ตัวแปร global ทำให้ไม่มีความเซฟเลยในการใช้ฟังก์ชัน

แต่เราสามารถสร้างฟังก์ชัน**ครอบ**โค้ดชุดนี้เอาไว้ เพื่อกันไม่ให้ภายนอกเข้ามายุ่งกับตัวแปร `count`

แบบนี้
```js
function createCountIt(){
  var count = 0
  function countIt() {
    count++
    return count
  }
  return countIt
}

var counter = createCountIt()
 
counter() // 1
counter() // 2
counter() // 3
```
แล้วก็มีประโยชน์อีกอย่างด้วย นั่นคือเราสามารถสร้างฟังก์ชัน counter นี่ขึ้นมาได้หลายชุด

```js
var firstCounter = createCountIt()
var secondCounter = createCountIt()

firstCounter()  --> 1
secondCounter() --> 1
secondCounter() --> 2
firstCounter()  --> 2
firstCounter()  --> 3
```
ซึ่งทั้ง 2 ตัวก็จะมี `count` สำหรับนับเลขแยกกันใช้ ไม่ผสมกัน

ในบทต่อไป เราจะกลับไปถูกถึงเรื่องที่ได้ใช้งานกับโลก imperative ก็บ้าง นั่นคือการใช้ `map`, `filter`, `reduce` และตัวอื่นๆ ที่น่าสนใจกัน