Was ist in der funktionalen Programmierung ein Funktor?

223

Ich bin beim Lesen verschiedener Artikel über funktionale Programmierung einige Male auf den Begriff "Functor" gestoßen, aber die Autoren gehen normalerweise davon aus, dass der Leser den Begriff bereits versteht. Wenn Sie sich im Internet umschauen, erhalten Sie entweder übermäßig technische Beschreibungen (siehe Wikipedia-Artikel ) oder unglaublich vage Beschreibungen (siehe Abschnitt über Functors auf dieser Website von ocaml-tutorial ).

Kann jemand den Begriff freundlich definieren, seine Verwendung erklären und vielleicht ein Beispiel dafür geben, wie Funktoren erstellt und verwendet werden?

Bearbeiten : Während ich mich für die Theorie hinter dem Begriff interessiere, interessiere ich mich weniger für die Theorie als für die Umsetzung und praktische Anwendung des Konzepts.

Edit 2 : Es sieht so aus, als ob es eine terminübergreifende Angelegenheit gibt: Ich beziehe mich speziell auf die Funktoren der funktionalen Programmierung, nicht auf die Funktionsobjekte von C ++.

Erik Forbes
quelle
4
Siehe auch: adit.io/posts/…
Vlad der Impala
Ziemlich gute Antwort auch: stackoverflow.com/a/45149475/1498178
toraritte
Wenn Sie mehr an der praktischen Implementierung und Verwendung als an der stratosphärischen Terminologie und Theorie hinter dem Konzept interessiert sind, benötigen Sie nur einen Einzeiler: Ein Funktor legt eine "Karten" -Funktion frei.
Richard Gomes
@RichardGomes IMHO Ich denke, es reduziert die Rolle eines Funktors auf eine einfache Java-ähnliche Oberfläche, die es nicht ist. Ein Funktor transformiert Dinge, er baut neue Typen aus vorhandenen (in Haskell) auf, was bedeutet, dass die Typen auch zugeordnet werden. fmapordnet die Funktionen zu. Es gibt zwei Arten von Zuordnungen. Diese Sichtweise hilft, die Kategorietheorie (die allgemeiner ist) zu verstehen. Ich meine, es ist interessant, die grundlegende Kategorietheorie zu verstehen, um uns bei all den kategorietheoretischen Dingen in Haskell zu helfen (Funktor, Monaden, ...).
Ludovic Kuty
@VladtheImpala Der Blog-Beitrag ist fantastisch, aber auch wenn er sehr hilfreich ist, möchte ich bedenken, dass ein Funktor einen anderen Typ erstellt (abbildet). Ich mag besonders den Satz "Ein Funktor F nimmt jeden Typ T und ordnet ihn einem neuen Typ FT zu" in Monaden sind wie Burritos . IMHO ist es nicht nur ein Kontext (eine Box) um einen Wert, auch wenn es sich als praktisch erweist, solche Dinge zu sehen (Haskell PoV gegen Kategorietheorie PoV?)
Ludovic Kuty

Antworten:

273

