Warum sollte ich bei der funktionalen Programmierung anwendungsbezogene Funktoren verwenden?


78

Ich bin neu in Haskell und lese über Funktoren und anwendungsbezogene Funktoren. Ok, ich verstehe Funktoren und wie ich sie verwenden kann, aber ich verstehe nicht, warum anwendbare Funktoren nützlich sind und wie ich sie in Haskell verwenden kann. Können Sie mir anhand eines einfachen Beispiels erklären, warum ich anwendbare Funktoren benötige?


Es ist nur ein Link, den ich geben kann, aber hier ist eine schöne Beschreibung von Applicative Functors mit Beispielen.
Hartmut P.

Antworten:


56

Anwendbare Funktoren sind eine Konstruktion, die den Mittelpunkt zwischen Funktoren und Monaden bildet und daher weiter verbreitet ist als Monaden, während sie nützlicher als Funktoren ist. Normalerweise können Sie eine Funktion einfach über einen Funktor abbilden. Mit anwendbaren Funktoren können Sie eine "normale" Funktion (unter Verwendung nicht funktionaler Argumente) verwenden, um mehrere Werte zu bearbeiten, die sich in Funktorkontexten befinden. Als Konsequenz erhalten Sie so eine effektive Programmierung ohne Monaden.

Eine nette, in sich geschlossene Erklärung voller Beispiele finden Sie hier . Sie können auch ein praktisches Parsing-Beispiel lesen, das von Bryan O'Sullivan entwickelt wurde und für das keine Vorkenntnisse erforderlich sind.


2
Es gibt hilfreiche Links, aber ich glaube nicht, dass sie ein kurzes einfaches Beispiel ersetzen können, das sich auf die Beantwortung der Frage konzentriert, wobei alle unwichtigen Details so weit wie möglich entfernt werden.
Dmitri Zaitsev

34

Anwendbare Funktoren sind nützlich, wenn Sie eine Sequenzierung von Aktionen benötigen, aber keine Zwischenergebnisse benennen müssen. Sie sind daher schwächer als Monaden, aber stärker als Funktoren (sie haben keinen expliziten Bindungsoperator, erlauben jedoch die Ausführung beliebiger Funktionen innerhalb des Funktors).

Wann sind sie nützlich? Ein häufiges Beispiel ist das Parsen, bei dem Sie eine Reihe von Aktionen ausführen müssen, die Teile einer Datenstruktur der Reihe nach lesen und dann alle Ergebnisse zusammenfügen. Dies ist wie eine allgemeine Form der Funktionszusammensetzung:

f a b c d

wo Sie sich vorstellen können a, bund so weiter als willkürliche Aktionen, die ausgeführt werden sollen, und fals Funktor, der auf das Ergebnis angewendet werden soll .

f <$> a <*> b <*> c <*> d

Ich stelle sie mir gerne als überladenes "Leerzeichen" vor. Oder dass reguläre Haskell-Funktionen im Identitätsanwendungs-Funktor enthalten sind.

Siehe " Anwendbare Programmierung mit Effekten "


12

Conor McBride und Ross Patersons Functional Pearl über den Stil haben mehrere gute Beispiele. Es ist auch in erster Linie für die Popularisierung des Stils verantwortlich. Sie verwenden den Begriff "Redewendung" für "Anwendungsfunktor", aber ansonsten ist er ziemlich verständlich.


8

Es ist schwierig, Beispiele zu finden, bei denen Sie anwendbare Funktoren benötigen . Ich kann verstehen, warum sich ein fortgeschrittener Haskell-Programmierer diese Frage selbst stellen würde, da die meisten Einführungstexte Instanzen darstellen, die von Monaden abgeleitet sind und Applicative Functors nur als bequeme Schnittstelle verwenden.

Wie hier und in den meisten Einführungen zum Thema erwähnt, besteht die wichtigste Erkenntnis darin, dass sich anwendbare Funktoren zwischen Funktoren und Monaden befinden (sogar zwischen Funktoren und Pfeilen). Alle Monaden sind anwendbare Funktoren, aber nicht alle Funktoren sind anwendbar.

Daher müssen wir manchmal manchmal anwendbare Kombinatoren für etwas verwenden, für das wir keine monadischen Kombinatoren verwenden können. Eine solche Sache ist ZipList(siehe auch diese SO-Frage für einige Details ), die nur ein Wrapper um Listen ist, um eine andere anwendbare Instanz zu haben als die, die von der Monad-Instanz der Liste abgeleitet ist. In der anwendbaren Dokumentation wird die folgende Zeile verwendet, um eine intuitive Vorstellung davon zu geben, wofür ZipList:

f <$> ZipList xs1 <*> ... <*> ZipList xsn = ZipList (zipWithn f xs1 ... xsn)

Wie hier erwähnt , ist es möglich, eigenartige Monad-Instanzen zu erstellen, die fast für ZipList funktionieren.

Es gibt andere Applicative Functors, die keine Monaden sind (siehe diese SO-Frage) und die leicht zu finden sind. Eine alternative Schnittstelle für Monaden zu haben ist nett und alles, aber manchmal ist es ineffizient, kompliziert oder sogar unmöglich, eine Monade zu erstellen, und dann benötigen Sie Applicative Functors.


