ภาพรวมคร่าวๆ
ในฟังก์ชั่นการเขียนโปรแกรมฟังก์ชั่น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ชนิดbNumber และ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ตัวอย่างเช่นArrayfunctor จะแมปประเภทString เป็นประเภท[String](ชุดของอาร์เรย์ทั้งหมดของสตริงที่มีความยาวโดยพลการ) และชุดประเภทNumberเป็นประเภทที่สอดคล้องกัน[Number](ชุดของตัวเลขทั้งหมดในอาร์เรย์)
เป็นสิ่งสำคัญที่จะไม่ทำให้แผนที่ Functor สับสน
Array :: a => [a]
a -> [a]กับซึ่มส์ นักแสดงจะทำการแมป (ประเภท) ประเภทaเป็นประเภท[a]หนึ่งกับอีกสิ่งหนึ่ง แต่ละประเภทเป็นชุดขององค์ประกอบจริง ๆ แล้วไม่มีความเกี่ยวข้องกันที่นี่ ในทางตรงกันข้ามมอร์ฟิซึ่มส์เป็นฟังก์ชั่นจริงระหว่างชุดเหล่านั้น ตัวอย่างเช่นมี morphism ตามธรรมชาติ (ฟังก์ชั่น)
pure :: a -> [a]
pure = x => [x]
ซึ่งจะส่งค่าไปยังอาร์เรย์ 1 องค์ประกอบพร้อมกับค่านั้นเป็นรายการเดียว ฟังก์ชั่นนั้นไม่ได้เป็นส่วนหนึ่งของArrayFunctor! จากมุมมองของ functor นี้pureเป็นเพียงฟังก์ชั่นเหมือนกันไม่มีอะไรพิเศษ
ในขณะที่ArrayFunctor มีส่วนที่สอง - ส่วนมอร์ฟิซึ่มส์ ซึ่งแผนที่ 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ตัวอย่างนี้