Das Wort "Funktor" stammt aus der Kategorietheorie, die ein sehr allgemeiner, sehr abstrakter Zweig der Mathematik ist. Es wurde von Designern funktionaler Sprachen auf mindestens zwei verschiedene Arten ausgeliehen.

  • In der ML-Sprachfamilie ist ein Funktor ein Modul, das ein oder mehrere andere Module als Parameter verwendet. Es wird als erweiterte Funktion angesehen, und die meisten Anfänger haben Schwierigkeiten damit.

    Als Beispiel für die Implementierung und praktische Anwendung könnten Sie Ihre bevorzugte Form eines ausgeglichenen binären Suchbaums ein für alle Mal als Funktor definieren und als Parameter ein Modul verwenden, das Folgendes bereitstellt:

    • Der Schlüsseltyp, der im Binärbaum verwendet werden soll

    • Eine Gesamtbestellfunktion für Tasten

    Sobald Sie dies getan haben, können Sie für immer dieselbe ausgeglichene Binärbaumimplementierung verwenden. (Die Art des im Baum gespeicherten Werts bleibt normalerweise polymorph - der Baum muss keine anderen Werte betrachten, als sie zu kopieren, während der Baum definitiv in der Lage sein muss, Schlüssel zu vergleichen, und die Vergleichsfunktion von erhält der Funktorparameter.)

    Eine weitere Anwendung von ML-Funktoren sind geschichtete Netzwerkprotokolle . Der Link führt zu einem wirklich großartigen Artikel der CMU Fox-Gruppe. Es wird gezeigt, wie mithilfe von Funktoren komplexere Protokollschichten (wie TCP) auf einfacheren Schichten (wie IP oder sogar direkt über Ethernet) erstellt werden. Jede Ebene ist als Funktor implementiert, der die darunter liegende Ebene als Parameter verwendet. Die Struktur der Software spiegelt tatsächlich die Art und Weise wider, wie Menschen über das Problem denken, im Gegensatz zu den Schichten, die nur im Kopf des Programmierers existieren. Als diese Arbeit 1994 veröffentlicht wurde, war es eine große Sache.

    Als wildes Beispiel für ML-Funktoren in Aktion sehen Sie das Papier ML Module Mania , das ein publizierbares (dh beängstigendes) Beispiel für Funktoren bei der Arbeit enthält. Lesen Sie die ersten Seiten von Xavier Leroys brillantem POPL-Papier Manifest Types, Modules und Separate Compilation aus dem Jahr 1994, um eine brillante, klare und übersichtliche Erklärung des ML-Modulsystems (mit Vergleichen mit anderen Modultypen) zu erhalten .

  • In Haskell und in einer verwandten rein funktionalen Sprache Functorhandelt es sich um eine Typklasse . Ein Typ gehört zu einer Typklasse (oder technisch gesehen ist der Typ "eine Instanz der Typklasse"), wenn der Typ bestimmte Operationen mit einem bestimmten erwarteten Verhalten bereitstellt. Ein Typ Tkann zur Klasse gehören, Functorwenn er ein bestimmtes sammlungsähnliches Verhalten aufweist:

    • Der Typ Twird über einen anderen Typ parametrisiert, den Sie sich als Elementtyp der Sammlung vorstellen sollten. Die Art der vollständigen Sammlung ist dann so etwas wie T Int, T String, T Bool, wenn Sie ganze Zahlen, Zeichenketten enthalten, oder Boolesche Werte sind. Wenn der Elementtyp unbekannt ist, wird es als Schrifttyp - Parameter a , wie in T a.

      Beispiele sind Listen (null oder mehr Elemente des Typs a), der MaybeTyp (null oder ein Element des Typs a), Sätze von Elementen des Typs a, Arrays von Elementen des Typs a, alle Arten von Suchbäumen, die Werte des Typs enthalten a, und viele andere von Ihnen kann mir vorstellen.

    • Die andere Eigenschaft, Tdie erfüllt sein muss, ist, dass Sie, wenn Sie eine Funktion vom Typ a -> b(eine Funktion für Elemente) haben, in der Lage sein müssen, diese Funktion zu übernehmen und eine verwandte Funktion für Sammlungen zu produzieren. Sie tun dies mit dem Operator fmap, der von jedem Typ in der FunctorTypklasse gemeinsam genutzt wird. Der Operator ist tatsächlich überlastet. Wenn Sie also eine Funktion evenmit Typ haben Int -> Bool, dann

      fmap even

      ist eine überladene Funktion, die viele wunderbare Dinge tun kann:

      • Konvertieren Sie eine Liste von Ganzzahlen in eine Liste von Booleschen Werten

      • Konvertieren Sie einen Baum von ganzen Zahlen in einen Baum von Booleschen Werten

      • Konvertieren Nothingin Nothingund Just 7nachJust False

      In Haskell wird diese Eigenschaft durch Angabe des folgenden Typs ausgedrückt fmap:

      fmap :: (Functor t) => (a -> b) -> t a -> t b

      wo wir jetzt eine kleine haben t, was "jeder Typ in der FunctorKlasse" bedeutet.

    Um es kurz zu machen: In Haskell ist ein Funktor eine Art Sammlung, für die Sie, wenn Sie eine Funktion für Elemente erhalten, fmapeine Funktion für Sammlungen zurückgeben . Wie Sie sich vorstellen können, ist dies eine Idee, die weitgehend wiederverwendet werden kann, weshalb sie als Teil der Standardbibliothek von Haskell gesegnet ist.

Wie üblich erfinden die Leute weiterhin neue, nützliche Abstraktionen, und Sie möchten sich vielleicht mit anwendungsbezogenen Funktoren befassen, für die die beste Referenz ein Artikel mit dem Titel Applikative Programmierung mit Effekten von Conor McBride und Ross Paterson sein kann.

