Functor là gì? Tôi có cần biết đến functor?

Functor là gì? Tôi có cần biết đến functor?

Tin ngắn hàng ngày dành cho bạn
  • Cũng giống như 12 Days of OpenAI - một chuỗi sự kiện diễn ra trong 12 ngày liên tiếp của OpenAI, mỗi ngày họ sẽ giới thiệu một công cụ "đột phá", và cứ như thế.

    DeepSeek đã bắt "trend" ngay sau đó với chuỗi 202502 Open-Source Week diễn ra ngay trong tuần sau. Mỗi ngày họ sẽ công bố một công cụ mã nguồn mở, trái được hoàn toàn với tính "Open" của AI. Chúng ta hãy chờ xem họ mang đến những dự án thú vị nào nhé 🤓. Chắc sẽ hấp dẫn lắm đây vì ai cũng biết từ lúc ra mô hình R1, Deepseek đã chiếm trọn tin tức nổi bật trên toàn thế giới.

    » Xem thêm
  • Grok 3 beta vừa ra mắt và cho mọi người dùng thử miễn phí có giới hạn số lần trong ngày (tài khoản trả phí hình như được dùng nhiều hơn). Trong đó có 2 tính năng nổi bật là Think và DeepSearch.

    Think thì chắc ai cũng biết hoặc dùng ở một số mô hình suy luận như ở ChatGPT rồi. Còn DeepSearch thì mới hơn, gõ điều bạn muốn vào thì nó sẽ tự lên mạng tìm kiếm thông tin rồi tổng hợp lại kết quả mà nó tìm thấy được. Khá hay nhưng chắc để tham khảo hoặc muốn tổng hợp thông tin nhanh chóng thôi chứ vẫn nên tự mình tìm kiếm thông tin 😅

    » Xem thêm
  • Có 2 phần mềm tiện ích cho Mac mà mỗi khi dùng máy Mac Mini hoặc Macbook có cắm thêm màn hình rời, thêm bàn phím với chuột nữa là BetterDisplayMac Mouse Fix. Đi qua từng cái nhé!

    BetterDisplay giúp tinh chỉnh kích thước của màn hình rời để đạt độ phân giải HiDPI. Như bạn biết Mac khá kén màn hình và tuỳ chọn độ phân giải trong cài đặt mặc định rất ít ỏi, nên BetterDisplay cung cấp nhiều lựa chọn phù hợp hơn.

    Cái thứ hai là Mac Mouse Fix, nếu dùng chuột ngoài bạn sẽ thấy nó cuộn không giống với Trackpad của Macbook cho lắm. Điều kì diệu xảy ra khi cài phần mềm này vào. Nó thêm hiệu ứng "smooth" và giúp cuộn chuột y như cuộn bằng Trackpad luôn. Thật thần kỳ.

    » Xem thêm

Vấn đề

Khái niệm Functor là một bước đệm để từ đó giúp cho bạn khám phá ra những điều mới mẻ trong thế giới lập trình hàm. Vậy thì functor là gì và nó mang lại lợi ích gì trong lập trình?

Functor là gì?

Về bản chất, functor là một cấu trúc dữ liệu mà bạn có thể map qua chúng để áp dụng một hàm vào từng phần tử với mục đích sửa đổi dữ liệu. Nhưng một điều quan trọng là dữ liệu đó được chứa trong một "vùng chứa", để có thể sửa được giá trị thì các hàm phải lấy ra, sửa đổi rồi đặt giá trị vào "vùng chứa".

Functor hay còn được kí hiệu là fmap. Đây là định nghĩa chung của fmap:

fmap :: (A -> B) -> Wrapper(A) -> Wrapper(B)

Hàm fmap nhận một hàm (A -> B) biến đổi hàm Wrapper(A) thành Wrapper(B) sau khi đã thực hiện việc biến đổi các giá trị A thành B. Để hiểu rõ hơn bạn có thể xem hình dưới:

Wrapper

Chúng ta thấy giá trị 1 được lấy ra khỏi "vùng chứa" -> áp dụng hàm -> đặt lại vào "vùng chứa".

Về cơ bản fmap sẽ trả về một bản sao mới của "vùng chứa" tại mỗi lần gọi nên nó có thể coi là bất biến.

Đó là lý thuyết, hãy để tôi lấy một ví dụ cụ thể: Biểu diễn phép tính 2 + 3 = 5 bằng functor.

Đầu tiên tôi sẽ xây dựng một class Wrapper nhận vào một giá trị, class này có hai methods: fmap để biến đổi và indentity để lấy ra giá trị:

class Wrapper {
  constructor(value) {
    this.value = value;
  }

  fmap(fn) {
    return new Wrapper(fn(this.value));
  }

  identity() {
    return this.value;
  }

  map(fn) {
    return fn(this.value);
  }
}

