2015-09-09 13 views
8

Ich bin neu bei Haskell und bin rätselhaft darüber, wie man einige Operationen am besten auf die idiomatische und klare Art ausdrücken kann. Momentan (es wird noch mehr geben) rätsele ich über <*> (ich bin mir nicht einmal sicher, wie ich das nennen soll).Haskell Applicative Idiom?

Zum Beispiel, wenn ich, sagen

f = (^2) 
g = (+10) 

als repräsentative Funktionen (in der Praxis werden sie komplexer sind, aber der Schlüssel ist dabei, dass sie verschieden sind und verschieden sind), dann

concatMap ($ [1,2,3,4,10]) [(f <$>), (g <$>) . tail . reverse] 

und

concat $ [(f <$>), (g <$>) . tail . reverse] <*> [[1,2,3,4,10]] 

erreichen die gleiche Sache.

Ist einer dieser mehr idiomatischen Haskell, impliziert man etwas, ein erfahrener Leser von Haskell, dass der andere nicht. Vielleicht gibt es zusätzliche (bessere) Möglichkeiten, genau das Gleiche auszudrücken. Gibt es konzeptionelle Unterschiede zwischen den beiden Ansätzen, die ein Anfänger Haskeller wie ich vielleicht fehlt?

+0

Ist Ihre Eingabe immer nur eine Liste sein würde? Wenn ja, ist Ersteres deutlich besser. Mit Eingabe meine ich die Liste der Zahlen. – itsbruce

+0

@itsbruce: Guter Punkt. Ja, immer nur eine einzige Liste. – orome

+0

Das Kombinieren der 'Applicative' oder' Functor' Instanz für Listen mit 'concat' ist sehr verdächtig. 'Functor + return + join = Monad', und für Listen,' concat = join'. Wenn Sie mappen und dann verketten, kann 'concatMap' oder' = << 'prägnanter sein. – dfeuer

Antwort

10

Sowohl Ihre Funktionen (f <$>) und (g <$>).tail.reverse ein Monoid Typ zurückgeben (Liste in diesem Fall), so können Sie mconcat verwenden sie in eine einzelne Funktion zu konvertieren. Dann können Sie diese Funktion direkt an die Eingabeliste anwenden, anstatt sie in einer anderen Liste Einwickeln und mit concatMap:

mconcat [(f <$>), (g <$>).tail.reverse] $ [1,2,3,4,10] 

auf diese zu erweitern, eine Funktion a -> b ist eine Instanz von Monoid wenn b ein Monoid ist. Die implementation von mappend für solche Funktionen ist:

mappend f g x = f x `mappend` g x 

oder äquivalent

mappend f g = \x -> (f x) `mappend` (g x) 

so zwei Funktionen f und g gegeben, die einen monoid Typ b, f mappend zurück g eine Funktion gibt, die ihr Argument gilt f und g und kombiniert die Ergebnisse unter Verwendung der Monoid Instanz von b.

mconcat hat den Typ Monoid a => [a] -> a und kombiniert alle Elemente der Eingabeliste mit mappend.

Listen sind Monoide wo mappend == (++) so

mconcat [(f <$>), (g <$>).tail.reverse] 

eine Funktion gibt wie

\x -> (fmap f x) ++ (((fmap g) . tail . reverse) x) 
+1

Das ist ziemlich interessant. Es dauerte einige Zeit zu verstehen, aber [die Quelle für die Funktionsinstanz von 'Monoid'] (http://hackage.haskell.org/package/base-4.8.1.0/docs/src/GHC.Base.html # line-255) räumt auf.'(f <$>)' ergibt '([f] <*>)', das den Typ '[a] -> [a]' hat, sein Rückgabetyp '[a]' ist eine Instanz von 'Monoid' und macht die Funktion zu Instanz von 'Monoid' selbst. –

+0

@SamvanHerwaarden: Ich sehe das '(f <$>) :: [a] -> [a]' aber habe Probleme beim Visualisieren, welche Art von Funktion 'mconcat [(f <$>), (g <$>) .tail.reverse] 'ist. Ich sehe, dass es sein muss "(Num b) => [b] -> [b]", aber kann nicht ganz visualisieren, wie es "aussieht". – orome

+1

Ich sagte '[a] -> [a]' aber du hast recht mit der 'Num'-Einschränkung, die auch hier gilt (ich war schlampig und habe sie weggelassen). 'mconcat' durchsetzt eine Liste mit' mappend', so dass wir 'mconcat [(f <$>), (g <$>) erhalten. Schwanz. reverse] = \ x -> (([f] <$>) x) \ 'mappend \' (((<$>). tail. reverse) x) so werden alle Funktionen in der Liste mit dem Argument geliefert, das an 'mconcat übergeben wird func "werden die Ausgaben dieser Funktionen dann mit" mappend "verknüpft. In unserem Fall gibt die Funktion lists 'mappend = (++) 'aus, für andere Monoids würde der entsprechende' mappend' verwendet. –

9

persönlich für Ihr Beispiel I

f = (^2) 
g = (+10) 

let xs = [1,2,3,4,10] 
in (map f xs) ++ (map g . tail $ reverse xs) 

In einer sehr Applicative "Stimmung" schreiben würde, würde ich das Teil nach in von

((++) <$> map f <*> map g . tail . reverse) xs 

ersetzen, die ich glaube eigentlich nicht mehr ist in diesem Fall lesbar. Wenn Sie nicht direkt verstehen, was es bedeutet, verbringen Sie etwas Zeit, um die Applicative Instanz von ((->) a) (Reader) zu verstehen.

Ich denke, die Wahl hängt wirklich davon ab, was Sie zu tun versuchen, d. H. Was Ihre Ausgabe bedeuten soll. In Ihrem Beispiel ist die Aufgabe sehr abstrakt (im Grunde wird nur gezeigt, was Applicative kann), so dass es nicht direkt offensichtlich ist, welche Version zu verwenden ist.

Die Applicative Instanz [] bezieht sich intuitiv Kombinationen, so würde ich es in einer Situation wie folgt verwenden:

-- I want all pair combinations of 1 to 5 
(,) <$> [1..5] <*> [1..5] 

Wenn Sie viele Funktionen haben würde, und Sie würden alle Kombinationen dieser Funktionen ausprobieren möchten Mit einer Reihe von Argumenten würde ich tatsächlich die [] Instanz von Applicative verwenden. Aber wenn das, was Sie suchen, eine Verkettung verschiedener Transformationen ist, würde ich es als solches schreiben (was ich oben getan habe).

Nur meine 2 Cent als Medium-Erfahrung Haskeller.

1

Ich habe manchmal mit dem ähnlichen Problem zu kämpfen. Sie haben ein einzelnes Element, aber mehrere Funktionen.

Normalerweise haben wir mehrere Elemente, und einzelne Funktion: so tun wir:

map f xs 

Aber es ist nicht das Problem in Haskell.Die Dual ist so einfach:

map ($ x) fs 

Die Tatsache, dass Ihr x ist eigentlich eine Liste, und Sie wollen concat nach den map, so dass Sie

concatMap ($ xs) fs 

tun kann ich nicht wirklich verstehen, was in der zweiten Gleichung direkt passiert, kann ich sogar meinen, dass es dasselbe tut wie das erste, das anwendbare Gesetze verwendet.