[Collection] Higher Order Functions

Điểm qua một số lý thuyết trước khi bắt đầu nào!

Imperative vs. Declarative Programming

Bạn có thể đã từng nghe các thuật ngữ mệnh lệnh (hoặc thủ tục) và lập trình khai báo trước đây, và nếu bạn giống tôi, hãy tìm kiếm một định nghĩa chính xác về cả hai chỉ để tìm thấy một số mô tả mơ hồ không cung cấp cho bạn bất kỳ định nghĩa về câu trả lời cụ thể nào.

Theo thời gian, tôi nhận ra rằng điều này là do nó thực sự không phải là đen hay trắng. Đoạn mã A có thể mang tính khai báo nhiều hơn đoạn mã B, nhưng có thể đoạn mã C thậm chí còn mang tính khai báo nhiều hơn đoạn mã A.

Đây là cách tốt nhất của tôi để giải thích cách tôi nghĩ về lập trình mệnh lệnh và khai báo.

Imperative Programming

Lập trình theo lệnh hay còn được gọi với tên khác là lập trình hướng thủ tục (Procedural Programming)

Kiểu lập trình này tập trung vào việc như thế nàohow something gets done“. Code sẽ thường xuyên tập trung vào việc building kết quả trong các biến trung gian và quản lý các luồng thông qua vòng lặp và các điều kiện.

Chúng ta có 1 tập users và chúng ta muốn lấy danh sách email của các users. Lập trình theo lệnh bằng code PHP sẽ như sau

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getUserEmails($users)
{
$emails = [];

for ($i = 0; $i < count($users); $i++) {
$user = $users[$i];

if ($user->email !== null) {
$emails[] = $user->email;
}
}

return $emails;
}

Thay vì cố gắng nói “give me the emails of users who have emails”, việc giải quyết tập trung vào việc thực thi chi tiết vào việc số vòng lặp, truy cập vào chỉ số của cấu trúc dữ liệu và quản lý bộ đếm

Declarative Programming

Thay vì tập trung vào việc máy tính nên làm việc như thế nào thì declaractive programing tập trung vào việc cái gì - what “what we need it to accomplish” (những gì chúng ta cần nó để đạt được mục đích.)

So sánh đoạn code trên với toán tử giống hệt trong SQL:

1
SELECT email FROM users WHERE email IS NOT NULL

Chúng ta không phải viết bất cứ gì về vòng lặp, bộ đếm hay array indexs. Chúng ta chỉ nói với máy tính cái chúng ta muốn và không quan tâm nó lấy dữ liệu như thế nào.

Under the hood, I’m sure the SQL engine must be doing some sort of iteration or keeping track of which records it’s checked or which records it hasn’t, but I don’t really know for sure.

And that’s the beauty of it: I don’t need to know.

Và dĩ nhiên PHP và một ngôn ngữ khác SQL và chúng ta sẽ không thể tạo lại cú pháp chính xác đó.

Tuy nhiên chúng ta có thể tạo 1 cái gì đó gần giống? Chắc chắn là được, sử dụng Higher Order Functions!

Higher Order Functions

Tạm dịch là hàm bậc cao đi :D

A higher order function is a function that takes another function as a parameter, returns a function, or does both.
(Một higher order function là 1 hàm nhận 1 hàm khác như 1 tham số, trả về 1 hàm hoặc làm cả 2)

Xem xét ví dụ sau, hàm bọc code database transaction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function transaction($func)
{
$this->beginTransaction();

try {
$result = $func();
$this->commitTransaction();
} catch (Exception $e) {
$this->rollbackTransaction();
throw $e;
}

return $result
}

Và đây là cách chúng ta sử dụng:
1
2
3
4
5
6
7
try {
$databaseConnection->transaction(function () use ($comment) {
$comment->save();
});
} catch (Exception $e) {
echo "Something went wrong!";
}

Noticing Patterns

HOF có sức mạnh to lớn vì chúng cho phép chúng ta tạo sự trừu tượng xung quanh các mẫu lập trình phổ biến mà không thể sử dụng lại.

Trở lại bài toán lấy danh ách email của khách hàng. Chúng ta có thể làm mà không qua HOF như sau:

1
2
3
4
5
6
7
$customerEmails = [];

foreach ($customers as $customer) {
$customerEmails[] = $customer->email;
}

return $customerEmails;

Và bây giờ chúng ta cũng có danh sách các sản phẩm hàng tồn kho và chúng ta muốn biết tổng giá trị dư thừa của chúng ta là bao nhiêu:

1
2
3
4
5
6
7
8
9
10
$stockTotals = [];

foreach ($inventoryItems as $item) {
$stockTotals[] = [
'product' => $item->productName,
'total_value' => $item->quantity * $item->price,
];
}

return $stockTotals;

Nhìn lướt qua thì nó không có gì là quá trừu tượng hóa (abstract) được ở đây nhưng nếu bạn nhìn kỹ lại bạn sẽ nhận thấy chỉ có 1 điểm khác nhau thực sự giữa 2 ví dụ này.

Trong cả 2 ví dụ, tất cả cái chúng ta làm là tạo ra 1 mảng mới bằng cách áp dụng 1 vài toán tử với mỗi item trong danh sách đã tồn tại. Cái mà chỉ khác nhau giữa 2 vi dụ thực chất là toán tử mà ta áp dụng.

Trong ví dụ đầu tiên, chúng ta chỉ thêm trường email vào mỗi item.

Trong ví dụ thứ 2, chúng ta tạo ra một tập hợp mảng từ 1 vài trường của item.

Như vậy chúng ta đã thấy được sự trừu tượng có thể áp dụng ở đây, đó là việc truyền vào hàm xử lý cho mỗi xử lý là được:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function map($items, $func)
{
$results = [];

foreach ($items as $item) {
$results[] = $func($item);
}

return $results;
}

$customerEmails = map($customers, function ($customer) {
return $customer->email;
});

$stockTotals = map($inventoryItems, function ($item) {
return [
'product' => $item->productName,
'total_value' => $item->quantity * $item->price,
];
});
Author

Ming

Posted on

2019-12-27

Updated on

2024-08-08

Licensed under

Comments