2012-12-30 8 views
9

Klasa Functor zawiera ukryty drugi człon:

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

Dokumentacja:

Wymień wszystkie lokalizacje na wejściu o tej samej wartości. Domyślna definicja to fmap . const, ale można ją zastąpić bardziej wydajną wersją.

Chciałbym wiedzieć więcej. Dlaczego ten idiom fmap . const jest osobnym członkiem? W jaki sposób alternatywna implementacja może być bardziej wydajna? Jakie są zastosowania tego kombinatora?

+0

Zasadniczo możesz mieć bardziej efektywny przypadek użycia. Jeśli nie, pozostaw domyślne. Oznacza to, że dbasz tylko o strukturę, a nie wartość. – PyRulez

Odpowiedz

8

Jest dołączany jako członek, aby umożliwić użytkownikom dostosowanie go do szybkości i wydaje mi się, że jest zgodny z >>.

Myślę, że może być szybciej w przypadku monada czytelnika ((->) r).

x <$ _ = const x 

vs

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

chociaż, to jest naprawdę kwestia optymalizacji kompilatora. I wydaje się, że nie jest on zdefiniowany dla monady czytelnika w bazie.

Może to również prowadzić do zwiększenia wydajności w przypadku ścisłych kolekcji. Mianowicie:

data Strict a = Strict !a 

instance Functor Strict where 
    fmap f (Strict a) = Strict (f a) 
    x <$ _ = Strict x 
nie jest to zgodne z przepisami obowiązującymi w zakresie funktur, ale mimo to możesz chcieć to zrobić w niektórych sytuacjach.

Trzeci przykład pochodzi z nieskończonych zbiorów.Rozważmy nieskończoną list

data Long a = Cons a (Long a) 

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

który działa dobrze, ale myślę o

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

teraz, z naszej definicji, która będzie rozwijać się

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)) 

że jest, to będzie przeznaczyć całą masę Cons komórki podczas przechodzenia przez tę listę. Chociaż, z drugiej strony, jeśli zdefiniowano

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

niż

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

które weszło w związki małżeńskie. Jeszcze bardziej skrajny przykład pochodzi z nieskończonych drzewach

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

Wygląda na to, że w drugiej połowie odpowiedzi zdałeś "<$' to '$>". Typo? – huon

+0

@dbaupp: Zakładałem, że tak, więc po prostu poszedłem naprzód i naprawiłem to. –

+0

@dbaupp dzięki, zrobiłem to. Jednym z powodów, dla których lubię mocno typowane języki, jest pomaganie w łapaniu takich błędów. –

3

Tutaj to fragmenty kodu para z czymś Obecnie piszę, że może dać wyobrażenie o tym, co chcesz skorzystać z tej COMBINATOR dla:

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

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

Jeśli literały łańcuchowe wyglądać dziwnie, że to dlatego, że mam OverloadedStrings włączone, a te są przekształcone parserami pasujących ciąg robiąc kilka innych rzeczy (jedzenie spacje, sprawdzanie granic tokenów, & c.)

wydaje się dość banalne, ale szczerze to sprawia Applicative -y polecenia definicje parsera do lot bardziej czytelny, gdy prowadzisz za pomocą analizatora składni, który nie generuje wartości, na której Ci zależy, takich jak wymagane słowa kluczowe i inne. W przeciwnym razie musisz wprowadzić kilka dodatkowych dziwnych nawiasów lub innych zakłócających uwagę zakłóceń.

Co do tego, dlaczego jest częścią klasy typów, powodem do nadania niepotrzebnej funkcji klasie typów jest oczekiwanie, że niektóre wystąpienia będą w stanie ją zoptymalizować, np. (>>). Ponieważ różnica wydajności zależy od instancji (to cały punkt!), Nie ma tam jednej odpowiedzi. Nie mogę od razu pomyśleć o żadnych oczywistych przypadkach, w których miałoby to duże znaczenie.

7

Innym przykładem <$ użycia:

Załóżmy, że masz funktor parsera P i parser :: P A.

f <$> parser oznacza, że ​​należy parsować coś, a następnie zastosować f do wyniku.

a <$ parser oznacza, że ​​nie musisz niczego analizować (nie jesteś zainteresowany wynikiem) - musisz tylko rozpoznać, która może być znacznie szybsza.

Zobacz np. biblioteka regex-applicative (zwróć uwagę na użycie konstruktora Void).