Haftungsausschluss: Die Herstellung von Anwendungsfunktionen kann auch ineffizient, kompliziert und unmöglich sein. Wenden Sie sich im Zweifelsfall an Ihren lokalen Kategorietheoretiker, um Informationen zur korrekten Verwendung von Anwendungsfunktionen zu erhalten.


7

Nach meiner Erfahrung sind Applikative Funktoren aus folgenden Gründen großartig:

Bestimmte Arten von Datenstrukturen lassen mächtige Arten von Kompositionen zu, können aber nicht wirklich zu Monaden gemacht werden. Tatsächlich fallen die meisten Abstraktionen in der funktionalen reaktiven Programmierung in diese Kategorie. Während wir technisch in der Lage sein könnten, zB Behavior(aka Signal) eine Monade zu machen, kann dies normalerweise nicht effizient durchgeführt werden. Applikative Funktoren ermöglichen es uns, immer noch kraftvolle Kompositionen zu haben, ohne die Effizienz zu beeinträchtigen (zugegebenermaßen ist es manchmal etwas schwieriger, ein Applikativ als eine Monade zu verwenden, nur weil Sie nicht so viel Struktur haben, mit der Sie arbeiten können).

Das Fehlen einer Datenabhängigkeit in einem anwendbaren Funktor ermöglicht es Ihnen, z. B. eine Aktion zu durchlaufen, um nach allen möglichen Effekten zu suchen, ohne dass die Daten verfügbar sind. Sie können sich also ein "Webformular" -Anwendungsprodukt vorstellen, das wie folgt verwendet wird:

userData = User <$> field "Name" <*> field "Address"

und Sie könnten eine Engine schreiben, die alle verwendeten Felder durchsucht und in einem Formular anzeigt. Wenn Sie die Daten zurückerhalten, führen Sie sie erneut aus, um die erstellten zu erhalten User. Dies kann nicht mit einem einfachen Funktor (weil er zwei Formen zu einer kombiniert) oder einer Monade durchgeführt werden, da man mit einer Monade Folgendes ausdrücken könnte:

userData = do
    name <- field "Name"
    address <- field $ name ++ "'s address"
    return (User name address)

Dies kann nicht gerendert werden, da der Name des zweiten Felds nicht bekannt ist, ohne dass bereits die Antwort des ersten Felds vorliegt. Ich bin mir ziemlich sicher, dass es eine Bibliothek gibt, die diese Formularidee umsetzt - ich habe meine eigene ein paar Mal für dieses und jenes Projekt gerollt.

Das andere schöne an anwendungsbezogenen Funktoren ist, dass sie komponieren . Genauer gesagt, der Kompositionsfunktor:

newtype Compose f g x = Compose (f (g x))

ist anwendbar, wann immer fund gsind. Das Gleiche gilt nicht für Monaden, die die gesamte Geschichte der Monadentransformatoren hervorgebracht haben, die auf unangenehme Weise kompliziert ist. Applikatoren sind auf diese Weise super sauber und es bedeutet, dass Sie die Struktur eines Typs aufbauen können, den Sie benötigen, indem Sie sich auf kleine zusammensetzbare Komponenten konzentrieren.

Kürzlich wurde die ApplicativeDoErweiterung in GHC veröffentlicht, mit der Sie die doNotation mit Applikativen verwenden können, wodurch ein Teil der Komplexität der Notation verringert wird, solange Sie keine monadischen Dinge tun.


6

Ein gutes Beispiel: Anwendungsanalyse.

Siehe [real world haskell] ch16 http://book.realworldhaskell.org/read/using-parsec.html#id652517

Dies ist der Parser-Code mit Do-Notation:

-- file: ch16/FormApp.hs
p_hex :: CharParser () Char
p_hex = do
  char '%'
  a <- hexDigit
  b <- hexDigit
  let ((d, _):_) = readHex [a,b]
  return . toEnum $ d

Mit dem Funktor wird es viel kürzer :

-- file: ch16/FormApp.hs
a_hex = hexify <$> (char '%' *> hexDigit) <*> hexDigit
    where hexify a b = toEnum . fst . head . readHex $ [a,b]

'Heben' kann die zugrunde liegenden Details eines sich wiederholenden Codes verbergen. Dann können Sie einfach weniger Wörter verwenden, um die genaue und genaue Geschichte zu erzählen.


4
Sie haben eine seltsame Vorstellung von "viel kürzer" - die anwendbare Version ist 7 Zeichen länger!
Daniel Wagner

@ Daniel Wagner: -_- ||, Oh mein .. du hast einen guten Einblick in den Code, ich muss gestehen. Ich
meine

Nun, es kann mit allgemeinen Funktionen aus der Control.Monad-Bibliothek kürzer geschrieben werden : char '%' >> liftM (toEnum . fst . head . readHex) (replicateM 2 hexDigit).
HaskellElephant

Oder verwenden Sie den countKombinator von Parsecund wechseln Sie zurück zum Anwendungsstil:toEnum . fst . head . readHex <$> (char '%' >> count 2 hexDigit)
Pat

3

Ich würde auch einen Blick auf nehmen vorschlagen diese

Am Ende des Artikels gibt es ein Beispiel

import Control.Applicative
hasCommentA blogComments =
BlogComment <$> lookup "title" blogComments
            <*> lookup "user" blogComments
            <*> lookup "comment" blogComments

Dies zeigt verschiedene Merkmale des anwendbaren Programmierstils.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.