Norman Ramsey
quelle
7
Ich verstehe sowohl ML-Funktoren als auch Haskell-Funktoren, aber es fehlt mir die Einsicht, sie miteinander in Beziehung zu setzen. Wie ist die Beziehung zwischen diesen beiden im kategorietheoretischen Sinne?
Wei Hu
6
@Wei Hu: Kategorietheorie hat für mich nie einen Sinn ergeben. Das Beste, was ich sagen kann, ist, dass alle drei Begriffe Mapping beinhalten.
Norman Ramsey
16
Laut diesem Haskell-Wiki: en.wikibooks.org/wiki/Haskell/Category_theory ist dies wie folgt : Eine Kategorie ist eine Sammlung von Objekten und Morphismen (Funktionen), wobei die Morphismen von Objekten in einer Kategorie zu anderen Objekten in dieser Kategorie reichen . Ein Funktor ist eine Funktion, die Objekte und Morphismen von einer Kategorie auf Objekte und Morphismen in einer anderen abbildet. Zumindest verstehe ich das so. Was das genau für die Programmierung bedeutet, muss ich noch verstehen.
Paul
5
@ norman-ramsey, hast du dir Conceptual Mathematics von Lawvere und Schanuel angesehen? Ich bin ein absoluter Neuling in der Region, aber das Buch ist hervorragend lesbar und - ich wage es zu sagen - angenehm. (
Liebte
2
then you have to be able to take that function and product a related function on collectionsMeinten Sie producestatt product?
Problemoffizier
64

Andere Antworten hier sind vollständig, aber ich werde eine andere Erklärung für die FP-Verwendung des Funktors versuchen . Nehmen Sie dies als Analogie:

Ein Funktor ist ein Container vom Typ a , der, wenn er einer Funktion unterzogen wird, die von ab abgebildet wird, einen Container vom Typ b ergibt .

Im Gegensatz zur Verwendung des abstrakten Funktionszeigers in C ++ ist hier der Funktor nicht die Funktion. Vielmehr verhält es sich konsequent, wenn es einer Funktion ausgesetzt wird .

seh
quelle
3
Ein Container vom Typ b bedeutet "die gleiche Art von Container wie der Eingabecontainer, aber jetzt mit b gefüllt". Also , wenn wir eine Liste von Bananen haben, und wir Karte eine Funktion , die eine Banane nimmt und gibt einen Obstsalat, haben wir jetzt eine Liste von Obstsalaten. Ebenso, wenn wir einen hatten Baum von Bananen, und wir Karte die gleiche Funktion, würden wir jetzt einen haben Baum von Äpfeln. Usw. Baum und Liste sind hier zwei Funktoren.
Qqwy
3
"Ein Funktor ist ein Container vom Typ a, der, wenn er einer Funktion unterworfen ist" - es ist eigentlich umgekehrt - die Funktion (Morphismus) einem Funktor unterliegt, um in einen anderen Morphismus abgebildet zu werden
Dmitri Zaitsev
38

Es gibt drei verschiedene Bedeutungen, die nicht viel miteinander zu tun haben!

  • In Ocaml ist es ein parametrisiertes Modul. Siehe Handbuch . Ich denke, der beste Weg, sie zu groken, ist ein Beispiel: (schnell geschrieben, könnte fehlerhaft sein)

    module type Order = sig
        type t
        val compare: t -> t -> bool
    end;;
    
    
    module Integers = struct
        type t = int
        let compare x y = x > y
    end;;
    
    module ReverseOrder = functor (X: Order) -> struct
        type t = X.t
        let compare x y = X.compare y x
    end;;
    
    (* We can order reversely *)
    module K = ReverseOrder (Integers);;
    Integers.compare 3 4;;   (* this is false *)
    K.compare 3 4;;          (* this is true *)
    
    module LexicographicOrder = functor (X: Order) -> 
      functor (Y: Order) -> struct
        type t = X.t * Y.t
        let compare (a,b) (c,d) = if X.compare a c then true
                             else if X.compare c a then false
                             else Y.compare b d
    end;;
    
    (* compare lexicographically *)
    module X = LexicographicOrder (Integers) (Integers);;
    X.compare (2,3) (4,5);;
    
    module LinearSearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    module BinarySearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    (* linear search over arrays of integers *)
    module LS = LinearSearch (Integers);;
    LS.find [|1;2;3] 2;;
    (* binary search over arrays of pairs of integers, 
       sorted lexicographically *)
    module BS = BinarySearch (LexicographicOrder (Integers) (Integers));;
    BS.find [|(2,3);(4,5)|] (2,3);;
    

Sie können jetzt schnell viele mögliche Aufträge hinzufügen, neue Aufträge bilden und einfach eine binäre oder lineare Suche durchführen. Generische Programmierung FTW.

  • In funktionalen Programmiersprachen wie Haskell bedeutet dies, dass einige Typkonstruktoren (parametrisierte Typen wie Listen, Mengen) "zugeordnet" werden können. Um genau zu sein, ist ein Funktor fmit ausgestattet (a -> b) -> (f a -> f b). Dies hat seinen Ursprung in der Kategorietheorie. Der Wikipedia-Artikel, auf den Sie verlinkt haben, ist diese Verwendung.

    class Functor f where
        fmap :: (a -> b) -> (f a -> f b)
    
    instance Functor [] where      -- lists are a functor
        fmap = map
    
    instance Functor Maybe where   -- Maybe is option in Haskell
        fmap f (Just x) = Just (f x)
        fmap f Nothing = Nothing
    
    fmap (+1) [2,3,4]   -- this is [3,4,5]
    fmap (+1) (Just 5)  -- this is Just 6
    fmap (+1) Nothing   -- this is Nothing
    

Dies ist also eine besondere Art von Konstrukteuren und hat wenig mit Funktoren in Ocaml zu tun!

  • In imperativen Sprachen ist es ein Zeiger auf die Funktion.
sdcvvc
quelle
Sollte <q> map </ q> in den letzten 3 Zeilen dieses Kommentars nicht tatsächlich <q> fmap </ q> sein?
imz - Ivan Zakharyaschev
1
Ich habe immer gelesen, dass Funktoren Container sind - aber das ist nur eine schlechte Vereinfachung. Ihre Antwort lieferte schließlich das fehlende Glied: Funktoren sind eine Typklasse (Typbeschränkung) für parametrisierte Typen (Typkonstruktoren). So einfach ist das!
16

In OCaml ist es ein parametrisiertes Modul.

Wenn Sie C ++ kennen, stellen Sie sich einen OCaml-Funktor als Vorlage vor. C ++ verfügt nur über Klassenvorlagen, und Funktoren arbeiten im Modulmaßstab.

Ein Beispiel für einen Funktor ist Map.Make; module StringMap = Map.Make (String);;Erstellt ein Kartenmodul, das mit Karten mit Zeichenfolgen funktioniert.

Mit Polymorphismus konnte man so etwas wie StringMap nicht erreichen. Sie müssen einige Annahmen über die Schlüssel treffen. Das String-Modul enthält die Operationen (Vergleich usw.) für einen vollständig geordneten String-Typ, und der Funktor verknüpft die Operationen, die das String-Modul enthält. Mit objektorientierter Programmierung könnten Sie etwas Ähnliches tun, aber Sie hätten einen Overhead für die Methodenindirektion.

Tobu
quelle
Ich habe das von der ocaml-Website erhalten - aber ich verstehe nicht, wie ein parametrisiertes Modul verwendet werden würde.
Erik Forbes
4
@Kornel Ja, was ich beschrieben habe, ist ein OCaml-Konzept. Das andere Konzept ist nur „funktionaler Wert“, was in FP nichts Besonderes ist. @Erik Ich habe etwas erweitert, aber die Referenzdokumente werden nur langsam geladen.
Tobu
13

Sie haben einige gute Antworten erhalten. Ich werde einsteigen:

Ein Funktor im mathematischen Sinne ist eine besondere Art von Funktion in einer Algebra. Es ist eine Minimalfunktion, die eine Algebra einer anderen Algebra zuordnet. "Minimalität" wird durch die Funktorgesetze ausgedrückt.

Es gibt zwei Möglichkeiten, dies zu betrachten. Zum Beispiel sind Listen Funktoren über einen bestimmten Typ. Das heißt, wenn eine Algebra über einem Typ 'a' gegeben ist, können Sie eine kompatible Algebra von Listen generieren, die Dinge vom Typ 'a' enthalten. (Zum Beispiel: Die Karte, die ein Element zu einer Singleton-Liste führt, die es enthält: f (a) = [a]) Auch hier wird der Begriff der Kompatibilität durch die Funktorgesetze ausgedrückt.

Wenn andererseits ein Funktor f "über" einen Typ a "(dh fa ist das Ergebnis der Anwendung des Funktors f auf die Algebra vom Typ a) und eine Funktion aus g: a -> b gegeben ist, können wir berechnen ein neuer Funktor F = (fmap g), der fa auf f b abbildet. Kurz gesagt, fmap ist der Teil von F, der "Funktorteile" auf "Funktorteile" abbildet, und g ist der Teil der Funktion, der "Algebrateile" auf "Algebrateile" abbildet. Es braucht eine Funktion, einen Funktor, und wenn es fertig ist, ist es auch ein Funktor.

Es scheint, dass verschiedene Sprachen unterschiedliche Vorstellungen von Funktoren verwenden, aber sie sind es nicht. Sie verwenden lediglich Funktoren über verschiedene Algebren. OCamls hat eine Algebra von Modulen, und mit Funktoren über diese Algebra können Sie neue Deklarationen auf "kompatible" Weise an ein Modul anhängen.

Ein Haskell-Funktor ist KEINE Typklasse. Es ist ein Datentyp mit einer freien Variablen, die die Typklasse erfüllt. Wenn Sie bereit sind, sich mit den Eingeweiden eines Datentyps (ohne freie Variablen) zu befassen, können Sie einen Datentyp als Funktor über eine zugrunde liegende Algebra neu interpretieren. Beispielsweise:

Daten F = F Int

ist isomorph zur Klasse der Ints. F als Wertekonstruktor ist also eine Funktion, die Int auf F Int, eine äquivalente Algebra, abbildet. Es ist ein Funktor. Auf der anderen Seite erhalten Sie hier keine kostenlose fmap. Dafür ist Pattern Matching gedacht.

Funktoren sind gut geeignet, um Dinge auf algebraisch kompatible Weise an Elemente von Algebren anzuhängen.

user276631
quelle
8

Die beste Antwort auf diese Frage findet sich in "Typeclassopedia" von Brent Yorgey.

Diese Ausgabe von Monad Reader enthält eine genaue Definition dessen, was ein Funktor ist, sowie viele Definitionen anderer Konzepte sowie ein Diagramm. (Monoid, Applicative, Monad und andere Konzepte werden in Bezug auf einen Funktor erklärt und gesehen).

http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf

Auszug aus Typeclassopedia for Functor: "Eine einfache Intuition ist, dass ein Functor einen" Container "darstellt, zusammen mit der Fähigkeit, eine Funktion einheitlich auf jedes Element im Container anzuwenden."

Aber wirklich, die ganze Typeclassopedia ist eine sehr empfehlenswerte Lektüre, die überraschend einfach ist. In gewisser Weise können Sie die dort dargestellte Typklasse als Parallele zum Entwurfsmuster im Objekt in dem Sinne sehen, dass sie Ihnen ein Vokabular für ein bestimmtes Verhalten oder eine bestimmte Fähigkeit gibt.

Prost

JFT
quelle
7

Es gibt ein ziemlich gutes Beispiel in dem O'Reilly OCaml-Buch auf Inrias Website (das zum Zeitpunkt des Schreibens leider nicht verfügbar ist). In diesem von caltech verwendeten Buch habe ich ein sehr ähnliches Beispiel gefunden: Einführung in OCaml (pdf-Link) . Der relevante Abschnitt ist das Kapitel über Funktoren (Seite 139 im Buch, Seite 149 im PDF).

In dem Buch haben sie einen Funktor namens MakeSet, der eine Datenstruktur erstellt, die aus einer Liste besteht, und Funktionen zum Hinzufügen eines Elements, zum Bestimmen, ob sich ein Element in der Liste befindet, und zum Suchen des Elements. Die Vergleichsfunktion, mit der bestimmt wird, ob sie im Set enthalten ist oder nicht, wurde parametrisiert (was MakeSet zu einem Funktor anstelle eines Moduls macht).

Sie haben auch ein Modul, das die Vergleichsfunktion implementiert, so dass ein Zeichenfolgenvergleich ohne Berücksichtigung der Groß- und Kleinschreibung durchgeführt wird.

Mit dem Funktor und dem Modul, das den Vergleich implementiert, können sie ein neues Modul in einer Zeile erstellen:

module SSet = MakeSet(StringCaseEqual);;

Dadurch wird ein Modul für eine festgelegte Datenstruktur erstellt, das Vergleiche ohne Berücksichtigung der Groß- und Kleinschreibung verwendet. Wenn Sie einen Satz erstellen möchten, bei dem Vergleiche zwischen Groß- und Kleinschreibung verwendet werden, müssen Sie lediglich ein neues Vergleichsmodul anstelle eines neuen Datenstrukturmoduls implementieren.

Tobu verglich Funktoren mit Vorlagen in C ++, was ich für ziemlich passend halte.

Niki Yoshiuchi
quelle
6

Angesichts der anderen Antworten und dem, was ich jetzt veröffentlichen werde, würde ich sagen, dass es ein ziemlich stark überladenes Wort ist, aber trotzdem ...

Für einen Hinweis zur Bedeutung des Wortes "Funktor" in Haskell fragen Sie GHCi:

Prelude> :info Functor
class Functor f where
  fmap :: forall a b. (a -> b) -> f a -> f b
  (GHC.Base.<$) :: forall a b. a -> f b -> f a
        -- Defined in GHC.Base
instance Functor Maybe -- Defined in Data.Maybe
instance Functor [] -- Defined in GHC.Base
instance Functor IO -- Defined in GHC.Base

Ein Funktor in Haskell kann also im Grunde genommen abgebildet werden. Eine andere Möglichkeit zu sagen ist, dass ein Funktor als Container betrachtet werden kann, der aufgefordert werden kann, eine bestimmte Funktion zu verwenden, um den darin enthaltenen Wert zu transformieren. Somit für Listen, fmapfällt zusammen mit map, für Maybe, fmap f (Just x) = Just (f x), fmap f Nothing = Nothingusw.

Der Unterabschnitt Functor Typeclass und der Abschnitt über Functors, Applicative Functors und Monoids of Learn You a Haskell for Great Good geben einige Beispiele dafür, wo dieses spezielle Konzept nützlich ist. (Eine Zusammenfassung: viele Orte! :-))

