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

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

Những mẩu tin ngắn hàng ngày dành cho bạn
  • countless.dev là một trang web khá thú vị khi mà nó so sánh giá tiền sử dụng các mô hình LLMs của các nhà cung cấp khác nhau.

    Tại đây bạn có thể nhìn thấy tất cả các mô hình ngôn ngữ lớn phổ biến bởi các nhà cung cấp như OpenAI, Azure, Mistral... Bảng giá cho mỗi 1M tokens đầu vào/ra. Hoặc thậm chí có thể so sánh chúng với nhau để tìm ra nhà cung cấp hoặc mô hình rẻ nhất tuỳ theo mục đích sử dụng.

    » Xem thêm
  • 1,2 năm trước, Kubernetes (k8s) tự nhiên được nhắc đến như một hiện tượng, chắc vì nó bá quá nên ai cũng muốn học và sử dụng. Nó là một công cụ "Automating deployment, scaling, and management of containerized applications" - Vâng! nghe hay ho đấy chứ 🤤.

    Hồi đó thì mình đang đam mê với Docker, đặc biệt là Docker Swarm, cũng tương tự như k8s ý nhưng ở quy mô nhỏ hơn. Docker Swarm thì có vẻ ít phức tạp hơn nhiều so với k8s. Mà như thế cũng tốt vì nó đã và đang đáp ứng rất tốt nhu cầu sử dụng của mình lúc đó, lại còn bớt đi phần phức tạp, lằng nhằng.

    Ấy thế mà 1-2 tháng trở lại đây, các bài viết có tiêu đề "bạn có thực sự cần đến Kubernetes" lại đang nổi lên với tần suất dày hơn. Quả thật k8s rất mạnh nhưng cũng quá phức tạp. Tại sao phải cố dùng dao "mổ trâu để giết gà" cơ chứ? Trừ khi bạn lường trước được độ phức tạp khi muốn áp dụng một công nghệ. Một cái nữa k8s tiêu tốn tài nguyên và nguồn lực ghê ghớm, để vận hành được nó không đơn giản là dựng lên được là xong mà còn phải có rất nhiều nhiều kiến thức nữa 😨.

    À, chắc cũng một phần nữa là do các "ông lớn" đang tập trung đẩy mạnh vào Serverless, giảm bớt sự phức tạp trong khâu vận hành đi, thay vào đó là nên tập trung vào phát triển ứng dụng.

    Bên cạnh đó, thì cái tên WASM cũng đang được nhắc đến rất là nhiều 🤔

    Do you really need Kubernetes in your company/startup? | dev.to

    Do You Really Need Kubernetes?

    » Xem thêm
  • Trước mình cứ khen lấy khen để Serverless, rằng tối ưu chi phí xuống 0đ để duy trì blog các thứ. Đúng là như vậy thật! Nhưng bên cạnh đó serverless cũng có các mặt tối đáng để lưu tâm đấy!

    Hôm kia mình phải mất ngày trời để truy tìm và khắc phục sự cố chỉ vì gọi hàm build-in của Cloudflare KV. Cụ thể là hàm list với limit 1000 - tức là một lần gọi nó trả về 1000 keys của KV. Cơ mà đời không như là mơ. Con số 1000 chỉ là trên lý thuyết. Lúc thì trả về vài trăm, lúc thì vài chục, thậm chí lúc thì lẹt đẹt có vài cái. Thế là làm tắc nghẽn cả hệ thống. À mà cũng không phải là nghẽn mà là hệ thống "nhàn rỗi" quá không có việc gì để làm, trong khi thực tế đáng ra nó phải xử lý cả trăm ngàn cái keys cơ 🥲

    » 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

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? 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.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

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