fmap nhận vào một hàm, dùng hàm đó để biến đổi value và lại đặt vào Wrapper. identity chỉ đơn giản là trả về value.

Tôi sẽ sử dụng curry function để thực hiện phép cộng. Nếu chưa biết về curry bạn có thể đọc bài viết Curry function là gì? Một món "cà ri" ngon và làm sao để thưởng thức nó?.

const plus = a => b => a + b;
const plus3 = plus(3);

const two = new Wrapper(2);
const sum = two.fmap(plus3); // Wrapper(5)
sum.identity(); // 5

Đến đây thì các bạn có phát hiện ra điều gì thú vị không? Đúng rồi đó, sum vẫn có thể tiếp tục sử dụng được hàm fmap hay nói cách khác là khi kết quả xử lý trả về một đối tượng là Wrapper thì chúng ta sẽ không phải lo lắng về tính liên tục của dữ liệu sau xử lý. Tôi có thể tiếp tục cộng trừ nhân chi một cách liên tiếp:

const multi = a => b => a * b;
const multi5 = multi(5);
sum.fmap(multi5).identity(); // 25

Khi kết quả của hàm fmap trả về là một Wrapper thì nó đảm bảo được rằng kết quả vẫn mang những tính chất của Wrapper.

Thật thú vị phải không? Ý tưởng về chuỗi các hàm có làm bạn liên tưởng đến hàm map hay filter trong Javascript? Thật vậy đó chính xác là những triển khai của functor.

map :: (A -> B) -> Array(A) -> Array(B)
filter :: (A -> Boolean) -> Array(A) -> Array(A)

mapfilter được coi là functor bởi chúng có những đặc điểm của functor:

  • Giống nhau
  • Duy trì cấu trúc
  • Loại giá trị

Functor cần phải đảm bảo được một số thuộc tính quan trọng:

Không gây ra side effect: có thể fmap qua một hàm identity để có được cùng một giá trị trong một ngữ cảnh. Điều này chứng minh được rằng chúng không gây ra side effect và vẫn bảo toàn cấu trúc của giá trị được bao bọc. Bạn có thể hiểu identity là một hàm chỉ đơn giản là trả về giá trị mà nó nhận được.

Wrapper('Get Functional').fmap(x => x); // Wrapper('Get Functional')

Thứ hai, chúng phải có thể kết hợp được. Tức là có thể fmap được liên tục. Để đảm bảo được điều này, các cấu trúc điều khiển ví dụ như fmap phải không được ném ra exception, thay đổi các phần tử trong danh sách hoặc thay đổi hành vi của một hàm. Mục đích là tạo ra một ngữ cảnh cho phép bạn thao tác vào các giá trị mà không làm thay đổi giá trị ban đầu. Điều này thể hiện rõ ràng trong việc hàm map biến đổi mảng này thành mảng khác mà không làm thay đổi mảng ban đầu.

Tuy nhiên trong lập trình không phải lúc nào ta cũng có dữ liệu hoàn hảo, mà chúng ta vẫn phải xử lý những exception, những giá trị như null, undefined... Lúc này việc áp dụng các functor sẽ không còn hoàn hảo nữa.

const div = a => b => b/a;
const subtr = a => b => a - b;
const plus = a => b => a + b;

const divided5 = div(5);
const subtr2 = subtr(2);
const plus3 = plus(3);

const two = Wrapper(2);
two.fmap(subtr2).fmap(divided5).fmap(plus3); // Wrapper(NaN)

Tổng kết

Functor là một cấu trúc dữ liệu lưu trữ dữ liệu ở trong một "vùng chứa", nó cung cấp các phương thức để thao tác với dữ liệu ở trong "vùng chứa" đó. Sử dụng functor chúng ta sẽ đảm bảo được đầu ra của dữ liệu sẽ không bị thay đổi kiểu, nó giống với việc hàm map nhận vào một array và luôn luôn trả ra một array.

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (2)

Nội dung bình luận...
Avatar
Update Group1 năm trước
Làm về monad, applicative, monoid chưa ạ?
Trả lời
Avatar
Xuân Hoài Tống1 năm trước
Chào bạn, cảm ơn bạn đã theo dõi mình suốt thời gian qua. Hiện tại mình vẫn đang bận rộn với những kế hoạch đề ra từ trước nên trước mắt vẫn chưa hoàn thành bài viết này được. Mong bạn thông cảm và tiếp tục ủng hộ :D
Avatar
Update Group2 năm trước
Làm về monad, applicative, monoid đi ạ
Trả lời
Avatar
Update Group1 năm trước
Làm về monad, applicative, monoid có chưa ạ
Avatar
Xuân Hoài Tống2 năm trước
Hi bạn, mình cũng đang trong quá trình tìm hiểu về lập trình hàm thôi nên nếu cảm thấy đủ hiểu thì mình sẽ viết các bài chi tiết