Beachten Sie, dass jede Monade als Funktor behandelt werden kann, und tatsächlich, wie Craig Stuntz betont, sind die am häufigsten verwendeten Funktoren in der Regel Monaden ... OTOH, es ist manchmal praktisch, einen Typ zu einer Instanz der Functor-Typklasse zu machen ohne sich die Mühe zu machen, daraus eine Monade zu machen. (ZB im Fall von ZipListvon Control.Applicative, auf einer der oben genannten Seiten erwähnt .)

Michał Marczyk
quelle
5

Hier ist ein Artikel über Funktoren aus einem Programmier-POV , gefolgt von einer genaueren Darstellung, wie sie in Programmiersprachen auftauchen .

Die praktische Verwendung eines Funktors findet in einer Monade statt, und Sie können viele Tutorials zu Monaden finden, wenn Sie danach suchen.

Craig Stuntz
quelle
1
"Der praktische Gebrauch eines Funktors ist in einer Monade" - nicht nur. Alle Monaden sind Funktoren, aber es gibt viele Verwendungsmöglichkeiten für Nicht-Monaden-Funktoren.
Amindfv
1
Ich würde sagen, dass das Lernen von Monaden, um Funktoren zu benutzen, wie das Sparen für einen Rolls ist, um Lebensmittel zu kaufen.
Marco Faustinelli
5

