เริ่มต้น Reactive Programming ด้วย Combine Part1: นับ 0 ให้ถึง 1

Cocodev
Lotus’s IT
Published in
4 min readDec 18, 2023

Reactive Programming เป็น paradigm ที่วันนี้ถือว่าไม่ได้เป็นของใหม่อีกแล้ว ซึ่งนั้นหมายความว่าเหล่า programmer ทุกสาย web , mobile และ อื่นๆ ก็ล้วนเจอ framework ตัวใหม่ๆที่ปรับมา support การเขียนแบบ reactive มามากมาย บทความนี้ได้หยิบเอา combine framework ของภาษา swift มาใช้เป็นหลัก เพื่ออธิบายคอนเซ็ปต์ของ reactive programming โดนใช้การเปรียบเทียบกับ Imperative Programming ซึ่งเป็นสิ่งที่ programmer ทุกคนคุ้นเคยเพื่อให้เห็นความต่างของทั้ง2แนวคิด และการอ่าน marble diagram เพื่อให้สามารถเรียนรู้ได้รวดเร็วยิ่งขึ้น

Reactive Programming vs Imperative Programming

var x = 10
let y = 20
let z = x + y
x = 30

Imperative Programming จากตัวอย่าง เราให้ที่มาของค่าของ z มาจาก x + y โดยเราให้ค่า x = 10 และ y = 20 แน่นอนว่าค่าของ z = 30

สมมุติว่า: เราเปลี่ยน x = 30 คำถามก็คือว่า z ของเราจะเป็น 30 หรือ 50 ??? ถ้าตามวิธีการเขียนโปรแกรมแบบ imperative ที่คุ้นเคยก็จะตอบได้ไม่ทันทีว่า z = 30 หรือยังเป็นค่าเดิมอยู่ เพราะมันอ้างอิงกับค่า x และ y ค่าเก่า(ค่าตอนที่ บรรทัดที่ 3 ทำงาน) ถึงแม้ว่าเราจะเปลี่ยนให้ x = 30 ในตอนหลัง นี้เป็นรูปแบบ programming ที่ต้องกำหนดวิธีการทำงานให้กับคอมพิวเตอร์อย่างเป็นขั้นเป็นตอน

Reactive Programming เรามาลองเปลี่ยนมุมมอง code ในที่นี้โดยใช้มุมมองทางคณิตศาสตร์ code ในตัวอย่างบอกว่าค่า z อ้างอิงจากผลบวกของค่า x และ y แล้วบรรทัดสุดท้ายเราก็มาเปลี่ยนให้ x = 30 ตามหลักของคณิตศาสตร์ ค่า z ของเราก็ควรจะเป็นค่าใหม่ที่เกิดจาก x + y บวกกันรอบ2 เนื่องจาก x ถูกเปลี่ยนค่า ดังนั้น 30(x) + 20(y) แล้ว z ก็ควรจะเปลี่ยนเป็น 50 ซึ่งเป็นวิธีการคิดทางคณิตศาสตร์

จะดีกว่าไหมถ้าการเขียนโปรแกรมมันเหมือนกับวิธีการทางคณิตศาสตร์ โดยการคํานวณใหม่เมื่อมีการเปลี่ยนแปลงค่าเกิดขึ้น นั้นเป็นแนวคิดของการเกิด Reactive programming

Dataflow and Propagation of change

Reactive programming มีคอนเซ็ปสำคัญ 2 อย่างคือ dataflow และ propagation of change อย่างแรก dataflow เป็นค่าเริ่มต้น และ propagation of change หรือ การเปลี่ยนแปลง คือเมื่อไหร่ก็แล้วแต่เกิดการเปลี่ยนแปลง การเปลี่ยนแปลงนั้นจะทําให้ค่าของ data flow ได้รับผลกระทบด้วย ซึ่งผลกระทบอย่างไรมาดูกันที่ ตัวอย่าง marble diagram

let X:   |---(10)-----------------------------------------------|
+
let Y: |---(20)-----------------------------------------------|

let Z: |---(30)-----------------------------------------------|
time(s): |----|----|----|----|----|----|----|----|----|----|----|
0 1 2 3 4 5 6 7 8 9 10 11

