Giải thích về JavaScript


Võ Mạnh Tuấn
4 năm trước
Hữu ích 9 Chia sẻ Viết bình luận 0
Đã xem 9394

Về bản chất, functor không gì khác hơn là một cấu trúc dữ liệu mà bạn có thể ánh xạ các hàm với mục đích nâng các giá trị từ một thùng chứa, sửa đổi chúng, sau đó đưa chúng trở lại vào một thùng chứa. Nói một cách đơn giản, đó là một mẫu thiết kế xác định ngữ nghĩa cho cách hoạt động của fmap. Đây là định nghĩa chung của fmap:

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

Hàm fmap lấy một hàm (từ A -> B) và hàm Wrapper (ngữ cảnh được bao bọc) (A) và trả về một hàm functor Wrapper (B) mới chứa kết quả của việc áp dụng hàm đã nói lên giá trị và sau đó đóng lại một lần nữa. Dưới đây là một ví dụ nhanh sử dụng hàm tăng làm hàm ánh xạ của chúng tôi từ A -> B (ngoại trừ trong trường hợp này A và B là cùng loại): 

 

Hình 1 Giá trị 1 được chứa trong một thùng chứa W, functor được gọi với hàm bao bọc đã nói và hàm tăng, biến đổi giá trị bên trong và đóng lại vào một thùng chứa.

Lưu ý rằng vì về cơ bản fmap trả về một bản sao mới của container tại mỗi lần gọi, nên nó có thể được coi là không thay đổi. 

Lý thuyết Functor

Một cuộc thảo luận về functor có thể dễ dàng có được rất chính thức và lý thuyết. Nếu bạn thực hiện tìm kiếm trên web nhanh chóng cho functor, bạn sẽ tìm thấy các bài viết sẽ bắn phá bạn bằng các thuật ngữ như: hình thái và thể loại. Lý do cho điều này là, giống như tất cả các kỹ thuật lập trình chức năng, functor bắt nguồn từ toán học trong trường hợp này, lý thuyết thể loại. 

Không đi sâu vào cỏ dại, tôi có thể giải thích ý nghĩa cơ bản của việc này. Functor được định nghĩa là: hình thái của người khác nhau giữa các thể loại. Tất cả điều này thực sự có nghĩa là functor là một thực thể xác định hành vi của (fmap), được đưa ra một giá trị và chức năng (morphism), ánh xạ hàm nói lên một giá trị của loại nhất định (thể loại) và tạo ra một functor mới. 


Thật vậy, đây là một chút lý thuyết để hiểu. Chúng ta hãy đi qua một ví dụ rất đơn giản. Hãy xem xét một phép cộng 2 + 3 = 5 đơn giản bằng cách sử dụng hàm functor. Tôi có thể curry một hàm add đơn giản để tạo hàm plus3 như sau:

var plus = R.curry((a, b) => a + b);  var plus3 = plus(3);

Bây giờ tôi sẽ lưu trữ số hai vào một functor Wrapper đơn giản:

var two = wrap(2);

Gọi fmap để ánh xạ plus3 qua container thực hiện bổ sung: 

var five = two.fmap(plus3); //-> Wrapper(5)

 five.map(R.identity); //-> 5

Kết quả của fmap mang lại một bối cảnh khác cùng loại, mà tôi có thể ánh xạ R.identity qua để trích xuất giá trị của nó. Lưu ý rằng, vì giá trị không bao giờ thoát khỏi trình bao bọc, tôi có thể ánh xạ nhiều hàm như tôi muốn lên nó và biến đổi giá trị của nó ở mỗi bước trên đường:

two.fmap(plus3).fmap(plus10); //-> Wrapper(15)

Điều này có thể khó hiểu, vì vậy đây là một hình ảnh về cách fmap hoạt động trở lại với plus3 trong hình này:

 

Hình 2 Giá trị 2 đã được thêm vào thùng chứa Wrapper. Functor được sử dụng để thao túng giá trị này, bằng cách trước tiên mở khóa nó khỏi bối cảnh, áp dụng hàm đã cho vào nó và bọc lại giá trị đó vào một bối cảnh mới.

