ภาพรวมคร่าวๆ
ในฟังก์ชั่นการเขียนโปรแกรมฟังก์ชั่นfunctorเป็นโครงสร้างของการยกฟังก์ชั่นยูนารีธรรมดา(เช่นที่มีอาร์กิวเมนต์เดียว) เพื่อฟังก์ชั่นระหว่างตัวแปรประเภทใหม่ มันง่ายกว่ามากในการเขียนและบำรุงรักษาฟังก์ชั่นที่เรียบง่ายระหว่างวัตถุธรรมดาและใช้ functors เพื่อยกพวกเขาจากนั้นจะเขียนฟังก์ชั่นระหว่างวัตถุภาชนะที่ซับซ้อนด้วยตนเอง ข้อได้เปรียบเพิ่มเติมคือการเขียนฟังก์ชั่นธรรมดาเพียงครั้งเดียวแล้วนำกลับมาใช้ใหม่ผ่านฟังก์ชั่นที่แตกต่างกัน
ตัวอย่างของ functors รวมถึงอาร์เรย์ "บางที" และ "อย่างใดอย่างหนึ่ง" functors ฟิวเจอร์ส (ดูเช่นhttps://github.com/Avaq/Fluture ) และอื่น ๆ อีกมากมาย
ภาพประกอบ
พิจารณาฟังก์ชั่นการสร้างชื่อเต็มของบุคคลจากชื่อและนามสกุล เราสามารถนิยามว่ามันfullName(firstName, lastName)
เป็นฟังก์ชั่นของสองข้อโต้แย้งซึ่งจะไม่เหมาะสำหรับ functors ที่จัดการกับฟังก์ชั่นของการขัดแย้งเดียว เพื่อแก้ไขเรารวบรวมข้อโต้แย้งทั้งหมดในวัตถุเดียวname
ซึ่งตอนนี้กลายเป็นอาร์กิวเมนต์เดียวของฟังก์ชั่น:
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
แล้วถ้าเรามีคนมากมายในอาเรย์ล่ะ? แทนที่จะไปดูรายการด้วยตนเองเราสามารถใช้ฟังก์ชั่นของเราได้อีกครั้งfullName
ผ่านmap
วิธีการที่กำหนดไว้สำหรับอาร์เรย์ด้วยรหัสบรรทัดเดียวขนาดสั้น:
fullNameList = nameList => nameList.map(fullName)
และใช้มันเหมือน
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
สิ่งนี้จะใช้ได้เมื่อทุกรายการในของเราnameList
เป็นวัตถุที่ให้ทั้งคุณสมบัติfirstName
และ lastName
แต่ถ้าวัตถุบางอย่างไม่ได้ (หรือแม้กระทั่งไม่ได้วัตถุเลย)? เพื่อหลีกเลี่ยงข้อผิดพลาดและทำให้รหัสปลอดภัยยิ่งขึ้นเราสามารถห่อวัตถุของเราเป็นMaybe
ประเภท (se เช่นhttps://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()
โดยที่Just(name)
container เป็นชื่อที่ถูกต้องเท่านั้นและNothing()
เป็นค่าพิเศษที่ใช้สำหรับทุกอย่าง ตอนนี้แทนที่จะขัดจังหวะ (หรือลืม) เพื่อตรวจสอบความถูกต้องของข้อโต้แย้งของเราเราสามารถนำfullName
ฟังก์ชันเดิมของเรากลับมาใช้ใหม่โดยใช้รหัสบรรทัดเดียวอีกบรรทัดโดยใช้map
วิธีการอีกครั้งคราวนี้เป็นประเภทประเภทบางที:
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
และใช้มันเหมือน
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()
ทฤษฎีหมวดหมู่
Functorในทฤษฎีประเภทคือแผนที่ระหว่างสองประเภทเคารพองค์ประกอบของ morphisms ของพวกเขา ในภาษาคอมพิวเตอร์หมวดหมู่ความสนใจหลักคือวัตถุที่มีประเภท (ชุดค่าบางอย่าง) และฟังก์ชันที่มีmorphismsเป็นf:a->b
ประเภทจากประเภทหนึ่งa
ไปยังอีกประเภทb
หนึ่ง
ตัวอย่างเช่นรับa
เป็นString
ชนิดb
Number และf
ฟังก์ชันการแม็พสตริงเข้ากับความยาว:
// f :: String -> Number
f = str => str.length
นี่คือa = String
ชุดของสตริงทั้งหมดและb = Number
ชุดของตัวเลขทั้งหมด ในแง่นั้นทั้งa
และb
แสดงวัตถุในหมวดหมู่ชุด (ซึ่งเกี่ยวข้องอย่างใกล้ชิดกับหมวดหมู่ของประเภทด้วยความแตกต่างที่ไม่จำเป็นที่นี่) ในหมวดหมู่ชุดmorphismsระหว่างสองชุดเป็นฟังก์ชันทั้งหมดอย่างแม่นยำจากชุดแรกเป็นชุดที่สอง ฟังก์ชันความยาวของเราตรงf
นี้คือมอร์ฟิซึ่มส์จากเซตของสตริงไปยังชุดตัวเลข
ในฐานะที่เราพิจารณาเฉพาะหมวดหมู่ชุดที่เกี่ยวข้องFunctorsจากมันเข้าไปในตัวของมันเองมีแผนที่จะส่งวัตถุวัตถุและ morphisms เพื่อ morphisms ที่ตอบสนองกฎหมายเกี่ยวกับพีชคณิตบางอย่าง
ตัวอย่าง: Array
Array
อาจหมายถึงหลายสิ่งหลายอย่าง แต่เพียงสิ่งเดียวที่เป็น Functor - ประเภทสร้าง, การทำแผนที่ชนิดa
เข้าไปในประเภทของอาร์เรย์ทุกประเภท[a]
a
ตัวอย่างเช่นArray
functor จะแมปประเภทString
เป็นประเภท[String]
(ชุดของอาร์เรย์ทั้งหมดของสตริงที่มีความยาวโดยพลการ) และชุดประเภทNumber
เป็นประเภทที่สอดคล้องกัน[Number]
(ชุดของตัวเลขทั้งหมดในอาร์เรย์)
เป็นสิ่งสำคัญที่จะไม่ทำให้แผนที่ Functor สับสน
Array :: a => [a]
a -> [a]
กับซึ่มส์ นักแสดงจะทำการแมป (ประเภท) ประเภทa
เป็นประเภท[a]
หนึ่งกับอีกสิ่งหนึ่ง แต่ละประเภทเป็นชุดขององค์ประกอบจริง ๆ แล้วไม่มีความเกี่ยวข้องกันที่นี่ ในทางตรงกันข้ามมอร์ฟิซึ่มส์เป็นฟังก์ชั่นจริงระหว่างชุดเหล่านั้น ตัวอย่างเช่นมี morphism ตามธรรมชาติ (ฟังก์ชั่น)
pure :: a -> [a]
pure = x => [x]
ซึ่งจะส่งค่าไปยังอาร์เรย์ 1 องค์ประกอบพร้อมกับค่านั้นเป็นรายการเดียว ฟังก์ชั่นนั้นไม่ได้เป็นส่วนหนึ่งของArray
Functor! จากมุมมองของ functor นี้pure
เป็นเพียงฟังก์ชั่นเหมือนกันไม่มีอะไรพิเศษ
ในขณะที่Array
Functor มีส่วนที่สอง - ส่วนมอร์ฟิซึ่มส์ ซึ่งแผนที่ morphism f :: a -> b
เป็น morphism [f] :: [a] -> [b]
:
// a -> [a]
Array.map(f) = arr => arr.map(f)
นี่arr
เป็นอาร์เรย์ของความยาวโดยพลการที่มีค่าของชนิดใด ๆa
และarr.map(f)
เป็นอาร์เรย์ของความยาวเช่นเดียวกันกับค่าประเภทb
ซึ่งรายการนี้เป็นผลของการประยุกต์ใช้กับรายการของf
arr
เพื่อให้เป็น functor กฎทางคณิตศาสตร์ของการทำแผนที่ตัวตนกับตัวตนและองค์ประกอบของการแต่งจะต้องถือซึ่งง่ายต่อการตรวจสอบในArray
ตัวอย่างนี้