สำหรับวิธีการอ่าน marble diagram สามารถเข้าไปเรียนรู้ในตอนที่2 หรือใน link นี้

Dataflow คือ x, y, z เป็นเส้นที่ลากยาวตามใน diagram เมื่อมี event ของการเกิดของ data เกิดขึ้น data ก้อนนั้นจะสัมพันธ์กับเวลา อย่างเช่น event ณ. วินาทีที่ 1 ให้ x เกิดขึ้นด้วยค่า 10 และ y มีค่าเป็น 20 ลักษณะนี้คือการเกิด data flow ที่สัมพันธ์กับเวลา ซึ่งตอนนี้ต้องบอกว่ามีการเปลี่ยนแปลง ของ x และ y เกิดขึ้นแล้วจึงทำให้กระทบให้ค่า z ซึ่งอิงอยู่กับ x และ y มีการเปลี่ยนแปลง เราเรียกสิ่งนี้ว่าการเปลี่ยนแปลง หรือ proplication of change โดยทำให้ค่าของ z ณ วินาทีที่ 1 มีค่าเป็น 30

let X:   |---(10)------(30)-------------------------------------|
let Y: |---(20)-----------------------------------------------|

let Z: |---(30)------(50)-------------------------------------|
time(s): |----|----|----|----|----|----|----|----|----|----|----|
0 1 2 3 4 5 6 7 8 9 10 11

เหตุการณ์ถัดไป ณ. วินาที่ 4 ให้ x มีค่าเป็น 30 แน่นอนครับมันเกิด proplication of change ดังนั้น z ก็จะได้รับการคำนวณใหม่ โดนค่าตอนนี้คือ x = 30 , y = 20 (ยังเป็นค่าเดิม) z = 50 ณ วินาทีที่ 4 นี้เป็นลักษณะของ reactive programming

Asynchronous + Stream

อ่านมาถึงตรงนี้อาจจะเกิดคำถามว่าทำไมไม่กล่าวถึง stream บางเลย จริงๆแล้ว stream ก็คือ dataflow!!! ใช่แล้วมันคือตัวเดียวกัน ความหมายสวยๆก็คือ สายน้ําของการไหลของข้อมูลตามระยะเวลา ฟังดูเป็นคำปรัชญา เอาเป็นว่าหลังจากนี้เราจะใช้คำว่า stream เพื่อให้เข้าใจตรงกันครับ

|---------------------|      |-----------------|      |-----------------|
| Reactive Programming| = | Asynchronous | + | Dataflow |
|---------------------| |-----------------| |-----------------|

จากหัวข้อที่แล้วจริงสรุปได้ว่า reactive programming มันทำงานแบบ asynchronous บวกกับ stream ใน marble diagram เราได้เห็นแล้วว่า stream ของข้อมูลที่ไหลไปตามเวลา อย่างเช่น วินาทีที่1 ค่า x = 10 วินาทีที่4 x = 30 แล้ว วินาทีถัดไปเรื่อยๆ ก็อาจจะเป็นค่าอื่นๆได้อีกเช่นกัน ลักษณะแบบนี้คือ stream ที่ไหลไปของข้อมูลตามระยะเวลานั้นเอง และเราก็จะสังเกตุว่าการทำงานแบบ reactive นั้นเป็น asynchronous เพราะว่าค่า z นั้นเปลี่ยนแปลงหรืออัพเดทค่าโดยไม่มีความต่อเนื่อง ซึ่งขึ้นอยู่กับ proplication of change ว่าจะเกิดขึ้นเมื่อไหร่และไม่ทำงานเป็นลำดับเป็นขั้นเป็นตอน

Stream + Operation

Stream และ Operation เป็นองค์ประกอบสำคัญของ reactive concept

  1. Streams: การสร้าง stream ไดๆอย่างเช่น stream z, y, z
  2. Operations: การสร้าง operation หรือตัวดําเนินการ เช่นการบวก(+) เครื่องหมายบวกจะเป็นการบอกว่าเราต้องการเอา stream x และ stream y มาบวกกัน และผลลัพธ์ของบวกก็จะทําให้เกิดเป็น stream z
