Grobe Übersicht
In der funktionalen Programmierung ist ein Funktor im Wesentlichen eine Konstruktion, bei der gewöhnliche unäre Funktionen (dh solche mit einem Argument) auf Funktionen zwischen Variablen neuen Typs angehoben werden. Es ist viel einfacher, einfache Funktionen zwischen einfachen Objekten zu schreiben und zu verwalten und sie mit Funktoren anzuheben, als Funktionen zwischen komplizierten Containerobjekten manuell zu schreiben. Ein weiterer Vorteil besteht darin, einfache Funktionen nur einmal zu schreiben und sie dann über verschiedene Funktoren wiederzuverwenden.
Beispiele für Funktoren sind Arrays, "Vielleicht" - und "Entweder" -Funktoren, Futures (siehe z. B. https://github.com/Avaq/Fluture ) und viele andere.
Illustration
Betrachten Sie die Funktion, die den vollständigen Namen der Person aus dem Vor- und Nachnamen erstellt. Wir könnten es fullName(firstName, lastName)als Funktion von zwei Argumenten definieren, was jedoch nicht für Funktoren geeignet wäre, die sich nur mit Funktionen eines Arguments befassen. Um Abhilfe zu schaffen, sammeln wir alle Argumente in einem einzigen Objekt name, das nun zum einzigen Argument der Funktion wird:
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
Was ist nun, wenn wir viele Leute in einem Array haben? Anstatt die Liste manuell durchzugehen, können wir unsere Funktion einfach fullNameüber die mapfür Arrays mit kurzer Codezeile bereitgestellte Methode wiederverwenden :
fullNameList = nameList => nameList.map(fullName)
und benutze es gerne
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
Das funktioniert immer dann, wenn jeder Eintrag in unserem nameListein Objekt ist, das sowohl Eigenschaften firstNameals auch lastNameEigenschaften bietet . Aber was ist, wenn einige Objekte dies nicht tun (oder gar keine Objekte sind)? Um die Fehler zu vermeiden und den Code sicherer zu machen, können wir unsere Objekte in den MaybeTyp einschließen (siehe z. B. 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()
Dabei Just(name)ist ein Container nur gültige Namen und Nothing()der spezielle Wert, der für alles andere verwendet wird. Anstatt jetzt zu unterbrechen (oder zu vergessen), um die Gültigkeit unserer Argumente zu überprüfen, können wir unsere ursprüngliche fullNameFunktion einfach mit einer anderen einzelnen Codezeile wiederverwenden (aufheben) , basierend auf der mapMethode, die diesmal für den Typ Vielleicht vorgesehen ist:
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
und benutze es gerne
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()
Kategorietheorie
Ein Funktor in der Kategorietheorie ist eine Karte zwischen zwei Kategorien, die die Zusammensetzung ihrer Morphismen berücksichtigen. In einer Computersprache , ist die wichtigste Kategorie von Interesse der, dessen Objekte sind Typen (bestimmte Sätze von Werten), und deren Morphismen sind Funktionen f:a->bvon einem Typ ain einen anderen Typ b.
Nehmen awir zum Beispiel den StringTyp, bden Zahlentyp und fdie Funktion, die eine Zeichenfolge in ihre Länge abbildet:
// f :: String -> Number
f = str => str.length
Hier wird a = Stringdie Menge aller Zeichenfolgen und b = Numberdie Menge aller Zahlen dargestellt. In diesem Sinne repräsentieren beide aund brepräsentieren Objekte in der Set-Kategorie (die eng mit der Kategorie der Typen verwandt ist, wobei der Unterschied hier unwesentlich ist). In der Set-Kategorie sind Morphismen zwischen zwei Sets genau alle Funktionen vom ersten bis zum zweiten Set. Unsere Längenfunktion fhier ist also ein Morphismus von der Menge der Zeichenketten in die Menge der Zahlen.
Da wir nur die Mengenkategorie betrachten, sind die relevanten Funktoren daraus Karten, die Objekte an Objekte und Morphismen an Morphismen senden, die bestimmte algebraische Gesetze erfüllen.
Beispiel: Array
Arraykann viele Dinge bedeuten, aber nur eines ist ein Functor - das Typkonstrukt, das einen Typ adem Typ [a]aller Arrays des Typs zuordnet a. Beispielsweise Arrayordnet der Funktor den Typ String dem Typ [String](der Menge aller Arrays von Zeichenfolgen beliebiger Länge) und den Typ Numberdem entsprechenden Typ [Number](der Menge aller Arrays von Zahlen) zu.
Es ist wichtig, die Functor-Karte nicht zu verwechseln
Array :: a => [a]
mit einem Morphismus a -> [a]. Der Funktor ordnet den Typ einfach als eine Sache der anderen adem Typ [a]zu. Dass jeder Typ tatsächlich eine Reihe von Elementen ist, spielt hier keine Rolle. Im Gegensatz dazu ist ein Morphismus eine tatsächliche Funktion zwischen diesen Mengen. Zum Beispiel gibt es einen natürlichen Morphismus (Funktion)
pure :: a -> [a]
pure = x => [x]
Dies sendet einen Wert in das 1-Element-Array mit diesem Wert als Einzeleintrag. Diese Funktion ist nicht Teil des ArrayFunctor! Aus der Sicht dieses Funktors pureist nur eine Funktion wie jede andere, nichts Besonderes.
Auf der anderen Seite hat der ArrayFunctor seinen zweiten Teil - den Morphismus-Teil. Was einen Morphismus f :: a -> bin einen Morphismus abbildet [f] :: [a] -> [b]:
// a -> [a]
Array.map(f) = arr => arr.map(f)
Hier arrist jedes Array beliebiger Länge mit Werten vom Typ aund arr.map(f)ist das Array derselben Länge mit Werten vom Typ b, dessen Einträge das Ergebnis der Anwendung fauf die Einträge von sind arr. Um es zu einem Funktor zu machen, müssen die mathematischen Gesetze der Zuordnung von Identität zu Identität und von Kompositionen zu Kompositionen gelten, die in diesem ArrayBeispiel leicht zu überprüfen sind .