Tổng quan thô
Trong lập trình chức năng, functor về cơ bản là một cấu trúc nâng các hàm unary thông thường (tức là các hàm có một đối số) thành các hàm giữa các biến của các kiểu mới. Việc viết và duy trì các hàm đơn giản giữa các đối tượng đơn giản sẽ dễ dàng hơn nhiều và sử dụng hàm functor để nâng chúng, sau đó viết thủ công các hàm giữa các đối tượng chứa phức tạp. Ưu điểm hơn nữa là chỉ viết các hàm đơn giản một lần và sau đó sử dụng lại chúng thông qua các hàm xử lý khác nhau.
Ví dụ về functor bao gồm mảng, "có thể" và "hoặc" functor, tương lai (xem ví dụ https://github.com/Avaq/Fluture ) và nhiều thứ khác.
Hình minh họa
Hãy xem xét chức năng xây dựng tên của người đầy đủ từ tên và họ. Chúng ta có thể định nghĩa nó giống fullName(firstName, lastName)
như chức năng của hai đối số, tuy nhiên sẽ không phù hợp với các hàm xử lý chỉ xử lý các chức năng của một đối số. Để khắc phục, chúng tôi thu thập tất cả các đối số trong một đối tượng name
, giờ đây trở thành đối số duy nhất của hàm:
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
Bây giờ nếu chúng ta có nhiều người trong một mảng thì sao? Thay vì tự đi qua danh sách, chúng ta chỉ cần sử dụng lại hàm của mình fullName
thông qua map
phương thức được cung cấp cho các mảng với một dòng mã ngắn:
fullNameList = nameList => nameList.map(fullName)
và sử dụng nó như
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
Điều đó sẽ hoạt động, bất cứ khi nào mỗi mục trong chúng tôi nameList
là một đối tượng cung cấp cả hai firstName
và lastName
thuộc tính. Nhưng nếu một số đối tượng không (hoặc thậm chí không phải là đối tượng) thì sao? Để tránh các lỗi và làm cho mã an toàn hơn, chúng ta có thể bọc các đối tượng của mình thành Maybe
loại (ví dụ: https : //sanc Sanctuary.js.org/#maybe-type ):
// function to test name for validity
isValidName = name =>
(typeof name === 'object')
&& (typeof name.firstName === 'string')
&& (typeof name.lastName === 'string')
// wrap into the Maybe type
maybeName = name =>
isValidName(name) ? Just(name) : Nothing()
trong đó Just(name)
một container chỉ chứa các tên hợp lệ và Nothing()
là giá trị đặc biệt được sử dụng cho mọi thứ khác. Bây giờ thay vì ngắt (hoặc quên) để kiểm tra tính hợp lệ của các đối số của chúng tôi, chúng tôi chỉ có thể sử dụng lại (nâng) fullName
hàm ban đầu của chúng tôi bằng một dòng mã khác, dựa trên map
phương thức, lần này được cung cấp cho loại Có thể:
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
và sử dụng nó như
justSteve = maybeName(
{firstName: 'Steve', lastName: 'Jobs'}
) // => Just({firstName: 'Steve', lastName: 'Jobs'})
notSteve = maybeName(
{lastName: 'SomeJobs'}
) // => Nothing()
steveFN = maybeFullName(justSteve)
// => Just('Steve Jobs')
notSteveFN = maybeFullName(notSteve)
// => Nothing()
Lý thuyết danh mục
Một Functor trong Lý thuyết Danh mục là một bản đồ giữa hai loại tôn trọng thành phần hình thái của chúng. Trong Ngôn ngữ máy tính , Danh mục quan tâm chính là đối tượng có đối tượng là các loại (bộ giá trị nhất định) và hình thái của chúng là các chức năng f:a->b
từ loại này a
sang loại khác b
.
Ví dụ, lấy a
là String
loại, b
loại Số và f
là hàm ánh xạ một chuỗi thành độ dài của nó:
// f :: String -> Number
f = str => str.length
Ở đây a = String
đại diện cho tập hợp của tất cả các chuỗi và b = Number
tập hợp của tất cả các số. Theo nghĩa đó, cả hai a
và b
đại diện cho các đối tượng trong Danh mục tập hợp (liên quan chặt chẽ đến danh mục các loại, với sự khác biệt là không cần thiết ở đây). Trong Danh mục nhóm, hình thái giữa hai bộ chính xác là tất cả các chức năng từ bộ thứ nhất đến bộ thứ hai. Vì vậy, hàm độ dài của chúng ta f
ở đây là một hình thái từ tập hợp chuỗi thành tập hợp số.
Vì chúng ta chỉ xem xét phạm trù tập hợp, các Functor có liên quan từ chính nó vào bản đồ là các bản đồ gửi các đối tượng đến các đối tượng và hình thái đến các hình thái, đáp ứng các định luật đại số nhất định.
Thí dụ: Array
Array
có thể có nghĩa là nhiều thứ, nhưng chỉ có một thứ là Functor - kiểu xây dựng, ánh xạ một kiểu a
thành kiểu [a]
của tất cả các mảng kiểu a
. Ví dụ, Array
functor ánh xạ loại String
thành loại [String]
(tập hợp tất cả các mảng của chuỗi có độ dài tùy ý) và đặt loại Number
thành loại tương ứng [Number]
(tập hợp tất cả các mảng số).
Điều quan trọng là không nhầm lẫn bản đồ Functor
Array :: a => [a]
với một hình thái a -> [a]
. Functor chỉ đơn giản là ánh xạ (liên kết) loại a
thành loại [a]
như một thứ khác. Rằng mỗi loại thực sự là một tập hợp các yếu tố, không liên quan ở đây. Ngược lại, một hình thái là một chức năng thực tế giữa các bộ đó. Ví dụ, có một hình thái tự nhiên (chức năng)
pure :: a -> [a]
pure = x => [x]
sẽ gửi một giá trị vào mảng 1 phần tử với giá trị đó dưới dạng một mục nhập. Chức năng đó không phải là một phần của Array
Functor! Theo quan điểm của functor này, pure
chỉ là một chức năng như bất kỳ khác, không có gì đặc biệt.
Mặt khác, Array
Functor có phần thứ hai - phần hình thái. Mà ánh xạ một hình thái f :: a -> b
thành một hình thái [f] :: [a] -> [b]
:
// a -> [a]
Array.map(f) = arr => arr.map(f)
Đây arr
là bất kỳ mảng nào có độ dài tùy ý với các giá trị của loại a
và arr.map(f)
là mảng có cùng độ dài với các giá trị của loại b
, có các mục nhập là kết quả của việc áp dụng f
cho các mục nhập của arr
. Để làm cho nó trở thành một functor, các định luật toán học về ánh xạ nhận dạng đến nhận dạng và các tác phẩm thành các tác phẩm phải được giữ, rất dễ kiểm tra trong Array
ví dụ này .