stream X:   |---(10)-----------(30)--------------------------------|
operation: +
stream Y: |---(20)-----------------------------------------------|

stream Z: |---(30)-----------(50)--------------------------------|
time(s): |----|----|----|----|----|----|----|----|----|----|----|
0 1 2 3 4 5 6 7 8 9 10 11
import Combine

var cancellables = Set<AnyCancellable>()
// create strean x
let x = PassthroughSubject<Int, Never>()
// create strean y
let y = PassthroughSubject<Int, Never>()

// create operation +
let zPublisher = Publishers.CombineLatest(x, y)
.map { x, y in
print("x:\(x) y\(y)")
return x + y

}
zPublisher.sink { z in
print("z: \(z)")
}.store(in: &cancellables)

x.send(10)
y.send(20)
x.send(30)

//output
//x:10 y20
//z: 30
//x:30 y20
//z: 50

Concept

คอนเซ็ปสําคัญสําหรับ reactive programming คือการ define พฤติกรรมของข้อมูล ณ เวลาที่เราสร้าง stream

อาจจะมองภาพไม่ชัดนัก ขอยกตัวอย่าง Imperative เทียบ Reactive Programming

let i = 1
let j = 2
print(i * 2)
print(j * 2)

วิธีคิดของการเขียนแบบ imperative programming ต้องการข้อมูลให้เป็นลักษณะไหน ณ ตอนไหน เราก็ทำ ณ ตอนนั้น อย่างเช่นถ้าเราต้องการ print ค่า i และ j คูณด้วย 2 นั่นก็คือเราต้องการผลคูณตอนที่เรา print ตอนนั้นเลย เราไม่สามารภ เปลี่ยนค่า i แล้วให้ print บรรทัดที่ 3 ทำงานได้อีกรอบ

################## การนิยามหรือว่าการสร้างพฤติกรรม ########
var values = PassthroughSubject<Int, Never>()
let transform = values.map {$0 * 2}
let cancellable = transform.sink { value in
print(value)
}
#######################
values.send(1)
values.send(2)

สำหรับ reactive programming แตกต่างออกไป นั่นก็คือเราจะต้องมีการนิยามหรือว่าการสร้างพฤติกรรมเอาไว้ ณ ตอนที่เราสร้าง stream อย่างเช่นเราเสร้าง stream ของ values(Int) ไว้หลังจากนั้นเราก็จะนิยามพฤติกรรมมันว่า stream จะต้องเกิดการ map ก็คือการเปลี่ยนแปลงค่าที่จะเกิดขึ้นใน stream ด้วยการเอาไปคูณ 2 นี่คือการที่เรานิยามพฤติกรรมของเราเอาไว้ ณ ตอนที่เราสร้างนั่นเอง ดังนั้นเมื่อไหร่ก็แล้วแต่ที่เกิดการเปลี่ยนแปลงของ stream นี้ เราจะรับรู้ด้วย subscribe (sink) และการเปลี่ยนแปลงของมันก็จะทําการ print และนี่คือข้อแตกต่างระหว่าง imperative programmin

Functional Reactive Programming

|---------------------------------|   |----------------------|   |------------|
| Functional Reactive Programming | = | Reactive Programming | + | Functional |
|---------------------------------| |----------------------| |------------|

Reactive programming ก็คือการทํางานของ stream + operation ส่วน functional ก็คือการทํางานโดยเป็นลักษณะของฟังก์ชัน จะไม่มีการใช้งานตัว if else statement ไม่มีการใช้งาน for loop แต่เราใช้การทํา data transformation อย่างเช่นการใช้ map reduce filter หรือ อื่นๆ และนี่คือ functional reactive

Summary

ในบทแรกนี้เป็นการกล่าวถึง concept ของ reactive programming ซึ่งบทถัดไปเป็นการเรียนรู้วิธีการ marble diagram ก่อนที่เราจะลง detail ให้มากว่านี้ในบทที่ 3,4

Next part2: Reactive Programming ด้วย Combine Part2: Marble Diagrams

https://medium.com/@ittipongkeawmahing/5b8d3bf5e685

--

--