Mục đích của việc có fmap trả về cùng loại (hoặc bọc kết quả lại vào một thùng chứa) là để chúng ta có thể tiếp tục các hoạt động chuỗi. Xem xét ví dụ sau đây ánh xạ cộng với giá trị được bọc và ghi lại kết quả như được hiển thị trong đoạn mã sau:

var two = wrap(2);

two.fmap(plus3).fmap(R.tap(infoLogger)); //-> Wrapper(5)

Chạy mã này in thông báo sau trên bảng điều khiển:

InfoLogger [THÔNG TIN] 5

Ý tưởng về chức năng xích này nghe có vẻ quen thuộc phải không? Trên thực tế, bạn đã sử dụng functor suốt mà không nhận ra điều đó. Đây chính xác là những gì mà các chức năng ánh xạ và bộ lọc thực hiện cho các mảng:

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

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

Bản đồ chức năng và bộ lọc là sự đồng hình của người Viking giữa các danh mục. Lý do là cả hai chức năng bảo tồn cùng một loại:

  • đồng tính : giống nhau 

  • morphism : một chức năng duy trì cấu trúc

  • loại : loại giá trị chứa

Mở rộng khái niệm này thành các hàm, hãy xem xét một loại khác của hàm functor đồng hình mà bạn đã thấy tất cả cùng: sáng tác. Như bạn có thể biết, hàm soạn thảo là ánh xạ từ các hàm sang các hàm khác:

compose :: (B -> C) -> (A -> B) -> (A -> C)

Functor, giống như bất kỳ tạo phẩm lập trình chức năng nào khác, bị chi phối bởi một số thuộc tính quan trọng:

Chúng phải có tác dụng phụ miễn phí: ánh xạ hàm R.identity có thể được sử dụng để có cùng giá trị trong một bối cảnh. Bằng chứng này, chúng không có tác dụng phụ và bảo tồn cấu trúc của giá trị được bọc. 

wrap('Get Functional').fmap(R.identity); //-> Wrapper('Get Functional')

Chúng phải có khả năng tổng hợp: thuộc tính này biểu thị thành phần của hàm được áp dụng cho fmap phải giống hệt như chuỗi các hàm fmap với nhau. Kết quả là, biểu thức sau đây chính xác tương đương với chương trình trước đó:

two.fmap(R.compose(plus3, R.tap(infoLogger))).map(R.identity); //-> 5

Các cấu trúc như functor bị cấm ném ngoại lệ, biến đổi các thành phần trong danh sách hoặc thay đổi hành vi của chức năng. Mục đích thực tế của họ là tạo ra một bối cảnh cho phép bạn thao tác an toàn và áp dụng các hoạt động cho các giá trị, mà không thay đổi giá trị ban đầu. Điều này thể hiện rõ qua cách bản đồ biến đổi một mảng thành một mảng khác mà không làm thay đổi mảng ban đầu; khái niệm này dịch tương tự cho bất kỳ loại container. 

Tuy nhiên, functor tự chúng không quá hấp dẫn và sẽ thất bại khi có dữ liệu null, giống như functor bản đồ mảng bỏ qua các phần tử null và soạn thảo một cách hiệu quả, sẽ bỏ qua việc gọi một đối tượng hàm null. Điều này tương tự với việc có một khối bắt trống để bỏ qua thất bại. Tuy nhiên, trong thực tế, bạn sẽ cần xử lý đúng lỗi và đối với điều này, bạn sẽ cần một loại dữ liệu chức năng mới gọi là Monads . Bạn có thể tìm hiểu thêm về functor và Monads, trong cuốn sách Lập trình chức năng bằng JavaScript của tôi.


Để tìm hiểu thêm về Lập trình chức năng, hãy tải xuống Lập trình chức năng của DZone với JavaScript Refcard của Luis Atencio .

Hữu ích 9 Chia sẻ Viết bình luận 0
Đã xem 9394