Szorstki przegląd
W programowaniu funkcjonalnym funktor jest zasadniczo konstrukcją podnoszenia zwykłych funkcji jednoargumentowych (tj. Tych z jednym argumentem) do funkcji między zmiennymi nowych typów. Znacznie łatwiej jest pisać i utrzymywać proste funkcje między prostymi obiektami i używać funktorów do ich podnoszenia, a następnie ręcznie pisać funkcje między skomplikowanymi obiektami kontenerowymi. Kolejną zaletą jest napisanie prostych funkcji tylko raz, a następnie ponowne użycie ich za pomocą różnych funktorów.
Przykłady funktorów obejmują tablice, funty „może” i „albo”, futures (patrz np. Https://github.com/Avaq/Fluture ) i wiele innych.
Ilustracja
Rozważ funkcję konstruującą pełne imię i nazwisko osoby na podstawie imienia i nazwiska. Możemy to zdefiniować fullName(firstName, lastName)jako funkcję dwóch argumentów, co jednak nie byłoby odpowiednie dla funktorów, które zajmują się funkcjami tylko jednego argumentu. Aby temu zaradzić, zbieramy wszystkie argumenty w jednym obiekcie name, który teraz staje się pojedynczym argumentem funkcji:
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
Co teraz, jeśli mamy wiele osób w jednym szeregu? Zamiast ręcznie przeglądać listę, możemy po prostu ponownie użyć naszej funkcji fullNameza pomocą mapmetody przewidzianej dla tablic z krótkim pojedynczym wierszem kodu:
fullNameList = nameList => nameList.map(fullName)
i używaj go jak
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
Będzie to działać, ilekroć każdy wpis w naszym nameListobiekcie jest obiektem zapewniającym zarówno właściwości, jak firstNamei lastNamewłaściwości. Ale co, jeśli niektóre obiekty nie (a nawet wcale nie są)? Aby uniknąć błędów i uczynić kod bezpieczniejszym, możemy owinąć nasze obiekty w Maybetyp (patrz np. 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()
gdzie Just(name)jest pojemnikiem zawierającym tylko prawidłowe nazwy i Nothing()jest specjalną wartością używaną do wszystkiego innego. Teraz zamiast przerywać (lub zapominać) sprawdzanie poprawności naszych argumentów, możemy po prostu ponownie użyć (podnieść) naszą oryginalną fullNamefunkcję z innym pojedynczym wierszem kodu, ponownie opartym na mapmetodzie, tym razem przewidzianym dla typu Może:
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
i używaj go jak
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()
Teoria kategorii
Funktora w teorii kategorii znajduje się mapa dwie kategorie poszanowaniem skład swoich morfizmów. W języku komputerowym główną kategorią zainteresowań jest ta, której obiektami są typy (określone zestawy wartości) i której morfizmy są funkcjami f:a->bjednego typu ana inny b.
Na przykład, weźmy asię za Stringtyp,b Liczba i fjest funkcją odwzorowującą ciąg znaków na jego długość:
// f :: String -> Number
f = str => str.length
Tutaj a = Stringreprezentuje zestaw wszystkich ciągów i b = Numberzestaw wszystkich liczb. W tym sensie, jak ai breprezentacji obiektów w Set Kategorii (co jest ściśle związane z kategorią typów, z tą różnicą, nieistotne tutaj). W kategorii zestawu morfizmy między dwoma zestawami są dokładnie wszystkimi funkcjami od pierwszego zestawu do drugiego. Zatem naszą funkcją długości fjest morfizm ze zbioru ciągów na zbiór liczb.
Ponieważ bierzemy pod uwagę tylko zestaw kategorii, odpowiedni funktorami z niej samych są mapy wysyłające obiekty do obiektów i morfizmy do morfizmów, które spełniają pewne prawa algebraiczne.
Przykład: Array
Arraymoże oznaczać wiele rzeczy, ale tylko jedna rzecz to Functor - konstrukcja typu, mapująca typ ana typ [a]wszystkich tablic typu a. Na przykład Arrayfunktor odwzorowuje typ String na typ [String](zbiór wszystkich tablic ciągów o dowolnej długości), a zestaw typ Numberna odpowiedni typ [Number](zbiór wszystkich tablic liczb).
Ważne jest, aby nie mylić mapy Functor
Array :: a => [a]
z morfizmem a -> [a]. Funktor po prostu odwzorowuje (kojarzy) typ ana typ [a]jako jedna rzecz na drugą. To, że każdy typ jest w rzeczywistości zestawem elementów, nie ma tutaj znaczenia. Natomiast morfizm jest faktyczną funkcją między tymi zbiorami. Na przykład występuje naturalny morfizm (funkcja)
pure :: a -> [a]
pure = x => [x]
który wysyła wartość do tablicy 1-elementowej z tą wartością jako pojedynczy wpis. Ta funkcja nie jest częściąArray Functora! Z punktu widzenia tego funktora purejest to jak każda inna funkcja, nic specjalnego.
Z drugiej strony ArrayFunctor ma swoją drugą część - część morfizmu. Który f :: a -> bzamienia morfizm w morfizm [f] :: [a] -> [b]:
// a -> [a]
Array.map(f) = arr => arr.map(f)
Oto arrdowolna tablica o dowolnej długości z wartościami typu ai arr.map(f)jest tablicą o tej samej długości z wartościami typu b, których wpisy są wynikiem zastosowania fdo wpisów z arr. Aby uczynić go funktorem, muszą obowiązywać matematyczne prawa odwzorowywania tożsamości na tożsamość i kompozycji na kompozycje, które łatwo sprawdzić w tym Arrayprzykładzie.