Tìm hiểu functor, applicative functor và monad bằng TypeScript
(evan-moon.github.io)Nội dung
- Giải thích bằng mã TypeScript quá trình đi từ hai vấn đề mà chỉ
mapcủa functor không thể giải quyết được (vấn đề hàm bị mắc kẹt trong container và vấn đề lồng ngữ cảnh khi hợp thành) đến applicative functor và monad - Bắt đầu từ bối cảnh năm 1988 khi Eugenio Moggi mô hình hóa chương trình thành
A → T(B)thay vìA → B - Trình bày cấu trúc
flatMap = map + joinvà ba luật để sử dụng an toàn cấu trúc này (kết hợp, đơn vị trái, đơn vị phải) - Giải thích vì sao monad là "đối tượng monoid trong phạm trù endofunctor" bằng cách đối chiếu với monoid của phép cộng số nguyên
- Cũng đề cập lý do Promise hoạt động theo kiểu monadic nhưng không phải là monad toán học theo nghĩa chặt chẽ
Giới hạn của functor: những gì map không làm được
- Khi áp dụng một hàm đã được curry bằng
map, kết quả làMaybe<(b: number) => number>nên hàm bị mắc kẹt trong containermapchỉ có thể nhận hàm ở bên ngoài container, nên không có cách nào áp dụng hàm bị nhốt bên trong cho một giá trị khác
- Khi hợp thành hai hàm trả về functor, ngữ cảnh sẽ bị lồng như
Maybe<Maybe>- Càng nhiều bước thì càng lồng vô hạn thành
Maybe<Maybe<Maybe<...>>>
- Càng nhiều bước thì càng lồng vô hạn thành
Applicative functor: áp dụng hàm bên trong container
- Với phép toán
apply, có thể áp dụng một hàm bị giữ trong container lên giá trị của container khácapply: T<(A → B)> → T<A> → T<B>
- Với phép toán
pure, chèn một giá trị thuần vào container - Giới hạn: phải xác định trước sẽ hợp thành những container nào
- Không thể biểu diễn sự phụ thuộc tuần tự động, tức quyết định phép tính tiếp theo dựa trên kết quả của phép tính trước
Monad: phát minh ra phép toán làm phẳng lồng nhau
- Phép toán
joinbiếnT<T<A>> → T<A>, làm phẳng container kép thành một lớp duy nhấtArray.prototype.flatcủa JavaScript đóng cùng vai trò
- Trong thực tế, người ta dùng
flatMap, là sự kết hợp củamap + joinflatMap: T<A> → (A → T<B>) → T<B>mapnhậnA → B, cònflatMapnhậnA → T<B>để giữ kết quả ở một lớp duy nhất
Ba luật của flatMap
- Luật kết hợp: khi làm phẳng ba lớp lồng nhau
T(T(T(A))), dù làm phẳng từ trong ra ngoài
hay từ ngoài vào trong thì kết quả cũng phải giống nhaum.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))
- Luật đơn vị trái: nếu đưa vào bằng
purerồiflatMapngay, thì tương đương với việc áp dụng trực tiếp hàm đópure(a).flatMap(f) === f(a)
- Luật đơn vị phải: nếu truyền
purevàoflatMap, ta nhận lại đúng container ban đầum.flatMap(pure) === m
Phân tích cụm từ "đối tượng monoid trong phạm trù endofunctor"
- Functor trong lập trình đi từ thế giới kiểu dữ liệu đến chính thế giới kiểu dữ liệu, nên là endofunctor
- Có thể xây dựng một phạm trù endofunctor, trong đó chính các endofunctor là đối tượng
- Nếu thay các điều kiện của monoid (phép toán nhị phân + luật kết hợp + phần tử đơn vị) vào thì sẽ có:
- phép toán nhị phân =
join - phần tử đơn vị =
pure - Cấu trúc này tương ứng chính xác với monoid của phép cộng số nguyên
- phép toán nhị phân =
Vì sao Promise không phải là monad
thenxử lý lẫn lộnmapvàflatMaptùy theo giá trị trả về- Trạng thái
Promise<Promise>không được cho phép ở runtime và lập tức được gộp thành một
lớp duy nhất - Dù tiện lợi trong thực tế, nó không thỏa các luật monad theo nghĩa toán học
1 bình luận
Hãy đề cập cả Comonad nữa!