PHP Arrays
ในการเขียนโปรแกรมบ่อยครั้งที่เราต้องทำงานกับอาเรย์ไม่ว่าจะเป็นการดึงข้อมูลจาก API ต่างๆ หรือการดึงข้อมูลจากฐานข้อมูล งานส่วนใหญ่ก็วุ่นอยู่กับอาเรย์แทบทั้งนั้น
โดยทั่วไปเมื่อเราต้องเปลี่ยนแปลงข้อมูลในอาเรย์เรามักจะสร้างตัวแปรผลลัพธ์เป็น อาเรย์ว่าง แล้ววนรอบอาเรย์ต้นแบบ เปลี่ยนแปลงค่า แล้วนำค่าที่ได้มาเพิ่มใน อาเรย์ผลลัพธ์ เช่น
$numbers = [1, 2, 3, 4, 5, 6];
$increased_numbers = [];
foreach ($numbers as $number) {
$increased_numbers[] = $number + 1;
}
$even_numbers = [];
foreach ($increased_numbers as $number) {
if ($number % 2 === 0) {
$even_numbers[] = $number;
}
}
$string_numbers = [];
foreach ($even_numbers as $number) {
$string_numbers[] = (string) $number;
}
$concat_strings = '';
foreach ($string_numbers as $string_number) {
$concat_string .= $string_number;
}
จากตัวอย่าง สังเกตว่า ณ ช่วงเวลาหนึ่ง ตัวแปร $increased_numbers, $even_numbers, $string_numbers, $concat_strings นั้นมีค่าว่าง และเมื่อการทำงานผ่านไปช่วงหนึ่ง ตัวแปรเหล่านั้นกลับมีค่าอื่นมาซะอย่างงั้น
ลองคิดดูว่าหากมีโค้ดแบบนี้มากๆ เข้ามันจะอ่านยากและ Debug วุ่นวายขนาดไหน (ผมก็เขียนโค้ดแบบนี้เป็นประจำและเมื่อวันที่โค้ดมันเพิ่มมากขึ้น มันจัดการยากขึ้นและมันไม่สนุกเอาซะเลย)
Immutability
จากปัญหาเรื่องตัวแปรมีการเปลี่ยนแปลงค่าที่เจอ สามารถแก้ได้ด้วยการให้ตัวแปรไม่สามารถเปลี่ยนแปลงค่าได้นั่นเองครับ ซึ่งนั่นคือการสร้างตัวแปรให้เป็น constant แต่ใน PHP นั้น const และ define กลับเป็นอะไรที่ข้อจำกัดเยอะมาก ผมเลยแก้ไขด้วยการเคารพตัวเองครับ ว่าเราไม่เปลี่ยนแปลงค่าตัวแปรครับ ฮ่าๆๆๆๆๆ
Data Transformation
เมื่อเราดี่มน้ำสาบานแล้วว่าจะไม่เปลี่ยนแปลงค่าตัวแปรใดใดแล้ว จะทำอย่างไรล่ะถึงจะสร้างตัวแปรพร้อมกับผลลัพธ์ที่เราต้องการได้เลย
เราสามารถใช้ความสามารถ Map, Reduce และ Filter ของอาเรย์เองในการสร้างผลลัพธ์ใหม่จากอาเรย์ต้นฉบับได้ครับ จากตัวอย่างแรกลองมาเขียนใหม่โดยใช้ Map, Reduce และ Filter กัน
const numbers = [1, 2, 3, 4, 5, 6];
$increased_numbers = array_map(function($number) {
return $number + 1;
}, numbers);$even_numbers = array_filter($increased_numbers, function($number) {
return $number % 2 === 0;
});$string_numbers = array_map(function($number) {
return (string) $number;
}, $even_numbers);$concat_strings = array_reduce($string_numbers, function($result, $number) {
return $result .= $number;
}, '');จากตัวอย่าง จะเห็นว่าเราแก้ปัญหาเรื่องการเปลี่ยนแปลงค่าของตัวแปรได้แล้ว แต่มันจะดีกว่าไหม ถ้าเราเอาแต่ละเหตุการณ์ที่เราทำมาเขียนเป็นฟังก์ชันและเรียกใช้งานต่อกันไปเรื่อยๆ ลองมาดูตัวอย่างกันครับ
const numbers = [1, 2, 3, 4, 5, 6];
$increment = function($numbers) {
return array_map(function($number) {
return $number + 1;
}, $numbers);
};$fetch_even = function($numbers) {
return array_filter($numbers, function($number) {
return $number % 2 === 0;
});
};$to_string = function($numbers) {
return array_map(function($number) {
return (string) $number;
}, $numbers);
};$concat = function($strings) {
return array_reduce($strings, function($result, $string) {
return $result .= $string;
}, '');
};$result = $concat($to_string($fetch_even($increment(numbers))));
จากตัวอย่างข้างบน จะเห็นว่าเมื่อเรามองที่ตัวแปร $result เราสามารถมองออกได้ว่าผลลัพธ์เกิดมาจากการทำอะไรบ้าง ดูง่ายขึ้นกว่าตัวอย่างก่อนหน้า แต่ก็ยังอ่านยากอยู่บ้างเพราะฟังก์ชั่นที่จะเริ่มทำงานเป็นฟังก์ชันแรกคือฟังก์ชันที่อยู่ในสุด
Composition
เมื่อมันยังดูยากอยู่ เราลองมาสร้างฟังก์ชันหนึ่งซึ่งจะเกิดจากการประกอบหลายๆ ฟังก์ชันขึ้นมาเพื่อให้ได้ผลลัพธ์ตามที่เราต้องการ ลองมาดูตัวอย่างกันครับ
$compose = function($fn1, $fn2, $fn3, $fn4) {
return function($numbers) use ($fn1, $fn2, $fn3, $fn4) {
return $fn4($fn3($fn2($fn1($numbers))));
};
};// เมื่อเรียกใช้งานด้วยตัวแปรและฟังก์ชันจากตัวอย่างก่อนหน้านี้
$result = $compose(
$increment,
$fetch_even,
$to_string,
$concat)(numbers);
จะสังเกตว่ามันอ่านง่ายขึ้นมากตอนเรียกใช้งาน แต่มันติดปัญหาอยู่อีกอย่าง คือ เมื่อเราต้องการเพิ่มจำนวนฟังก์ชันที่ต้องการใช้งาน เราต้องเพิ่ม Argument เข้าไปเรื่อยๆ
เราสามารถแก้ไขปัญหานี้ได้ด้วยการใช้ Variadic functions เพื่อรับ Arguments ได้โดยไม่ต้องระบุจำนวนร่วมกับ Array reduce ครับ
$better_compose = function(...$fns) {// $fns ที่รับมาจะเป็นอาเรย์ของฟังก์ชัน
return function($numbers) use ($fns) {
return array_reduce($fns, function($result, $fn) {
return $fn($result);
}, $numbers);
};
};
// เมื่อเรียกใช้งานด้วยตัวแปรและฟังก์ชันจากตัวอย่างก่อนหน้านี้
$result = $better_compose(
$increment,
$fetch_even,
$to_string,
$concat)(numbers);
และหน้าตาโค้ดเราเมื่อเขียนใหม่แล้วก็จะหน้าตาแบบนี้ครับ
const numbers = [1, 2, 3, 4, 5, 6];
$increment = function($numbers) {
return array_map(function($number) {
return $number + 1;
}, $numbers);
};$fetch_even = function($numbers) {
return array_filter($numbers, function($number) {
return $number % 2 === 0;
});
};$to_string = function($numbers) {
return array_map(function($number) {
return (string) $number;
}, $numbers);
};$concat = function($strings) {
return array_reduce($strings, function($result, $string) {
return $result .= $string;
}, '');
};$better_compose = function(...$fns) {
return function($numbers) use ($fns) {
return array_reduce($fns, function($result, $fn) {
return $fn($result);
}, $numbers);
};
};$result = $better_compose(
$increment,
$fetch_even,
$to_string,
$concat)(numbers);
หวังว่าจะมีประโยชน์กับหลายๆ คนนะครับ ผิดพลาดประการใดหรือมีข้อแนะนำคอมเมนต์ได้เลยครับ 😀