In einem Kommentar zur am besten bewerteten Antwort fragt Benutzer Wei Hu :

Ich verstehe sowohl ML-Funktoren als auch Haskell-Funktoren, aber es fehlt mir die Einsicht, sie miteinander in Beziehung zu setzen. Wie ist die Beziehung zwischen diesen beiden im kategorietheoretischen Sinne?

Hinweis : Ich kenne ML nicht. Bitte verzeihen und korrigieren Sie alle damit verbundenen Fehler.

Nehmen wir zunächst an, dass wir alle mit den Definitionen von 'Kategorie' und 'Funktor' vertraut sind.

Eine kompakte Antwort wäre, dass "Haskell-Funktoren" (Endo-) Funktoren sind, F : Hask -> Haskwährend "ML-Funktoren" Funktoren sind G : ML -> ML'.

Hier Haskist die Kategorie von Haskell Typen und Funktionen zwischen ihnen gebildet, und in ähnlicher Weise MLund ML'sind Kategorien definiert durch ML - Strukturen.

Hinweis : Es gibt einige technische Probleme beim Erstellen Haskeiner Kategorie, aber es gibt Möglichkeiten, diese zu umgehen.

Aus kategorietheoretischer Sicht bedeutet dies, dass ein Hask-Funktions eine Karte Fvon Haskell-Typen ist:

