# Applicative Functor — Kết hợp song song những “hộp” trong lập trình hàm > 📘 *Bài viết thuộc chuỗi “Lập trình hàm qua Functor – Applicative – Monad”, dựa trên ví dụ thực tế trong OCaml.* --- ## 1. Từ Functor đến Applicative Trước khi nói đến Applicative, ta nhớ lại **Functor**: Functor cho phép “nâng” một hàm **1 tham số** vào trong ngữ cảnh (hộp). ```ocaml Option.map (fun x -> x + 1) (Some 3) (* -> Some 4 *) ``` Nhưng nếu hàm có **2 tham số** (`+ : int -> int -> int`) thì sao? ```ocaml Option.map (+) (Some 3) (* -> Some (fun y -> 3 + y) *) ``` Kết quả lại là *một hộp chứa một hàm chưa đủ đối số*. Làm sao cho hàm trong hộp “ăn” thêm đối số khác cũng nằm trong hộp? Đó chính là lý do **Applicative Functor** ra đời. --- ## 2. Định nghĩa Applicative Applicative mở rộng từ Functor bằng 2 phép toán mới: ```ocaml val pure : 'a -> 'a t val (<*>) : ('a -> 'b) t -> 'a t -> 'b t ``` - `pure` đưa một giá trị thường vào hộp (ngữ cảnh). - `<*>` cho phép áp dụng **hàm trong hộp** lên **giá trị trong hộp**. Ví dụ: ```ocaml pure (fun x y -> x + y) <*> Some 3 <*> Some 4 (* -> Some 7 *) ``` Nếu một trong hai hộp là `None` → kết quả cũng `None`. --- ## 3. Ứng dụng thực tế: validate form Giả sử bạn có form đăng ký với ba trường: - `name` (không được rỗng) - `email` (phải chứa @) - `age` (phải ≥ 18) Mục tiêu: gom tất cả lỗi cùng lúc, chứ không dừng ở lỗi đầu tiên. ### Kiểu dữ liệu Validation ```ocaml type 'a validation = | Ok of 'a | Errors of string list let pure x = Ok x let (<*>) f x = match f, x with | Ok f, Ok v -> Ok (f v) | Errors e1, Ok _ -> Errors e1 | Ok _, Errors e2 -> Errors e2 | Errors e1, Errors e2 -> Errors (e1 @ e2) ``` --- ### Hàm kiểm tra từng field ```ocaml let validate_name n = if n <> "" then Ok n else Errors ["Name required"] let validate_email e = if String.contains e '@' then Ok e else Errors ["Invalid email"] let validate_age a = if a >= 18 then Ok a else Errors ["Too young"] ``` --- ### Gom kết quả bằng Applicative ```ocaml type user = { name: string; email: string; age: int } let make_user name email age = { name; email; age } let create_user name email age = pure make_user <*> validate_name name <*> validate_email email <*> validate_age age ``` Khi chạy: ```ocaml match create_user "" "abc" 15 with | Ok u -> Printf.printf "Created: %s, %s, %d\n" u.name u.email u.age | Errors errs -> List.iter print_endline errs ``` Kết quả: ``` Name required Invalid email Too young ``` --- ## 4. Minh họa trực quan ![ChatGPT Image Oct 5, 2025, 10_46_56 AM](https://hackmd.io/_uploads/B1li5PyTxl.png) > Mỗi `validate_*` là một hộp, `<*>` kết hợp chúng song song, và cuối cùng `pure make_user` hợp nhất kết quả. --- ## 5. So sánh với Functor và Monad | Mức độ | Toán tử chính | Ý nghĩa | Khi dùng | |--------|----------------|----------|-----------| | **Functor** | `map` | Nâng hàm 1 tham số lên “hộp” | Khi chỉ cần biến đổi 1 giá trị | | **Applicative** | `pure`, `<*>` | Nâng hàm nhiều tham số, kết hợp song song nhiều “hộp” | Khi có nhiều tác vụ độc lập, cần gom kết quả | | **Monad** | `>>=` | Chuỗi hóa các bước, bước sau phụ thuộc kết quả bước trước | Khi kết quả bước trước ảnh hưởng đến logic tiếp theo | Hình dung như ba tầng năng lực: ``` Functor : biến đổi giá trị trong hộp Applicative : kết hợp song song nhiều hộp Monad : xâu chuỗi tuần tự nhiều hộp ``` --- ## 6. Kết luận **Applicative Functor** là cầu nối giữa **Functor** và **Monad**: - Dễ dùng, tổng quát, không cần quan tâm thứ tự. - Code gọn, tái sử dụng được. - Đặc biệt hữu ích trong validation, xử lý song song, hoặc lập trình phản ứng (reactive programming). > Khi bạn thấy code pattern match quá nhiều, hoặc muốn kết hợp nhiều tác vụ độc lập — đó là lúc Applicative lên tiếng. 😎