2012-12-30 8 views
9

Die Functor Klasse enthält ein verstecktes zweites Element:

class Functor f where 
    fmap :: (a -> b) -> f a -> f b 
    (GHC.Base.<$) :: a -> f b -> f a 

Dokumentation:

alle Standorte mit dem gleichen Wert in der Eingabe ersetzen. Die Standarddefinition ist fmap . const, aber dies kann mit einer effizienteren Version überschrieben werden.

Ich möchte mehr wissen. Warum ist dieses fmap . const Idiom ein separates Mitglied? Wie könnte eine alternative Implementierung effizienter sein? Was sind Anwendungen dieses Kombinators?

+0

Im Wesentlichen könnten Sie einen effizienteren Anwendungsfall haben. Wenn Sie dies nicht tun, behalten Sie die Standardeinstellung bei. Es bedeutet, dass Sie sich nur für Struktur, nicht Wert interessieren. – PyRulez

Antwort

8

Es ist als Mitglied enthalten, um Benutzern zu ermöglichen, es für Geschwindigkeit anzupassen, und ich denke, da es mit >> konsistent macht.

Ich denke, es könnte schneller sein in den Fällen des Lesers Monad ((->) r).

x <$ _ = const x 

vs

x <$ fa = fmap (const x) fa = (const x) . fa 

obwohl, das ist wirklich eine Frage der Compiler-Optimierung. Und es scheint nicht für die Leser-Monade in der Basis definiert zu sein.

Es könnte auch zu einer Leistungssteigerung in strengen Sammlungen führen. Nämlich

data Strict a = Strict !a 

instance Functor Strict where 
    fmap f (Strict a) = Strict (f a) 
    x <$ _ = Strict x 

dies nicht gehorcht die Funktors Gesetze, aber dennoch, könnten Sie dies in einigen Situationen tun wollen.

Ein drittes Beispiel stammt aus unendlichen Sammlungen.Betrachten unendliche Listen

data Long a = Cons a (Long a) 

instance Functor Long where 
    fmap f (Cons x xs) = Cons (f x) (fmap f xs) 

der gut arbeitet, aber denken Sie

countUpFrom x = Cons x (countUpFrom (x+1)) 
ones = 1 <$ (countUpFrom 0) 

jetzt mit unserer Definition, die

ones = 1 <$ (countUpFrom 0) 
    = fmap (const 1) (countUpFrom 0) 
    = Cons (const 1 0) (fmap (const 1) (countUpFrom 1) 
    = Cons (const 1 0) (Cons (const 1 1) (fmap (const 1) (countUpFrom 2)) 

erweitern wird, das heißt, es wird eine ganze Reihe von zuteilen Cons Zellen, wie Sie diese Liste gehen. Während auf der anderen Seite, wenn Sie definiert

x <$ _ = let xs = Cons x xs in xs 

als

ones = 1 <$ countUpFrom 0 
= let xs = Cons 1 xs in xs 

, die den Knoten gebunden hat. Ein noch extremes Beispiel kommt mit unendlich vielen Bäumen

data ITree a = ITree a (ITree a) (ITree a) 
+0

Sie schienen '<$' to '$>' in der zweiten Hälfte Ihrer Antwort umgedreht zu haben. Tippfehler? – huon

+0

@dbaupp: Ich nahm an, also ging ich einfach weiter und reparierte es. –

+0

@dbaupp danke, das hatte ich getan. Ein Teil des Grundes, warum ich stark typisierte Sprachen mag, ist, dass sie Fehler wie diese fangen. –

3

Hier ein paar Code-Schnipsel aus etwas, was ich bin derzeit schriftlich, dass Sie eine Vorstellung davon geben können, worauf Sie diese combinator für verwenden würde:

pPrimType = choice 
    [ WIPrimIntType <$> flag "unsigned" <*> pIntTypeSize 
    , WIPrimFloatType <$> flag "unrestricted" <*> pFloatTypeSize 
    , WIPrimBoolType <$ "boolean" 
    , WIPrimByteType <$ "byte" 
    , WIPrimOctetType <$ "octet" 
    ] 

pConst = WIConst 
    <$ "const" 
    <*> pConstType 
    <*> pIdent 
    <* "=" 
    <*> pConstValue 
    <* semicolon 

Wenn die Stringliterale seltsam aussehen es ist, weil ich OverloadedStrings haben aktiviert und solche umgewandelt werden in Parsern, die eine Zeichenfolge übereinstimmen, während ein paar andere Dinge zu tun (Leerzeichen essen, für Token Grenzen Überprüfung & c.)

es scheint ziemlich trivial, aber ehrlich gesagt macht es Applicative -y Definitionen Parser ein Los besser lesbar, wenn Sie mit einem Parser führen, der nicht einen Wert erzeugt, den Sie interessieren, wie erforderliche Schlüsselwörter und so. Andernfalls müssen Sie eine Reihe von zusätzlichen pure s oder seltsame Klammern oder andere störende Geräusche einführen.

Aus Gründen, warum es Teil der Typklasse ist, ist der übliche Grund für das Hinzufügen einer ansonsten überflüssigen Funktion zu einer Typklasse die Erwartung, dass einige Instanzen in der Lage sein werden, sie zu optimieren, z. (>>). Da die Effizienzdifferenz von der Instanz abhängt (das ist der springende Punkt!) Gibt es keine einzige Antwort. Ich kann mir nicht sofort irgendwelche offensichtlichen Fälle vorstellen, in denen es einen großen Unterschied machen würde.

7

Ein weiteres Beispiel für <$ Nutzung:

Sie haben Funktors einen Parser Angenommen P und parser :: P A.

f <$> parser bedeutet, dass Sie etwas analysieren müssen und dann f auf das Ergebnis anwenden.

a <$ parser bedeutet, dass Sie brauchen, um alles nicht zu analysieren (Sie sind nicht daran interessiert, das Ergebnis) - Sie müssen nurerkennen, die viel schneller sein kann.

Siehe z.B. die regex-applicative Bibliothek (achten Sie auf die Verwendung des Void Konstruktors).