data F a = ...

zusammen mit einer Karte fmapder Haskell-Funktionen:

instance Functor F where
    fmap f = ...

ML ist ziemlich gleich, obwohl fmapmir keine kanonische Abstraktion bekannt ist. Definieren wir also eine:

signature FUNCTOR = sig
  type 'a f
  val fmap: 'a -> 'b -> 'a f -> 'b f
end

Das ist fKarten ML-Typen und fmapKarten ML-Funktionen, so

functor StructB (StructA : SigA) :> FUNCTOR =
struct
  fmap g = ...
  ...
end

ist ein Funktor F: StructA -> StructB.

Ncat
quelle
5

"Functor ist die Abbildung von Objekten und Morphismen, die die Zusammensetzung und Identität einer Kategorie bewahrt."

Definieren wir, was eine Kategorie ist.

Es ist ein Haufen Objekte!

Zeichnen Sie ein paar Punkte (für den Moment 2 Punkte, einer ist 'a', ein anderer ist 'b') innerhalb eines Kreises und benennen Sie diesen Kreis A (Kategorie) für jetzt.

Was hält die Kategorie?

Zusammensetzung zwischen Objekten und Identitätsfunktion für jedes Objekt.

Wir müssen also die Objekte zuordnen und die Komposition beibehalten, nachdem wir unseren Functor angewendet haben.

Stellen wir uns vor, 'A' ist unsere Kategorie mit Objekten ['a', 'b'] und es gibt einen Morphismus a -> b

Jetzt müssen wir einen Funktor definieren, der diese Objekte und Morphismen in eine andere Kategorie 'B' abbilden kann.

Nehmen wir an, der Funktor heißt "Vielleicht".

data Maybe a = Nothing | Just a

Die Kategorie 'B' sieht also so aus.

Bitte zeichnen Sie einen weiteren Kreis, diesmal jedoch mit 'Vielleicht a' und 'Vielleicht b' anstelle von 'a' und 'b'.

Alles scheint gut zu sein und alle Objekte werden zugeordnet

'a' wurde 'Vielleicht a' und 'b' wurde 'Vielleicht b'.

Das Problem ist jedoch, dass wir den Morphismus auch von 'a' nach 'b' abbilden müssen.

Das bedeutet, dass Morphismus a -> b in 'A' dem Morphismus 'Vielleicht a' -> 'Vielleicht b' zugeordnet werden sollte.

Morphismus von a -> b heißt f, dann heißt Morphismus von 'Vielleicht a' -> 'Vielleicht b' heißt 'fmap f'

Nun wollen wir sehen, welche Funktion 'f' in 'A' ausgeführt hat und ob wir sie in 'B' replizieren können.

Funktionsdefinition von 'f' in 'A':

f :: a -> b

f nimmt a und gibt b zurück

Funktionsdefinition von 'f' in 'B':

f :: Maybe a -> Maybe b

f nimmt Vielleicht a und kehrt zurück Vielleicht b

Mal sehen, wie man mit fmap die Funktion 'f' von 'A' auf die Funktion 'fmap f' in 'B' abbildet.

Definition von fmap

fmap :: (a -> b) -> (Maybe a -> Maybe b)
fmap f Nothing = Nothing
fmap f (Just x) = Just(f x)

Also, was machen wir hier?

Wir wenden die Funktion 'f' auf 'x' vom Typ 'a' an. Spezielle Musterübereinstimmung von 'Nichts' ergibt sich aus der Definition von Functor Maybe.

Also haben wir unsere Objekte [a, b] und Morphismen [f] von Kategorie 'A' auf Kategorie 'B' abgebildet.

Das ist Functor!

Geben Sie hier die Bildbeschreibung ein

Sumanth Kumar Mora
quelle
Interessante Antwort. Ich möchte es mit Monaden ergänzen, die wie Burritos sind (lustige Antwort auf Abstraktion, Intuition und den „Iradentutorium-Irrtum“ ), und sein Satz "Ein Funktor F nimmt jeden Typ T und ordnet ihn einem neuen Typ FT zu", auch bekannt als Typkonstruktor . Funktionale Programmierung und Kategorietheorie - Kategorien und Funktoren waren ebenfalls nützlich.
Ludovic Kuty
3

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 .

Dmitri Zaitsev
quelle
2

Um den vorherigen theoretischen oder mathematischen Antworten nicht zu widersprechen, ist ein Functor auch ein Objekt (in einer objektorientierten Programmiersprache), das nur eine Methode hat und effektiv als Funktion verwendet wird.

Ein Beispiel ist die Runnable-Schnittstelle in Java, die nur eine Methode hat: run.

Betrachten Sie dieses Beispiel zuerst in Javascript, das erstklassige Funktionen hat:

[1, 2, 5, 10].map(function(x) { return x*x; });

Ausgabe: [1, 4, 25, 100]

Die Map-Methode übernimmt eine Funktion und gibt ein neues Array zurück, wobei jedes Element das Ergebnis der Anwendung dieser Funktion auf den Wert an derselben Position im ursprünglichen Array ist.

Um dasselbe zu tun, müssen Sie in Java mit einem Functor zunächst eine Schnittstelle definieren, z. B.:

public interface IntMapFunction {
  public int f(int x);
}

Wenn Sie dann eine Sammlungsklasse mit einer Kartenfunktion hinzufügen, können Sie Folgendes tun:

myCollection.map(new IntMapFunction() { public int f(int x) { return x * x; } });

Hierbei wird eine Inline-Unterklasse von IntMapFunction verwendet, um einen Functor zu erstellen. Dies ist das OO-Äquivalent der Funktion aus dem früheren JavaScript-Beispiel.

Mit Functors können Sie Funktionstechniken in einer OO-Sprache anwenden. Natürlich unterstützen einige OO-Sprachen Funktionen auch direkt, sodass dies nicht erforderlich ist.

Referenz: http://en.wikipedia.org/wiki/Function_object

Kevin Greer
quelle
Eigentlich ist "Funktionsobjekt" keine korrekte Beschreibung eines Funktors. ZB Arrayist ein Funktor, Array(value)gibt aber nur 1-Element-Arrays.
Dmitri Zaitsev
0

KISS: Ein Funktor ist ein Objekt mit einer Kartenmethode.

Arrays in JavaScript implementieren Map und sind daher Funktoren. Versprechen, Streams und Bäume implementieren Karten häufig in funktionalen Sprachen, und wenn sie dies tun, werden sie als Funktoren betrachtet. Die Map-Methode des Funktors verwendet ihren eigenen Inhalt und transformiert jeden von ihnen mithilfe des an die Map übergebenen Transformationsrückrufs und gibt einen neuen Funktor zurück, der die Struktur als ersten Funktor enthält, jedoch die transformierten Werte enthält.

src: https://www.youtube.com/watch?v=DisD9ftUyCk&feature=youtu.be&t=76

Soundyogi
quelle
1
Mit der Randnotiz, dass "Objekt" sehr weit gefasst sein sollte und nur "etwas" bedeutet. Ersetzen Sie beispielsweise für OOP-Sprachen das Objekt durch die Klasse . Man könnte sagen, ein Funktor ist eine Klasse, die die Funktor-Schnittstelle implementiert. (Natürlich ist diese Schnittstelle möglicherweise nicht physisch vorhanden, aber Sie können die Zuordnungslogik auf diese Schnittstelle übertragen und alle Ihre zuordnungsfähigen Klassen gemeinsam nutzen - solange Ihr Typensystem die Eingabe von Dingen erlaubt, die so allgemein sind (d. h.).
Qqwy
1
Ich finde Klassen super verwirrend, um ehrlich zu sein, auf der einen Seite sind sie nur eine Blaupause für etwas Konkretes / aber sie können auch Methoden (statisches Zeug) haben und sich wie Objekte verhalten. Implementiert die Klasse die Schnittstelle oder die von ihr erstellte Instanz?
Soundyogi
1
Ja, sie können verwirrend sein. Aber: Klassen implementieren Schnittstellen (sie "füllen" die Lücken aus, die in den Schnittstellenmethoden angegeben wurden. Mit anderen Worten: Sie verwandeln die abstrakten Richtlinien der Schnittstelle in eine konkrete Richtlinie, die sofort instanziiert werden kann (verzeihen Sie das Wortspiel)). Zu 'Klassen verhalten sich wie Objekte': In echten OOP-Sprachen wie Ruby sind Klassen Instanzen der 'Klasse'-Klasse. Es sind Schildkröten den ganzen Weg nach unten.
Qqwy
ArrayTypkonstrukt definiert einen einzelnen Funktor. Seine Instanzen werden auch als "Arrays" bezeichnet, sind jedoch keine Funktoren. Die Beschreibung hier sollte präzisiert werden.
Dmitri Zaitsev
@DmitriZaitsev Könnten Sie näher darauf eingehen? Sie sagen also, dass Instanzen keine Funktoren sind? Ich sehe den Sinn darin nicht, da Sie einen neuen Funktor erhalten, indem Sie einen überordnen.
Soundyogi
-4

In der Praxis bedeutet Funktor ein Objekt, das den Aufrufoperator in C ++ implementiert. In ocaml bezieht sich functor meiner Meinung nach auf etwas, das ein Modul als Eingabe und Ausgabe eines anderen Moduls verwendet.

Feng
quelle
-6

Einfach ausgedrückt ist ein Funktor oder Funktionsobjekt ein Klassenobjekt, das genau wie eine Funktion aufgerufen werden kann.

In C ++:

So schreiben Sie eine Funktion

void foo()
{
    cout << "Hello, world! I'm a function!";
}

So schreiben Sie einen Funktor

class FunctorClass
{
    public:
    void operator ()
    {
        cout << "Hello, world! I'm a functor!";
    }
};

Jetzt können Sie dies tun:

foo(); //result: Hello, World! I'm a function!

FunctorClass bar;
bar(); //result: Hello, World! I'm a functor!

Was diese so großartig macht, ist, dass Sie den Status in der Klasse beibehalten können - stellen Sie sich vor, Sie möchten eine Funktion fragen, wie oft sie aufgerufen wurde. Es gibt keine Möglichkeit, dies ordentlich und gekapselt zu tun. Bei einem Funktionsobjekt ist es genau wie bei jeder anderen Klasse: Sie haben eine Instanzvariable, die Sie inkrementieren, operator ()und eine Methode, um diese Variable zu überprüfen, und alles ist ordentlich, wie Sie möchten.

Matt
quelle
12
Nein, diese Funktoren sind nicht der Begriff der Typentheorie, der von FP-Sprachen verwendet wird.
Tobu
1
Ich kann sehen, wie man beweisen kann, dass FunctorClassdas erste Funktorgesetz erfüllt ist, aber können Sie einen Beweis für das zweite Gesetz skizzieren? Ich sehe es nicht ganz.
Jörg W Mittag
3
Bah, ihr habt recht. Ich habe versucht, das Problem zu lösen, dass "das Web überaus technische Beschreibungen geliefert hat", und versucht zu vermeiden: "In der ML-Sprachfamilie ist ein Funktor ein Modul, das ein oder mehrere andere Module als Parameter verwendet." Diese Antwort ist jedoch schlecht. Übervereinfacht und unterbestimmt. Ich bin versucht, es zu toben, aber ich überlasse es zukünftigen Generationen, den Kopf zu schütteln :)
Matt
Ich bin froh, dass Sie die Antwort und die Kommentare hinterlassen haben, da dies dazu beiträgt, das Problem zu erfassen. Danke dir! Ich habe Probleme damit, dass die meisten Antworten in Haskell oder OCaml geschrieben sind, und für mich ist das ein bisschen so, als würde man Alligatoren in Krokodilen erklären.
Rob
-10

Functor ist nicht speziell mit der funktionalen Programmierung verbunden. Es ist nur ein "Zeiger" auf eine Funktion oder ein Objekt, das so aufgerufen werden kann, als wäre es eine Funktion.

Alemjerus
quelle
8
Es gibt ein spezifisches FP-Konzept eines Funktors (aus der Kategorietheorie), aber Sie haben Recht, dass dasselbe Wort auch für andere Dinge in Nicht-FP-Sprachen verwendet wird.
Craig Stuntz
Sind Sie sicher, dass Funktionszeiger Funktoren sind? Ich sehe nicht, wie Funktionszeiger die beiden Funktorgesetze erfüllen, insbesondere das zweite Funktorgesetz (Erhaltung der Morphismuszusammensetzung). Hast du einen Beweis dafür? (Nur eine grobe Skizze.)
Jörg W Mittag